diff --git a/.editorconfig b/.editorconfig
index d82e66309b..6c5221951f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,240 +1,240 @@
-###############################
-# Core EditorConfig Options #
-###############################
-root = true
-
-[obsolete/*]
-generated_code = true
-
-# All files
-[*]
-tab_width = 4
-end_of_line = crlf
-indent_style = space
-
-# XML project files
-[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
-indent_size = 2
-
-# XML config files
-[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
-indent_size = 2
-
-###############################
-# .NET Coding Conventions #
-###############################
-[*.{cs,vb}]
-
-# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1826#exclude-firstordefault-and-lastordefault-methods
-dotnet_code_quality.CA1826.exclude_ordefault_methods = true
-
-# Collection Expressions - int[] numbers = []
-dotnet_style_collection_initializer = true:error
-dotnet_style_prefer_collection_expression = true:error
-
-# Nullable reference types
-dotnet_diagnostic.CS8600.severity = suggestion
-dotnet_diagnostic.CS8601.severity = suggestion
-dotnet_diagnostic.CS8602.severity = suggestion
-dotnet_diagnostic.CS8603.severity = suggestion
-dotnet_diagnostic.CS8604.severity = suggestion
-dotnet_diagnostic.CS8618.severity = suggestion
-dotnet_diagnostic.CS8619.severity = suggestion
-dotnet_diagnostic.CS8620.severity = suggestion
-dotnet_diagnostic.CS8625.severity = suggestion
-dotnet_diagnostic.CS8629.severity = suggestion
-dotnet_diagnostic.CS8632.severity = suggestion
-dotnet_diagnostic.CS8764.severity = suggestion
-dotnet_diagnostic.CS8765.severity = suggestion
-dotnet_diagnostic.CS8767.severity = suggestion
-
-dotnet_diagnostic.IDE0021.severity = error
-dotnet_diagnostic.IDE0022.severity = error
-dotnet_diagnostic.IDE0023.severity = error
-dotnet_diagnostic.IDE0024.severity = error
-dotnet_diagnostic.IDE0025.severity = error
-dotnet_diagnostic.IDE0026.severity = error
-dotnet_diagnostic.IDE0027.severity = error
-
-# Suppress CS1591: Missing XML comment for publicly visible type or member
-dotnet_diagnostic.CS1591.severity = suggestion
-
-# Suppress IDE0058: Expression value is never used
-dotnet_diagnostic.IDE0058.severity = none
-
-# Organize usings
-dotnet_sort_system_directives_first = true:error
-dotnet_separate_import_directive_groups = true:error
-
-# Unfortunately, no java-style usings
-dotnet_diagnostic.IDE0064.severity = none
-
-# Using directive is unnecessary.
-dotnet_diagnostic.IDE0005.severity = error
-
-# this. preferences
-dotnet_style_qualification_for_field = true:error
-dotnet_style_qualification_for_property = true:error
-dotnet_style_qualification_for_method = false:error
-dotnet_style_qualification_for_event = true:error
-
-# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:error
-dotnet_style_predefined_type_for_member_access = true:error
-
-# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:error
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:error
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:error
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:error
-
-# Modifier preferences
-dotnet_style_require_accessibility_modifiers = always:error
-dotnet_style_readonly_field = true:error
-
-# Expression-level preferences
-dotnet_style_object_initializer = true:error
-dotnet_style_collection_initializer = true:error
-dotnet_style_explicit_tuple_names = true:error
-dotnet_style_null_propagation = true:error
-dotnet_style_coalesce_expression = true:error
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
-dotnet_style_prefer_inferred_tuple_names = true:error
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
-dotnet_style_prefer_auto_properties = true:error
-dotnet_style_prefer_conditional_expression_over_assignment = true:error
-dotnet_style_prefer_conditional_expression_over_return = true:error
-
-# Don't force namespaces to match their folder names (DSharpPlus.Entities)
-dotnet_diagnostic.IDE0130.severity = none
-dotnet_style_namespace_match_folder = false
-
-dotnet_style_prefer_simplified_boolean_expressions = true:error
-dotnet_style_operator_placement_when_wrapping = beginning_of_line:error
-
-###############################
-# Naming Conventions #
-###############################
-
-# Style Definitions
-dotnet_naming_style.pascal_case_style.capitalization = pascal_case
-
-# Use PascalCase for constant fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error
-dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
-dotnet_naming_symbols.constant_fields.applicable_kinds = field
-dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
-dotnet_naming_symbols.constant_fields.required_modifiers = const
-
-# Interfaces should start with I
-dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
-dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
-dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
-dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.capitalization = pascal_case
-dotnet_naming_symbols.interface.applicable_kinds = interface
-
-# Async Methods should end with Async
-dotnet_naming_rule.async_methods_should_be_async_suffix.severity = error
-dotnet_naming_rule.async_methods_should_be_async_suffix.symbols = async_methods
-dotnet_naming_rule.async_methods_should_be_async_suffix.style = async_suffix
-dotnet_naming_style.async_suffix.required_suffix = Async
-dotnet_naming_style.async_suffix.capitalization = pascal_case
-dotnet_naming_symbols.async_methods.applicable_kinds = method
-dotnet_naming_symbols.async_methods.required_modifiers = async
-
-# Fields should not begin with underscores, should be camelCase and should not use word separators
-dotnet_naming_rule.all_fields_notunderscored.symbols = all_fields
-dotnet_naming_rule.all_fields_notunderscored.style = notunderscored
-dotnet_naming_rule.all_fields_notunderscored.severity = error
-dotnet_naming_style.notunderscored.capitalization = camel_case
-dotnet_naming_style.notunderscored.required_prefix =
-dotnet_naming_style.notunderscored.word_separator =
-dotnet_naming_symbols.all_fields.applicable_kinds = field
-dotnet_naming_symbols.all_fields.applicable_accessibilities = *
-
-###############################
-# C# Coding Conventions #
-###############################
-[*.cs]
-
-# File-scoped namespaces
-csharp_style_namespace_declarations = file_scoped:error
-
-# var preferences
-csharp_style_var_for_built_in_types = false:error
-csharp_style_var_when_type_is_apparent = false:error
-csharp_style_var_elsewhere = false:error
-
-# Pattern matching preferences
-csharp_style_pattern_matching_over_is_with_cast_check = true:error
-csharp_style_pattern_matching_over_as_with_null_check = true:error
-
-# Null-checking preferences
-csharp_style_throw_expression = true:error
-csharp_style_conditional_delegate_call = true:error
-
-# Modifier preferences
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:error
-
-# Expression-level preferences
-csharp_prefer_braces = true:error
-csharp_style_deconstructed_variable_declaration = true:error
-csharp_prefer_simple_default_expression = true:error
-csharp_style_pattern_local_over_anonymous_function = true:error
-csharp_style_inlined_variable_declaration = true:error
-
-###############################
-# C# Formatting Rules #
-###############################
-
-# https://stackoverflow.com/q/63369382/10942966
-csharp_qualified_using_at_nested_scope = true:error
-csharp_using_directive_placement = outside_namespace:error
-
-# New line preferences
-csharp_new_line_before_open_brace = all
-csharp_new_line_before_else = true
-csharp_new_line_before_catch = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_between_query_expression_clauses = true
-
-# Indentation preferences
-charset = utf-8
-indent_size = 4
-insert_final_newline = true
-csharp_indent_case_contents = true
-csharp_indent_switch_labels = true
-csharp_indent_labels = flush_left
-
-# Space preferences
-csharp_space_after_cast = false
-csharp_space_after_keywords_in_control_flow_statements = true
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_around_binary_operators = before_and_after
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-
-# Wrapping preferences
-csharp_preserve_single_line_statements = false
-csharp_preserve_single_line_blocks = true
-
-# Use regular constructors over primary constructors
-csharp_style_prefer_primary_constructors = false:error
-
-# Expression-bodied members
-csharp_style_expression_bodied_methods = when_on_single_line:error
-csharp_style_expression_bodied_constructors = when_on_single_line:error
-csharp_style_expression_bodied_operators = when_on_single_line:error
-csharp_style_expression_bodied_properties = when_on_single_line:error
-csharp_style_expression_bodied_indexers = when_on_single_line:error
-csharp_style_expression_bodied_accessors = when_on_single_line:error
+###############################
+# Core EditorConfig Options #
+###############################
+root = true
+
+[obsolete/*]
+generated_code = true
+
+# All files
+[*]
+tab_width = 4
+end_of_line = crlf
+indent_style = space
+
+# XML project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+###############################
+# .NET Coding Conventions #
+###############################
+[*.{cs,vb}]
+
+# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1826#exclude-firstordefault-and-lastordefault-methods
+dotnet_code_quality.CA1826.exclude_ordefault_methods = true
+
+# Collection Expressions - int[] numbers = []
+dotnet_style_collection_initializer = true:error
+dotnet_style_prefer_collection_expression = true:error
+
+# Nullable reference types
+dotnet_diagnostic.CS8600.severity = suggestion
+dotnet_diagnostic.CS8601.severity = suggestion
+dotnet_diagnostic.CS8602.severity = suggestion
+dotnet_diagnostic.CS8603.severity = suggestion
+dotnet_diagnostic.CS8604.severity = suggestion
+dotnet_diagnostic.CS8618.severity = suggestion
+dotnet_diagnostic.CS8619.severity = suggestion
+dotnet_diagnostic.CS8620.severity = suggestion
+dotnet_diagnostic.CS8625.severity = suggestion
+dotnet_diagnostic.CS8629.severity = suggestion
+dotnet_diagnostic.CS8632.severity = suggestion
+dotnet_diagnostic.CS8764.severity = suggestion
+dotnet_diagnostic.CS8765.severity = suggestion
+dotnet_diagnostic.CS8767.severity = suggestion
+
+dotnet_diagnostic.IDE0021.severity = error
+dotnet_diagnostic.IDE0022.severity = error
+dotnet_diagnostic.IDE0023.severity = error
+dotnet_diagnostic.IDE0024.severity = error
+dotnet_diagnostic.IDE0025.severity = error
+dotnet_diagnostic.IDE0026.severity = error
+dotnet_diagnostic.IDE0027.severity = error
+
+# Suppress CS1591: Missing XML comment for publicly visible type or member
+dotnet_diagnostic.CS1591.severity = suggestion
+
+# Suppress IDE0058: Expression value is never used
+dotnet_diagnostic.IDE0058.severity = none
+
+# Organize usings
+dotnet_sort_system_directives_first = true:error
+dotnet_separate_import_directive_groups = true:error
+
+# Unfortunately, no java-style usings
+dotnet_diagnostic.IDE0064.severity = none
+
+# Using directive is unnecessary.
+dotnet_diagnostic.IDE0005.severity = error
+
+# this. preferences
+dotnet_style_qualification_for_field = true:error
+dotnet_style_qualification_for_property = true:error
+dotnet_style_qualification_for_method = false:error
+dotnet_style_qualification_for_event = true:error
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:error
+dotnet_style_predefined_type_for_member_access = true:error
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:error
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:error
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:error
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:error
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = always:error
+dotnet_style_readonly_field = true:error
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:error
+dotnet_style_collection_initializer = true:error
+dotnet_style_explicit_tuple_names = true:error
+dotnet_style_null_propagation = true:error
+dotnet_style_coalesce_expression = true:error
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error
+dotnet_style_prefer_inferred_tuple_names = true:error
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:error
+dotnet_style_prefer_auto_properties = true:error
+dotnet_style_prefer_conditional_expression_over_assignment = true:error
+dotnet_style_prefer_conditional_expression_over_return = true:error
+
+# Don't force namespaces to match their folder names (DSharpPlus.Entities)
+dotnet_diagnostic.IDE0130.severity = none
+dotnet_style_namespace_match_folder = false
+
+dotnet_style_prefer_simplified_boolean_expressions = true:error
+dotnet_style_operator_placement_when_wrapping = beginning_of_line:error
+
+###############################
+# Naming Conventions #
+###############################
+
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# Use PascalCase for constant fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+
+# Interfaces should start with I
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = error
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+dotnet_naming_symbols.interface.applicable_kinds = interface
+
+# Async Methods should end with Async
+dotnet_naming_rule.async_methods_should_be_async_suffix.severity = error
+dotnet_naming_rule.async_methods_should_be_async_suffix.symbols = async_methods
+dotnet_naming_rule.async_methods_should_be_async_suffix.style = async_suffix
+dotnet_naming_style.async_suffix.required_suffix = Async
+dotnet_naming_style.async_suffix.capitalization = pascal_case
+dotnet_naming_symbols.async_methods.applicable_kinds = method
+dotnet_naming_symbols.async_methods.required_modifiers = async
+
+# Fields should not begin with underscores, should be camelCase and should not use word separators
+dotnet_naming_rule.all_fields_notunderscored.symbols = all_fields
+dotnet_naming_rule.all_fields_notunderscored.style = notunderscored
+dotnet_naming_rule.all_fields_notunderscored.severity = error
+dotnet_naming_style.notunderscored.capitalization = camel_case
+dotnet_naming_style.notunderscored.required_prefix =
+dotnet_naming_style.notunderscored.word_separator =
+dotnet_naming_symbols.all_fields.applicable_kinds = field
+dotnet_naming_symbols.all_fields.applicable_accessibilities = *
+
+###############################
+# C# Coding Conventions #
+###############################
+[*.cs]
+
+# File-scoped namespaces
+csharp_style_namespace_declarations = file_scoped:error
+
+# var preferences
+csharp_style_var_for_built_in_types = false:error
+csharp_style_var_when_type_is_apparent = false:error
+csharp_style_var_elsewhere = false:error
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:error
+csharp_style_pattern_matching_over_as_with_null_check = true:error
+
+# Null-checking preferences
+csharp_style_throw_expression = true:error
+csharp_style_conditional_delegate_call = true:error
+
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:error
+
+# Expression-level preferences
+csharp_prefer_braces = true:error
+csharp_style_deconstructed_variable_declaration = true:error
+csharp_prefer_simple_default_expression = true:error
+csharp_style_pattern_local_over_anonymous_function = true:error
+csharp_style_inlined_variable_declaration = true:error
+
+###############################
+# C# Formatting Rules #
+###############################
+
+# https://stackoverflow.com/q/63369382/10942966
+csharp_qualified_using_at_nested_scope = true:error
+csharp_using_directive_placement = outside_namespace:error
+
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+charset = utf-8
+indent_size = 4
+insert_final_newline = true
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+
+# Wrapping preferences
+csharp_preserve_single_line_statements = false
+csharp_preserve_single_line_blocks = true
+
+# Use regular constructors over primary constructors
+csharp_style_prefer_primary_constructors = false:error
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = when_on_single_line:error
+csharp_style_expression_bodied_constructors = when_on_single_line:error
+csharp_style_expression_bodied_operators = when_on_single_line:error
+csharp_style_expression_bodied_properties = when_on_single_line:error
+csharp_style_expression_bodied_indexers = when_on_single_line:error
+csharp_style_expression_bodied_accessors = when_on_single_line:error
csharp_style_expression_bodied_lambdas = when_on_single_line:error
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..3332e4e4ad
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+*.cs text eol=crlf
+*.json text eol=crlf
+*.txt text eol=crlf
+*.md text eol=crlf
+*.yml text eol=crlf
+*.sln text eol=crlf
+*.csproj text eol=crlf
+*.sh text eol=lf
+.* text eol=crlf
\ No newline at end of file
diff --git a/.github/workflows/build-commit.yml b/.github/workflows/build-commit.yml
index 2f09025cf6..bec933b9d0 100644
--- a/.github/workflows/build-commit.yml
+++ b/.github/workflows/build-commit.yml
@@ -1,112 +1,112 @@
-name: Build Commit
-
-on:
- push:
- branches: [master]
- paths:
- - ".github/workflows/build-commit.yml"
- - "DSharpPlus*/**"
- - "tools/**"
- - "docs/**"
- - "*.sln"
- - "obsolete/DSharpPlus*/**"
- workflow_dispatch:
-
-env:
- DOTNET_NOLOGO: 1
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
-
-jobs:
- package-commit:
- name: Package Commit
- runs-on: ubuntu-latest
- if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- fetch-depth: 0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 9
- - name: Build Project
- run: dotnet build
- - name: Test Changes
- run: dotnet test --blame-crash --blame-hang --blame-hang-timeout "30s"
- - name: Get Nightly Version
- id: nightly
- run: printf "version=%0*d" 5 $(( 1195 + 691 + ${{ github.run_number }} )) >> "$GITHUB_OUTPUT"
- - name: Package Project
- run: |
- # We add 1195 since it's the last build number AppVeyor used.
- # We add 686 since it's the last build number GitHub Actions used before the workflow was renamed.
- dotnet pack -c Release -o build -p:Nightly=${{ steps.nightly.outputs.version }}
- dotnet nuget push "build/*" --skip-duplicate -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json
- env:
- DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
- DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
- DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
- DISCORD_CHANNEL_TOPIC: ${{ secrets.DISCORD_CHANNEL_TOPIC }}
- DISCORD_DOC_BOT_USER_ID: ${{ secrets.DISCORD_DOC_BOT_USER_ID }}
- DISCORD_BOT_USAGE_CHANNEL_ID: ${{ secrets.DISCORD_BOT_USAGE_CHANNEL_ID }}
- NUGET_URL: ${{ secrets.NUGET_URL }}
- GITHUB_URL : ${{ github.server_url }}/${{ github.repository }}
- - name: Upload Artifact
- uses: actions/upload-artifact@v4
- with:
- name: DSharpPlus-Nightly-${{ steps.nightly.outputs.version }}.zip
- path: ./build/*
- - name: Get commit message header
- id: get_commit_message
- run: |
- commit_message_header=$(git log -1 --pretty=%B | head -n 1)
- echo "message_header=${commit_message_header}" >> $GITHUB_OUTPUT
- - name: Discord Webhook
- uses: tsickert/discord-webhook@v4.0.0
- with:
- webhook-url: ${{ secrets.BUILD_WEBHOOK }}
- embed-title: DSharpPlus 5.0.0-nightly-${{ steps.nightly.outputs.version }}
- embed-description: |
- NuGet Link: [`5.0.0-nightly-${{ steps.nightly.outputs.version }}`](https://www.nuget.org/packages/DSharpPlus/5.0.0-nightly-${{ steps.nightly.outputs.version }})
- Commit hash: [`${{ github.sha }}`](https://github.com/${{github.repository}}/commit/${{github.sha}})
- Commit message: ``${{ steps.get_commit_message.outputs.message_header }}``
- embed-color: 7506394
- document-commit:
- name: Document Commit
- runs-on: ubuntu-latest
- needs: package-commit
- permissions:
- pages: write
- id-token: write
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- fetch-depth: 0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 9
- - name: Build Project
- run: |
- dotnet build
- dotnet tool update -g docfx --prerelease
- docfx docs/docfx.json
- - name: Upload GitHub Pages artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: ./docs/_site/
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
- - name: Reload Discord documentation
- run: |
- curl -X POST -H 'Authorization: Bot ${{ secrets.DISCORD_TOKEN }}' -H 'Content-Type: application/json' -d '{"content":"<@341606460720939008> reload"}' 'https://discord.com/api/v10/channels/379379415475552276/messages'
+name: Build Commit
+
+on:
+ push:
+ branches: [master]
+ paths:
+ - ".github/workflows/build-commit.yml"
+ - "DSharpPlus*/**"
+ - "tools/**"
+ - "docs/**"
+ - "*.sln"
+ - "obsolete/DSharpPlus*/**"
+ workflow_dispatch:
+
+env:
+ DOTNET_NOLOGO: 1
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
+
+jobs:
+ package-commit:
+ name: Package Commit
+ runs-on: ubuntu-latest
+ if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9
+ - name: Build Project
+ run: dotnet build
+ - name: Test Changes
+ run: dotnet test --blame-crash --blame-hang --blame-hang-timeout "30s"
+ - name: Get Nightly Version
+ id: nightly
+ run: printf "version=%0*d" 5 $(( 1195 + 691 + ${{ github.run_number }} )) >> "$GITHUB_OUTPUT"
+ - name: Package Project
+ run: |
+ # We add 1195 since it's the last build number AppVeyor used.
+ # We add 686 since it's the last build number GitHub Actions used before the workflow was renamed.
+ dotnet pack -c Release -o build -p:Nightly=${{ steps.nightly.outputs.version }}
+ dotnet nuget push "build/*" --skip-duplicate -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json
+ env:
+ DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
+ DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
+ DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
+ DISCORD_CHANNEL_TOPIC: ${{ secrets.DISCORD_CHANNEL_TOPIC }}
+ DISCORD_DOC_BOT_USER_ID: ${{ secrets.DISCORD_DOC_BOT_USER_ID }}
+ DISCORD_BOT_USAGE_CHANNEL_ID: ${{ secrets.DISCORD_BOT_USAGE_CHANNEL_ID }}
+ NUGET_URL: ${{ secrets.NUGET_URL }}
+ GITHUB_URL : ${{ github.server_url }}/${{ github.repository }}
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: DSharpPlus-Nightly-${{ steps.nightly.outputs.version }}.zip
+ path: ./build/*
+ - name: Get commit message header
+ id: get_commit_message
+ run: |
+ commit_message_header=$(git log -1 --pretty=%B | head -n 1)
+ echo "message_header=${commit_message_header}" >> $GITHUB_OUTPUT
+ - name: Discord Webhook
+ uses: tsickert/discord-webhook@v4.0.0
+ with:
+ webhook-url: ${{ secrets.BUILD_WEBHOOK }}
+ embed-title: DSharpPlus 5.0.0-nightly-${{ steps.nightly.outputs.version }}
+ embed-description: |
+ NuGet Link: [`5.0.0-nightly-${{ steps.nightly.outputs.version }}`](https://www.nuget.org/packages/DSharpPlus/5.0.0-nightly-${{ steps.nightly.outputs.version }})
+ Commit hash: [`${{ github.sha }}`](https://github.com/${{github.repository}}/commit/${{github.sha}})
+ Commit message: ``${{ steps.get_commit_message.outputs.message_header }}``
+ embed-color: 7506394
+ document-commit:
+ name: Document Commit
+ runs-on: ubuntu-latest
+ needs: package-commit
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9
+ - name: Build Project
+ run: |
+ dotnet build
+ dotnet tool update -g docfx --prerelease
+ docfx docs/docfx.json
+ - name: Upload GitHub Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./docs/_site/
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
+ - name: Reload Discord documentation
+ run: |
+ curl -X POST -H 'Authorization: Bot ${{ secrets.DISCORD_TOKEN }}' -H 'Content-Type: application/json' -d '{"content":"<@341606460720939008> reload"}' 'https://discord.com/api/v10/channels/379379415475552276/messages'
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index ca91c500e6..493f124b17 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -1,48 +1,48 @@
-name: Build PR
-
-on:
- pull_request:
- types:
- - opened
- - synchronize
- - reopened
- - ready_for_review
- paths:
- - ".github/workflows/build-pr.yml"
- - "DSharpPlus*/**"
- - "tools/**"
- - "*.sln"
-env:
- DOTNET_NOLOGO: 1
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
-
-jobs:
- build-commit:
- name: "Build PR #${{ github.event.pull_request.number }}"
- runs-on: ubuntu-latest
- if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 9
- - name: Build Project
- run: dotnet build
- - name: Test Changes
- run: dotnet test --blame-crash --blame-hang --blame-hang-timeout "30s"
- - name: Get PR Version
- id: pr
- run: printf "version=%0*d" 5 ${{ github.run_number }} >> "$GITHUB_OUTPUT"
- - name: Build and Package Project
- run: dotnet pack --include-symbols --include-source -o build -p:PR="${{ github.event.pull_request.number }}-${{ steps.pr.outputs.version }}"
- - name: Upload artifacts
- uses: actions/upload-artifact@v4
- with:
- name: DSharpPlus-PR-${{ github.event.pull_request.number }}-${{ steps.pr.outputs.version }}
+name: Build PR
+
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
+ paths:
+ - ".github/workflows/build-pr.yml"
+ - "DSharpPlus*/**"
+ - "tools/**"
+ - "*.sln"
+env:
+ DOTNET_NOLOGO: 1
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
+
+jobs:
+ build-commit:
+ name: "Build PR #${{ github.event.pull_request.number }}"
+ runs-on: ubuntu-latest
+ if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9
+ - name: Build Project
+ run: dotnet build
+ - name: Test Changes
+ run: dotnet test --blame-crash --blame-hang --blame-hang-timeout "30s"
+ - name: Get PR Version
+ id: pr
+ run: printf "version=%0*d" 5 ${{ github.run_number }} >> "$GITHUB_OUTPUT"
+ - name: Build and Package Project
+ run: dotnet pack --include-symbols --include-source -o build -p:PR="${{ github.event.pull_request.number }}-${{ steps.pr.outputs.version }}"
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: DSharpPlus-PR-${{ github.event.pull_request.number }}-${{ steps.pr.outputs.version }}
path: ./build/*
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b56a758d1c..792bbd9cf1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,92 +1,92 @@
-name: Release
-on:
- release:
- types: ["published"]
-
-env:
- DOTNET_NOLOGO: 1
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
-
-jobs:
- build-commit:
- name: Build Commit
- runs-on: ubuntu-latest
- if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 9
- - name: Build Project
- run: dotnet build
- package-commit:
- name: Package Commit
- runs-on: ubuntu-latest
- needs: build-commit
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- fetch-depth: 0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 8
- - name: Package Project
- run: |
- dotnet pack -c Release -o build
- dotnet nuget push "build/*" --skip-duplicate -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json
- LATEST_STABLE_VERSION=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') dotnet run --project ./tools/AutoUpdateChannelDescription
- env:
- DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
- DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
- DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
- DISCORD_CHANNEL_TOPIC: ${{ secrets.DISCORD_CHANNEL_TOPIC }}
- DISCORD_DOC_BOT_USER_ID: ${{ secrets.DISCORD_DOC_BOT_USER_ID }}
- DISCORD_BOT_USAGE_CHANNEL_ID: ${{ secrets.DISCORD_BOT_USAGE_CHANNEL_ID }}
- NUGET_URL: ${{ secrets.NUGET_URL }}
- GITHUB_URL : ${{ github.server_url }}/${{ github.repository }}
- - name: Upload Artifact
- uses: actions/upload-artifact@v4
- with:
- name: DSharpPlus.zip
- path: ./build/*
- document-commit:
- name: Document Commit
- runs-on: ubuntu-latest
- needs: package-commit
- permissions:
- pages: write
- id-token: write
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: recursive
- fetch-depth: 0
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: 8
- - name: Build Project
- run: |
- dotnet build
- dotnet tool update -g docfx --prerelease
- docfx docs/docfx.json
- - name: Upload GitHub Pages artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: ./docs/_site/
- - name: Deploy to GitHub Pages
- id: deployment
+name: Release
+on:
+ release:
+ types: ["published"]
+
+env:
+ DOTNET_NOLOGO: 1
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
+
+jobs:
+ build-commit:
+ name: Build Commit
+ runs-on: ubuntu-latest
+ if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9
+ - name: Build Project
+ run: dotnet build
+ package-commit:
+ name: Package Commit
+ runs-on: ubuntu-latest
+ needs: build-commit
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8
+ - name: Package Project
+ run: |
+ dotnet pack -c Release -o build
+ dotnet nuget push "build/*" --skip-duplicate -k ${{ secrets.NUGET_ORG_API_KEY }} -s https://api.nuget.org/v3/index.json
+ LATEST_STABLE_VERSION=$(git describe --abbrev=0 --tags 2>/dev/null || echo '') dotnet run --project ./tools/AutoUpdateChannelDescription
+ env:
+ DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
+ DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
+ DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }}
+ DISCORD_CHANNEL_TOPIC: ${{ secrets.DISCORD_CHANNEL_TOPIC }}
+ DISCORD_DOC_BOT_USER_ID: ${{ secrets.DISCORD_DOC_BOT_USER_ID }}
+ DISCORD_BOT_USAGE_CHANNEL_ID: ${{ secrets.DISCORD_BOT_USAGE_CHANNEL_ID }}
+ NUGET_URL: ${{ secrets.NUGET_URL }}
+ GITHUB_URL : ${{ github.server_url }}/${{ github.repository }}
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: DSharpPlus.zip
+ path: ./build/*
+ document-commit:
+ name: Document Commit
+ runs-on: ubuntu-latest
+ needs: package-commit
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8
+ - name: Build Project
+ run: |
+ dotnet build
+ dotnet tool update -g docfx --prerelease
+ docfx docs/docfx.json
+ - name: Upload GitHub Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./docs/_site/
+ - name: Deploy to GitHub Pages
+ id: deployment
uses: actions/deploy-pages@v4
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d224448a06..ecebeb4bde 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,490 +1,490 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
-
-# User-specific files
-*.rsuser
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Mono auto generated files
-mono_crash.*
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-[Ww][Ii][Nn]32/
-[Aa][Rr][Mm]/
-[Aa][Rr][Mm]64/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-[Ll]ogs/
-
-# Visual Studio 2015/2017 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUnit
-*.VisualState.xml
-TestResult.xml
-nunit-*.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# Tye
-.tye/
-
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_h.h
-*.ilk
-*.meta
-*.obj
-*.iobj
-*.pch
-*.pdb
-*.ipdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*_wpftmp.csproj
-*.log
-*.tlog
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# NuGet Symbol Packages
-*.snupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-*.appxbundle
-*.appxupload
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!?*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-*- [Bb]ackup.rdl
-*- [Bb]ackup ([0-9]).rdl
-*- [Bb]ackup ([0-9][0-9]).rdl
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio 6 auto-generated project file (contains which files were open etc.)
-*.vbp
-
-# Visual Studio 6 workspace and project file (working project files containing files to include in project)
-*.dsw
-*.dsp
-
-# Visual Studio 6 technical files
-*.ncb
-*.aps
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# CodeRush personal settings
-.cr/personal
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
-
-# Visual Studio History (VSHistory) files
-.vshistory/
-
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Backup folder for Package Reference Convert tool in Visual Studio 2017
-MigrationBackup/
-
-# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
-# VS Code files for those working on multiple tools
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-*.code-workspace
-
-# Local History for Visual Studio Code
-.history/
-
-# Windows Installer files from build outputs
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# JetBrains Rider
-*.sln.iml
-
-##
-## Visual studio for Mac
-##
-
-
-# globs
-Makefile.in
-*.userprefs
-*.usertasks
-config.make
-config.status
-aclocal.m4
-install-sh
-autom4te.cache/
-*.tar.gz
-tarballs/
-test-results/
-
-# Mac bundle stuff
-*.dmg
-*.app
-
-# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
-# General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
-# Windows thumbnail cache files
-Thumbs.db
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-# Vim temporary swap files
-*.swp
-
-# DSharpPlus changes
-.idea
-.vscode
-*.DotSettings
-*.patch
-docs/_site
-DSharpPlus.Test/config.json
-/DSharpPlus.HttpInteractions.AspNetCore/Properties/launchSettings.json
-/DSharpPlus.Http.AspNetCore/Properties/launchSettings.json
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
+
+# DSharpPlus changes
+.idea
+.vscode
+*.DotSettings
+*.patch
+docs/_site
+DSharpPlus.Test/config.json
+/DSharpPlus.HttpInteractions.AspNetCore/Properties/launchSettings.json
+/DSharpPlus.Http.AspNetCore/Properties/launchSettings.json
diff --git a/DSharpPlus.Commands/AbstractContext.cs b/DSharpPlus.Commands/AbstractContext.cs
index 7913ea7f2f..cb7835fb7a 100644
--- a/DSharpPlus.Commands/AbstractContext.cs
+++ b/DSharpPlus.Commands/AbstractContext.cs
@@ -1,20 +1,20 @@
-using System;
-using DSharpPlus.Commands.Trees;
-using DSharpPlus.Entities;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace DSharpPlus.Commands;
-
-public abstract record AbstractContext
-{
- public required DiscordUser User { get; init; }
- public required DiscordChannel Channel { get; init; }
- public required CommandsExtension Extension { get; init; }
- public required Command Command { get; init; }
- public required IServiceScope ServiceScope { internal get; init; }
-
- public DiscordGuild? Guild => this.Channel.Guild;
- public DiscordMember? Member => this.User as DiscordMember;
- public DiscordClient Client => this.Extension.Client;
- public IServiceProvider ServiceProvider => this.ServiceScope.ServiceProvider;
-}
+using System;
+using DSharpPlus.Commands.Trees;
+using DSharpPlus.Entities;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DSharpPlus.Commands;
+
+public abstract record AbstractContext
+{
+ public required DiscordUser User { get; init; }
+ public required DiscordChannel Channel { get; init; }
+ public required CommandsExtension Extension { get; init; }
+ public required Command Command { get; init; }
+ public required IServiceScope ServiceScope { internal get; init; }
+
+ public DiscordGuild? Guild => this.Channel.Guild;
+ public DiscordMember? Member => this.User as DiscordMember;
+ public DiscordClient Client => this.Extension.Client;
+ public IServiceProvider ServiceProvider => this.ServiceScope.ServiceProvider;
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/ChannelTypesAttribute.cs b/DSharpPlus.Commands/ArgumentModifiers/ChannelTypesAttribute.cs
index b1e14a8fee..91a3e9e2b3 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/ChannelTypesAttribute.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/ChannelTypesAttribute.cs
@@ -1,18 +1,18 @@
-using System;
-using DSharpPlus.Commands.ContextChecks.ParameterChecks;
-using DSharpPlus.Entities;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-///
-/// Specifies what channel types the parameter supports.
-///
-/// The required types of channels.
-[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
-public sealed class ChannelTypesAttribute(params DiscordChannelType[] channelTypes) : ParameterCheckAttribute
-{
- ///
- /// Gets the channel types allowed for this parameter.
- ///
- public DiscordChannelType[] ChannelTypes { get; init; } = channelTypes;
-}
+using System;
+using DSharpPlus.Commands.ContextChecks.ParameterChecks;
+using DSharpPlus.Entities;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+///
+/// Specifies what channel types the parameter supports.
+///
+/// The required types of channels.
+[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
+public sealed class ChannelTypesAttribute(params DiscordChannelType[] channelTypes) : ParameterCheckAttribute
+{
+ ///
+ /// Gets the channel types allowed for this parameter.
+ ///
+ public DiscordChannelType[] ChannelTypes { get; init; } = channelTypes;
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/FromCode/CodeType.cs b/DSharpPlus.Commands/ArgumentModifiers/FromCode/CodeType.cs
index 0932f5f8b0..e8d33790c3 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/FromCode/CodeType.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/FromCode/CodeType.cs
@@ -1,25 +1,25 @@
-using System;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-///
-/// The types of code-formatted text to accept.
-///
-[Flags]
-public enum CodeType
-{
- ///
- /// Accept inline code blocks - codeblocks that will not contain any newlines.
- ///
- Inline = 1 << 0,
-
- ///
- /// Accept codeblocks - codeblocks that will contain possibly multiple newlines.
- ///
- Codeblock = 1 << 1,
-
- ///
- /// Accept any type of code block.
- ///
- All = Inline | Codeblock
-}
+using System;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+///
+/// The types of code-formatted text to accept.
+///
+[Flags]
+public enum CodeType
+{
+ ///
+ /// Accept inline code blocks - codeblocks that will not contain any newlines.
+ ///
+ Inline = 1 << 0,
+
+ ///
+ /// Accept codeblocks - codeblocks that will contain possibly multiple newlines.
+ ///
+ Codeblock = 1 << 1,
+
+ ///
+ /// Accept any type of code block.
+ ///
+ All = Inline | Codeblock
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/FromCode/FromCodeAttribute.cs b/DSharpPlus.Commands/ArgumentModifiers/FromCode/FromCodeAttribute.cs
index a330a13ca4..fefaab1c40 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/FromCode/FromCodeAttribute.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/FromCode/FromCodeAttribute.cs
@@ -1,21 +1,21 @@
-using System;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-///
-/// Removes the need to manually parse code blocks from a string.
-///
-[AttributeUsage(AttributeTargets.Parameter)]
-public sealed partial class FromCodeAttribute : Attribute
-{
- ///
- /// The type of code block to accept.
- ///
- public CodeType CodeType { get; init; }
-
- ///
- /// Creates a new with the specified .
- ///
- /// The type of code block to accept.
- public FromCodeAttribute(CodeType codeType = CodeType.All) => this.CodeType = codeType;
-}
+using System;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+///
+/// Removes the need to manually parse code blocks from a string.
+///
+[AttributeUsage(AttributeTargets.Parameter)]
+public sealed partial class FromCodeAttribute : Attribute
+{
+ ///
+ /// The type of code block to accept.
+ ///
+ public CodeType CodeType { get; init; }
+
+ ///
+ /// Creates a new with the specified .
+ ///
+ /// The type of code block to accept.
+ public FromCodeAttribute(CodeType codeType = CodeType.All) => this.CodeType = codeType;
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/MinMaxLengthAttribute.cs b/DSharpPlus.Commands/ArgumentModifiers/MinMaxLengthAttribute.cs
index df6a4bcd09..53e9eb6826 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/MinMaxLengthAttribute.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/MinMaxLengthAttribute.cs
@@ -1,42 +1,42 @@
-using System;
-using DSharpPlus.Commands.ContextChecks.ParameterChecks;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-///
-/// Determines the minimum and maximum length that a parameter can accept.
-///
-[AttributeUsage(AttributeTargets.Parameter)]
-public sealed class MinMaxLengthAttribute : ParameterCheckAttribute
-{
- // on text commands, we interpret 6000 as unlimited - it exceeds the message limit anyway
- private const int MinLengthMinimum = 0;
- private const int MinLengthMaximum = 6000;
- private const int MaxLengthMinimum = 1;
- private const int MaxLengthMaximum = 6000;
-
- ///
- /// The minimum length that this parameter can accept.
- ///
- public int MinLength { get; private init; }
-
- ///
- /// The maximum length that this parameter can accept.
- ///
- public int MaxLength { get; private init; }
-
- ///
- /// Determines the minimum and maximum length that a parameter can accept.
- ///
- public MinMaxLengthAttribute(int minLength = MinLengthMinimum, int maxLength = MaxLengthMaximum)
- {
- ArgumentOutOfRangeException.ThrowIfLessThan(minLength, MinLengthMinimum, nameof(minLength));
- ArgumentOutOfRangeException.ThrowIfGreaterThan(minLength, MinLengthMaximum, nameof(minLength));
- ArgumentOutOfRangeException.ThrowIfLessThan(maxLength, MaxLengthMinimum, nameof(maxLength));
- ArgumentOutOfRangeException.ThrowIfGreaterThan(maxLength, MaxLengthMaximum, nameof(maxLength));
- ArgumentOutOfRangeException.ThrowIfGreaterThan(minLength, maxLength, nameof(minLength));
-
- this.MinLength = minLength;
- this.MaxLength = maxLength;
- }
-}
+using System;
+using DSharpPlus.Commands.ContextChecks.ParameterChecks;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+///
+/// Determines the minimum and maximum length that a parameter can accept.
+///
+[AttributeUsage(AttributeTargets.Parameter)]
+public sealed class MinMaxLengthAttribute : ParameterCheckAttribute
+{
+ // on text commands, we interpret 6000 as unlimited - it exceeds the message limit anyway
+ private const int MinLengthMinimum = 0;
+ private const int MinLengthMaximum = 6000;
+ private const int MaxLengthMinimum = 1;
+ private const int MaxLengthMaximum = 6000;
+
+ ///
+ /// The minimum length that this parameter can accept.
+ ///
+ public int MinLength { get; private init; }
+
+ ///
+ /// The maximum length that this parameter can accept.
+ ///
+ public int MaxLength { get; private init; }
+
+ ///
+ /// Determines the minimum and maximum length that a parameter can accept.
+ ///
+ public MinMaxLengthAttribute(int minLength = MinLengthMinimum, int maxLength = MaxLengthMaximum)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(minLength, MinLengthMinimum, nameof(minLength));
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(minLength, MinLengthMaximum, nameof(minLength));
+ ArgumentOutOfRangeException.ThrowIfLessThan(maxLength, MaxLengthMinimum, nameof(maxLength));
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(maxLength, MaxLengthMaximum, nameof(maxLength));
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(minLength, maxLength, nameof(minLength));
+
+ this.MinLength = minLength;
+ this.MaxLength = maxLength;
+ }
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/MinMaxValueAttribute.cs b/DSharpPlus.Commands/ArgumentModifiers/MinMaxValueAttribute.cs
index 5876555122..450bed7f6e 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/MinMaxValueAttribute.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/MinMaxValueAttribute.cs
@@ -1,60 +1,60 @@
-using System;
-using DSharpPlus.Commands.ContextChecks.ParameterChecks;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-///
-/// Determines the minimum and maximum values that a parameter can accept.
-///
-[AttributeUsage(AttributeTargets.Parameter)]
-public sealed class MinMaxValueAttribute : ParameterCheckAttribute
-{
- ///
- /// The minimum value that this parameter can accept.
- ///
- public object? MinValue { get; private init; }
-
- ///
- /// The maximum value that this parameter can accept.
- ///
- public object? MaxValue { get; private init; }
-
- ///
- /// Determines the minimum and maximum values that a parameter can accept.
- ///
- public MinMaxValueAttribute(object? minValue = null, object? maxValue = null)
- {
- this.MinValue = minValue;
- this.MaxValue = maxValue;
-
- if (minValue is not null && maxValue is not null && minValue.GetType() != maxValue.GetType())
- {
- throw new ArgumentException("The minimum and maximum values must be of the same type.");
- }
-
- if (minValue is null || maxValue is null)
- {
- return;
- }
-
- bool correctlyOrdered = minValue switch
- {
- byte => (byte)minValue <= (byte)maxValue,
- sbyte => (sbyte)minValue <= (sbyte)maxValue,
- short => (short)minValue <= (short)maxValue,
- ushort => (ushort)minValue <= (ushort)maxValue,
- int => (int)minValue <= (int)maxValue,
- uint => (uint)minValue <= (uint)maxValue,
- long => (long)minValue <= (long)maxValue,
- ulong => (ulong)minValue <= (ulong)maxValue,
- float => (float)minValue <= (float)maxValue,
- double => (double)minValue <= (double)maxValue,
- _ => throw new ArgumentException("The type of the minimum/maximum values is not supported."),
- };
-
- if (!correctlyOrdered)
- {
- throw new ArgumentException("The minimum value cannot be greater than the maximum value.");
- }
- }
-}
+using System;
+using DSharpPlus.Commands.ContextChecks.ParameterChecks;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+///
+/// Determines the minimum and maximum values that a parameter can accept.
+///
+[AttributeUsage(AttributeTargets.Parameter)]
+public sealed class MinMaxValueAttribute : ParameterCheckAttribute
+{
+ ///
+ /// The minimum value that this parameter can accept.
+ ///
+ public object? MinValue { get; private init; }
+
+ ///
+ /// The maximum value that this parameter can accept.
+ ///
+ public object? MaxValue { get; private init; }
+
+ ///
+ /// Determines the minimum and maximum values that a parameter can accept.
+ ///
+ public MinMaxValueAttribute(object? minValue = null, object? maxValue = null)
+ {
+ this.MinValue = minValue;
+ this.MaxValue = maxValue;
+
+ if (minValue is not null && maxValue is not null && minValue.GetType() != maxValue.GetType())
+ {
+ throw new ArgumentException("The minimum and maximum values must be of the same type.");
+ }
+
+ if (minValue is null || maxValue is null)
+ {
+ return;
+ }
+
+ bool correctlyOrdered = minValue switch
+ {
+ byte => (byte)minValue <= (byte)maxValue,
+ sbyte => (sbyte)minValue <= (sbyte)maxValue,
+ short => (short)minValue <= (short)maxValue,
+ ushort => (ushort)minValue <= (ushort)maxValue,
+ int => (int)minValue <= (int)maxValue,
+ uint => (uint)minValue <= (uint)maxValue,
+ long => (long)minValue <= (long)maxValue,
+ ulong => (ulong)minValue <= (ulong)maxValue,
+ float => (float)minValue <= (float)maxValue,
+ double => (double)minValue <= (double)maxValue,
+ _ => throw new ArgumentException("The type of the minimum/maximum values is not supported."),
+ };
+
+ if (!correctlyOrdered)
+ {
+ throw new ArgumentException("The minimum value cannot be greater than the maximum value.");
+ }
+ }
+}
diff --git a/DSharpPlus.Commands/ArgumentModifiers/RemainingTextAttribute.cs b/DSharpPlus.Commands/ArgumentModifiers/RemainingTextAttribute.cs
index 5c901ce30e..4b8ce68fe3 100644
--- a/DSharpPlus.Commands/ArgumentModifiers/RemainingTextAttribute.cs
+++ b/DSharpPlus.Commands/ArgumentModifiers/RemainingTextAttribute.cs
@@ -1,6 +1,6 @@
-using System;
-
-namespace DSharpPlus.Commands.ArgumentModifiers;
-
-[AttributeUsage(AttributeTargets.Parameter)]
-public sealed class RemainingTextAttribute : Attribute;
+using System;
+
+namespace DSharpPlus.Commands.ArgumentModifiers;
+
+[AttributeUsage(AttributeTargets.Parameter)]
+public sealed class RemainingTextAttribute : Attribute;
diff --git a/DSharpPlus.Commands/CommandAttribute.cs b/DSharpPlus.Commands/CommandAttribute.cs
index a51ba0a563..f4bc617d92 100644
--- a/DSharpPlus.Commands/CommandAttribute.cs
+++ b/DSharpPlus.Commands/CommandAttribute.cs
@@ -1,30 +1,30 @@
-using System;
-
-namespace DSharpPlus.Commands;
-
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate)]
-public sealed class CommandAttribute : Attribute
-{
- ///
- /// The name of the command.
- ///
- public string Name { get; init; }
-
- ///
- /// Creates a new instance of the class.
- ///
- /// The name of the command.
- public CommandAttribute(string name)
- {
- if (string.IsNullOrWhiteSpace(name))
- {
- throw new ArgumentNullException(nameof(name), "The name of the command cannot be null or whitespace.");
- }
- else if (name.Length is < 1 or > 32)
- {
- throw new ArgumentOutOfRangeException(nameof(name), "The name of the command must be between 1 and 32 characters.");
- }
-
- this.Name = name;
- }
-}
+using System;
+
+namespace DSharpPlus.Commands;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate)]
+public sealed class CommandAttribute : Attribute
+{
+ ///
+ /// The name of the command.
+ ///
+ public string Name { get; init; }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The name of the command.
+ public CommandAttribute(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException(nameof(name), "The name of the command cannot be null or whitespace.");
+ }
+ else if (name.Length is < 1 or > 32)
+ {
+ throw new ArgumentOutOfRangeException(nameof(name), "The name of the command must be between 1 and 32 characters.");
+ }
+
+ this.Name = name;
+ }
+}
diff --git a/DSharpPlus.Commands/CommandContext.cs b/DSharpPlus.Commands/CommandContext.cs
index 249e728a96..256f11b2e7 100644
--- a/DSharpPlus.Commands/CommandContext.cs
+++ b/DSharpPlus.Commands/CommandContext.cs
@@ -1,138 +1,138 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using DSharpPlus.Commands.Trees;
-using DSharpPlus.Entities;
-
-namespace DSharpPlus.Commands;
-
-///
-/// Represents a base context for application command contexts.
-///
-public abstract record CommandContext : AbstractContext
-{
- ///
- /// The command arguments.
- ///
- public required IReadOnlyDictionary Arguments { get; init; }
-
- ///
- /// The followup messages sent from this interaction.
- ///
- public IReadOnlyDictionary FollowupMessages => this.followupMessages;
- protected Dictionary followupMessages = [];
-
- ///
- public virtual ValueTask RespondAsync(string content) => RespondAsync(new DiscordMessageBuilder().WithContent(content));
-
- ///
- public virtual ValueTask RespondAsync(DiscordEmbed embed) => RespondAsync(new DiscordMessageBuilder().AddEmbed(embed));
-
- ///
- /// Creates a response to this interaction.
- /// You must create a response within 3 seconds of this interaction being executed; if the command has the potential to take more than 3 seconds, use at the start, and edit the response later.
- ///
- /// Content to send in the response.
- /// Embed to send in the response.
- public virtual ValueTask RespondAsync(string content, DiscordEmbed embed) => RespondAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
-
- ///
- /// The message builder.
- public abstract ValueTask RespondAsync(IDiscordMessageBuilder builder);
-
- ///
- public virtual ValueTask EditResponseAsync(string content) => EditResponseAsync(new DiscordMessageBuilder().WithContent(content));
-
- ///
- public virtual ValueTask EditResponseAsync(DiscordEmbed embed) => EditResponseAsync(new DiscordMessageBuilder().AddEmbed(embed));
-
- ///
- /// Edits the response.
- ///
- /// Content to send in the response.
- /// Embed to send in the response.
- public virtual ValueTask EditResponseAsync(string content, DiscordEmbed embed)
- => EditResponseAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
-
- ///
- /// The message builder.
- public abstract ValueTask EditResponseAsync(IDiscordMessageBuilder builder);
-
- ///
- /// Gets the sent response.
- ///
- /// The sent response.
- public abstract ValueTask GetResponseAsync();
-
- ///
- /// Creates a deferred response to this interaction.
- ///
- public abstract ValueTask DeferResponseAsync();
-
- ///
- /// Deletes the sent response.
- ///
- public abstract ValueTask DeleteResponseAsync();
-
- ///
- public virtual ValueTask FollowupAsync(string content) => FollowupAsync(new DiscordMessageBuilder().WithContent(content));
-
- ///
- public virtual ValueTask FollowupAsync(DiscordEmbed embed) => FollowupAsync(new DiscordMessageBuilder().AddEmbed(embed));
-
- ///
- /// Creates a followup message to the interaction.
- ///
- /// Content to send in the followup message.
- /// Embed to send in the followup message.
- /// The created message.
- public virtual ValueTask FollowupAsync(string content, DiscordEmbed embed)
- => FollowupAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
-
- ///
- /// The followup message to be sent.
- public abstract ValueTask FollowupAsync(IDiscordMessageBuilder builder);
-
- ///
- public virtual ValueTask EditFollowupAsync(ulong messageId, string content)
- => EditFollowupAsync(messageId, new DiscordMessageBuilder().WithContent(content));
-
- ///
- public virtual ValueTask EditFollowupAsync(ulong messageId, DiscordEmbed embed)
- => EditFollowupAsync(messageId, new DiscordMessageBuilder().AddEmbed(embed));
-
- ///
- /// Edits a followup message.
- ///
- /// The id of the followup message to edit.
- /// Content to send in the followup message.
- /// Embed to send in the followup message.
- /// The edited message.
- public virtual ValueTask EditFollowupAsync(ulong messageId, string content, DiscordEmbed embed)
- => EditFollowupAsync(messageId, new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
-
- ///
- /// The id of the followup message to edit.
- /// The message builder.
- public abstract ValueTask EditFollowupAsync(ulong messageId, IDiscordMessageBuilder builder);
-
- ///
- /// Gets a sent followup message from this interaction.
- ///
- /// The id of the followup message to edit.
- /// Whether to ignore the cache and fetch the message from Discord.
- /// The message.
- public abstract ValueTask GetFollowupAsync(ulong messageId, bool ignoreCache = false);
-
- ///
- /// Deletes a followup message sent from this interaction.
- ///
- /// The id of the followup message to delete.
- public abstract ValueTask DeleteFollowupAsync(ulong messageId);
-
- ///
- /// Cast this context to a different one.
- ///
- /// The type to cast to.
- /// This context as T.
- public T As() where T : CommandContext => (T)this;
-}
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using DSharpPlus.Commands.Trees;
+using DSharpPlus.Entities;
+
+namespace DSharpPlus.Commands;
+
+///
+/// Represents a base context for application command contexts.
+///
+public abstract record CommandContext : AbstractContext
+{
+ ///
+ /// The command arguments.
+ ///
+ public required IReadOnlyDictionary Arguments { get; init; }
+
+ ///
+ /// The followup messages sent from this interaction.
+ ///
+ public IReadOnlyDictionary FollowupMessages => this.followupMessages;
+ protected Dictionary followupMessages = [];
+
+ ///
+ public virtual ValueTask RespondAsync(string content) => RespondAsync(new DiscordMessageBuilder().WithContent(content));
+
+ ///
+ public virtual ValueTask RespondAsync(DiscordEmbed embed) => RespondAsync(new DiscordMessageBuilder().AddEmbed(embed));
+
+ ///
+ /// Creates a response to this interaction.
+ /// You must create a response within 3 seconds of this interaction being executed; if the command has the potential to take more than 3 seconds, use at the start, and edit the response later.
+ ///
+ /// Content to send in the response.
+ /// Embed to send in the response.
+ public virtual ValueTask RespondAsync(string content, DiscordEmbed embed) => RespondAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
+
+ ///
+ /// The message builder.
+ public abstract ValueTask RespondAsync(IDiscordMessageBuilder builder);
+
+ ///
+ public virtual ValueTask EditResponseAsync(string content) => EditResponseAsync(new DiscordMessageBuilder().WithContent(content));
+
+ ///
+ public virtual ValueTask EditResponseAsync(DiscordEmbed embed) => EditResponseAsync(new DiscordMessageBuilder().AddEmbed(embed));
+
+ ///
+ /// Edits the response.
+ ///
+ /// Content to send in the response.
+ /// Embed to send in the response.
+ public virtual ValueTask EditResponseAsync(string content, DiscordEmbed embed)
+ => EditResponseAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
+
+ ///
+ /// The message builder.
+ public abstract ValueTask EditResponseAsync(IDiscordMessageBuilder builder);
+
+ ///
+ /// Gets the sent response.
+ ///
+ /// The sent response.
+ public abstract ValueTask GetResponseAsync();
+
+ ///
+ /// Creates a deferred response to this interaction.
+ ///
+ public abstract ValueTask DeferResponseAsync();
+
+ ///
+ /// Deletes the sent response.
+ ///
+ public abstract ValueTask DeleteResponseAsync();
+
+ ///
+ public virtual ValueTask FollowupAsync(string content) => FollowupAsync(new DiscordMessageBuilder().WithContent(content));
+
+ ///
+ public virtual ValueTask FollowupAsync(DiscordEmbed embed) => FollowupAsync(new DiscordMessageBuilder().AddEmbed(embed));
+
+ ///
+ /// Creates a followup message to the interaction.
+ ///
+ /// Content to send in the followup message.
+ /// Embed to send in the followup message.
+ /// The created message.
+ public virtual ValueTask FollowupAsync(string content, DiscordEmbed embed)
+ => FollowupAsync(new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
+
+ ///
+ /// The followup message to be sent.
+ public abstract ValueTask FollowupAsync(IDiscordMessageBuilder builder);
+
+ ///
+ public virtual ValueTask EditFollowupAsync(ulong messageId, string content)
+ => EditFollowupAsync(messageId, new DiscordMessageBuilder().WithContent(content));
+
+ ///
+ public virtual ValueTask EditFollowupAsync(ulong messageId, DiscordEmbed embed)
+ => EditFollowupAsync(messageId, new DiscordMessageBuilder().AddEmbed(embed));
+
+ ///
+ /// Edits a followup message.
+ ///
+ /// The id of the followup message to edit.
+ /// Content to send in the followup message.
+ /// Embed to send in the followup message.
+ /// The edited message.
+ public virtual ValueTask EditFollowupAsync(ulong messageId, string content, DiscordEmbed embed)
+ => EditFollowupAsync(messageId, new DiscordMessageBuilder().WithContent(content).AddEmbed(embed));
+
+ ///
+ /// The id of the followup message to edit.
+ /// The message builder.
+ public abstract ValueTask EditFollowupAsync(ulong messageId, IDiscordMessageBuilder builder);
+
+ ///
+ /// Gets a sent followup message from this interaction.
+ ///
+ /// The id of the followup message to edit.
+ /// Whether to ignore the cache and fetch the message from Discord.
+ /// The message.
+ public abstract ValueTask GetFollowupAsync(ulong messageId, bool ignoreCache = false);
+
+ ///
+ /// Deletes a followup message sent from this interaction.
+ ///
+ /// The id of the followup message to delete.
+ public abstract ValueTask DeleteFollowupAsync(ulong messageId);
+
+ ///
+ /// Cast this context to a different one.
+ ///
+ /// The type to cast to.
+ /// This context as T.
+ public T As() where T : CommandContext => (T)this;
+}
diff --git a/DSharpPlus.Commands/CommandsConfiguration.cs b/DSharpPlus.Commands/CommandsConfiguration.cs
index c019abb573..62b1867fdf 100644
--- a/DSharpPlus.Commands/CommandsConfiguration.cs
+++ b/DSharpPlus.Commands/CommandsConfiguration.cs
@@ -1,34 +1,34 @@
-namespace DSharpPlus.Commands;
-
-///
-/// The configuration copied to an instance of .
-///
-public sealed record CommandsConfiguration
-{
- ///
- /// The guild id to use for debugging. Leave as 0 to disable.
- ///
- public ulong DebugGuildId { get; set; }
-
- ///
- /// Whether to enable the default command error handler.
- ///
- public bool UseDefaultCommandErrorHandler { get; set; } = true;
-
- ///
- /// Whether to register default command processors when they're not found in the processor list.
- ///
- ///
- /// You may still provide your own custom processors via ,
- /// as this configuration option will only add the default processors if they're not found in the list.
- ///
- public bool RegisterDefaultCommandProcessors { get; set; } = true;
-
- ///
- /// The command executor to use for command execution.
- ///
- ///
- /// The command executor is responsible for executing context checks, making full use of the dependency injection system, executing the command method itself, and handling errors.
- ///
- public ICommandExecutor CommandExecutor { get; set; } = new DefaultCommandExecutor();
-}
+namespace DSharpPlus.Commands;
+
+///
+/// The configuration copied to an instance of .
+///
+public sealed record CommandsConfiguration
+{
+ ///
+ /// The guild id to use for debugging. Leave as 0 to disable.
+ ///
+ public ulong DebugGuildId { get; set; }
+
+ ///
+ /// Whether to enable the default command error handler.
+ ///
+ public bool UseDefaultCommandErrorHandler { get; set; } = true;
+
+ ///
+ /// Whether to register default command processors when they're not found in the processor list.
+ ///
+ ///
+ /// You may still provide your own custom processors via ,
+ /// as this configuration option will only add the default processors if they're not found in the list.
+ ///
+ public bool RegisterDefaultCommandProcessors { get; set; } = true;
+
+ ///
+ /// The command executor to use for command execution.
+ ///
+ ///
+ /// The command executor is responsible for executing context checks, making full use of the dependency injection system, executing the command method itself, and handling errors.
+ ///
+ public ICommandExecutor CommandExecutor { get; set; } = new DefaultCommandExecutor();
+}
diff --git a/DSharpPlus.Commands/CommandsExtension.cs b/DSharpPlus.Commands/CommandsExtension.cs
index e4838a0e76..867b6bdc67 100644
--- a/DSharpPlus.Commands/CommandsExtension.cs
+++ b/DSharpPlus.Commands/CommandsExtension.cs
@@ -1,593 +1,593 @@
-using System;
-using System.Collections.Frozen;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
-
-using DSharpPlus.AsyncEvents;
-using DSharpPlus.Commands.ContextChecks;
-using DSharpPlus.Commands.ContextChecks.ParameterChecks;
-using DSharpPlus.Commands.EventArgs;
-using DSharpPlus.Commands.Exceptions;
-using DSharpPlus.Commands.Processors;
-using DSharpPlus.Commands.Processors.MessageCommands;
-using DSharpPlus.Commands.Processors.SlashCommands;
-using DSharpPlus.Commands.Processors.TextCommands;
-using DSharpPlus.Commands.Processors.TextCommands.ContextChecks;
-using DSharpPlus.Commands.Processors.UserCommands;
-using DSharpPlus.Commands.Trees;
-using DSharpPlus.Commands.Trees.Metadata;
-using DSharpPlus.Entities;
-using DSharpPlus.Exceptions;
-
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-using CheckFunc = System.Func
-<
- object,
- DSharpPlus.Commands.ContextChecks.ContextCheckAttribute,
- DSharpPlus.Commands.CommandContext,
- System.Threading.Tasks.ValueTask
->;
-
-using ParameterCheckFunc = System.Func
-<
- object,
- DSharpPlus.Commands.ContextChecks.ParameterChecks.ParameterCheckAttribute,
- DSharpPlus.Commands.ContextChecks.ParameterChecks.ParameterCheckInfo,
- DSharpPlus.Commands.CommandContext,
- System.Threading.Tasks.ValueTask
->;
-
-namespace DSharpPlus.Commands;
-
-///
-/// An all in one extension for managing commands.
-///
-public sealed class CommandsExtension
-{
- public DiscordClient Client { get; private set; }
-
- ///
- public IServiceProvider ServiceProvider { get; private set; }
-
- ///
- public ulong DebugGuildId { get; init; }
-
- ///
- public bool UseDefaultCommandErrorHandler { get; init; }
-
- ///
- public bool RegisterDefaultCommandProcessors { get; init; }
-
- public ICommandExecutor CommandExecutor { get; init; }
-
- ///
- /// The registered commands that the users can execute.
- ///
- public IReadOnlyDictionary Commands { get; private set; } = new Dictionary();
- private readonly List commandBuilders = [];
-
- ///
- /// All registered command processors.
- ///
- public IReadOnlyDictionary Processors => this.processors;
- private readonly Dictionary processors = [];
-
- public IReadOnlyList Checks => this.checks;
- private readonly List checks = [];
-
- public IReadOnlyList ParameterChecks => this.parameterChecks;
- private readonly List parameterChecks = [];
-
- ///
- /// Executed everytime a command is finished executing.
- ///
- public event AsyncEventHandler CommandExecuted
- {
- add => this.commandExecuted.Register(value);
- remove => this.commandExecuted.Unregister(value);
- }
-
- internal AsyncEvent commandExecuted;
-
- ///
- /// Executed everytime a command has errored.
- ///
- public event AsyncEventHandler CommandErrored
- {
- add => this.commandErrored.Register(value);
- remove => this.commandErrored.Unregister(value);
- }
-
- internal AsyncEvent commandErrored;
-
- ///
- /// Executed before commands are finalized into a read-only state.
- ///
- ///
- /// Apply any mass-mutations to the commands or command parameters here.
- ///
- public event AsyncEventHandler ConfiguringCommands
- {
- add => this.configuringCommands.Register(value);
- remove => this.configuringCommands.Unregister(value);
- }
-
- private AsyncEvent configuringCommands;
-
- ///
- /// Used to log messages from this extension.
- ///
- private ILogger logger;
-
- ///
- /// Creates a new instance of the class.
- ///
- /// The configuration to use.
- internal CommandsExtension(CommandsConfiguration configuration)
- {
- ArgumentNullException.ThrowIfNull(configuration);
-
- this.DebugGuildId = configuration.DebugGuildId;
- this.UseDefaultCommandErrorHandler = configuration.UseDefaultCommandErrorHandler;
- this.RegisterDefaultCommandProcessors = configuration.RegisterDefaultCommandProcessors;
- this.CommandExecutor = configuration.CommandExecutor;
- }
-
- ///
- /// Sets up the extension to use the specified .
- ///
- /// The client to register our event handlers too.
- public void Setup(DiscordClient client)
- {
- if (client is null)
- {
- throw new ArgumentNullException(nameof(client));
- }
- else if (this.Client is not null)
- {
- throw new InvalidOperationException("Commands Extension is already initialized.");
- }
-
- this.Client = client;
- this.ServiceProvider = client.ServiceProvider;
- this.logger = client.ServiceProvider.GetRequiredService>();
-
- DefaultClientErrorHandler errorHandler = new(client.Logger);
- this.commandErrored = new(errorHandler);
- this.commandExecuted = new(errorHandler);
- this.configuringCommands = new(errorHandler);
-
- // TODO: Move this to the IEventHandler system so the Commands namespace
- // will have zero awareness of built-in command processors.
- this.configuringCommands.Register(SlashCommandProcessor.ConfigureCommands);
- if (this.UseDefaultCommandErrorHandler)
- {
- this.CommandErrored += DefaultCommandErrorHandlerAsync;
- }
-
- AddCheck();
- AddCheck();
- AddCheck();
- AddCheck();
- AddCheck();
- AddCheck();
-
- AddParameterCheck();
- AddParameterCheck();
- AddParameterCheck();
- AddParameterCheck();
- }
-
- public void AddCommand(CommandBuilder command) => this.commandBuilders.Add(command);
- public void AddCommand(Delegate commandDelegate, params ulong[] guildIds) => this.commandBuilders.Add(CommandBuilder.From(commandDelegate, guildIds));
- public void AddCommand(Delegate commandDelegate) => this.commandBuilders.Add(CommandBuilder.From(commandDelegate));
- public void AddCommand(Type type, params ulong[] guildIds) => this.commandBuilders.Add(CommandBuilder.From(type, guildIds));
- public void AddCommand(Type type) => this.commandBuilders.Add(CommandBuilder.From(type));
-
- // !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null
- // This is done to prevent nested classes from being added as commands, while still allowing non-command classes containing commands to be added.
- // See https://github.com/DSharpPlus/DSharpPlus/pull/2273#discussion_r2009114568 for more information.
- public void AddCommands(Assembly assembly, params ulong[] guildIds) => AddCommands(assembly.GetTypes().Where(type =>
- !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null), guildIds);
-
- public void AddCommands(Assembly assembly) => AddCommands(assembly.GetTypes().Where(type =>
- !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null));
-
- public void AddCommands(IEnumerable commands) => this.commandBuilders.AddRange(commands);
- public void AddCommands(IEnumerable types) => AddCommands(types, []);
- public void AddCommands(params CommandBuilder[] commands) => this.commandBuilders.AddRange(commands);
- public void AddCommands(Type type, params ulong[] guildIds) => AddCommands([type], guildIds);
- public void AddCommands(Type type) => AddCommands([type]);
- public void AddCommands() => AddCommands([typeof(T)]);
- public void AddCommands(params ulong[] guildIds) => AddCommands([typeof(T)], guildIds);
- public void AddCommands(IEnumerable types, params ulong[] guildIds)
- {
- foreach (Type type in types)
- {
- if (type.GetCustomAttribute() is not null)
- {
- this.Client.Logger.LogDebug("Adding command from type {Type}", type.FullName ?? type.Name);
- this.commandBuilders.Add(CommandBuilder.From(type, guildIds));
- continue;
- }
-
- foreach (MethodInfo method in type.GetMethods())
- {
- if (method.GetCustomAttribute() is not null)
- {
- this.Client.Logger.LogDebug("Adding command from type {Type}", type.FullName ?? type.Name);
- this.commandBuilders.Add(CommandBuilder.From(method, guildIds: guildIds));
- }
- }
- }
- }
-
- ///
- /// Gets a list of commands filtered for a specific command processor
- ///
- /// Processor which is calling this method
- /// Returns a list of valid commands. This list can be empty if no commands are valid for this processor type
- public IReadOnlyList GetCommandsForProcessor(ICommandProcessor processor)
- {
- // Those processors use a different attribute to filter and filter themself
- if (processor is MessageCommandProcessor or UserCommandProcessor)
- {
- return this.Commands.Values.ToList();
- }
-
- Type contextType = processor.ContextType;
- Type processorType = processor.GetType();
- List commands = new(this.Commands.Values.Count());
- foreach (Command command in this.Commands.Values)
- {
- Command? filteredCommand = FilterCommand(command, processorType, contextType);
- if (filteredCommand is not null)
- {
- commands.Add(filteredCommand);
- }
- }
-
- return commands;
- }
-
- private Command? FilterCommand(Command command, Type processorType, Type contextType)
- {
- AllowedProcessorsAttribute? allowedProcessorsAttribute = command.Attributes.OfType().FirstOrDefault();
- if (allowedProcessorsAttribute is not null && !allowedProcessorsAttribute.Processors.Contains(processorType))
- {
- return null;
- }
- else if (command.Method is not null)
- {
- Type methodContextType = command.Method.GetParameters().First().ParameterType;
- if (!methodContextType.IsAssignableTo(contextType) && methodContextType != typeof(CommandContext))
- {
- return null;
- }
- }
-
- List subcommands = new(command.Subcommands.Count);
- foreach (Command subcommand in command.Subcommands)
- {
- Command? filteredSubcommand = FilterCommand(subcommand, processorType, contextType);
- if (filteredSubcommand is not null)
- {
- subcommands.Add(filteredSubcommand);
- }
- }
-
- return command with
- {
- Subcommands = subcommands,
- };
- }
-
- public void AddProcessor(ICommandProcessor processor) => this.processors.Add(processor.GetType(), processor);
- public void AddProcessor() where TProcessor : ICommandProcessor, new() => AddProcessor(new TProcessor());
- public void AddProcessors(params ICommandProcessor[] processors) => AddProcessors((IEnumerable)processors);
- public void AddProcessors(IEnumerable processors)
- {
- foreach (ICommandProcessor processor in processors)
- {
- AddProcessor(processor);
- }
- }
-
- public TProcessor GetProcessor() where TProcessor : ICommandProcessor => (TProcessor)this.processors[typeof(TProcessor)];
- public bool TryGetProcessor([NotNullWhen(true)] out TProcessor? processor) where TProcessor : ICommandProcessor
- {
- if (this.processors.TryGetValue(typeof(TProcessor), out ICommandProcessor? baseProcessor))
- {
- processor = (TProcessor)baseProcessor;
- return true;
- }
-
- processor = default;
- return false;
- }
-
- ///
- /// Adds all public checks from the provided assembly to the extension.
- ///
- public void AddChecks(Assembly assembly)
- {
- foreach (Type t in assembly.GetTypes())
- {
- if (t.GetInterface("DSharpPlus.Commands.ContextChecks.IContextCheck`1") is not null)
- {
- AddCheck(t);
- }
- }
- }
-
- ///
- /// Adds a new check to the extension.
- ///
- public void AddCheck() where T : IContextCheck => AddCheck(typeof(T));
-
- ///
- /// Adds a new check to the extension.
- ///
- public void AddCheck(Type checkType)
- {
- // get all implemented check interfaces, we can pretty easily handle having multiple checks in one type
- foreach (Type t in checkType.GetInterfaces())
- {
- if (t.Namespace != "DSharpPlus.Commands.ContextChecks" || t.Name != "IContextCheck`1")
- {
- continue;
- }
-
- Type attributeType = t.GetGenericArguments()[0];
- MethodInfo method = checkType
- .GetMethods(BindingFlags.Public | BindingFlags.Instance)
- .First(x => x.Name == "ExecuteCheckAsync" && x.GetParameters()[0].ParameterType == attributeType);
-
- // create the func for invoking the check here, during startup
- ParameterExpression check = Expression.Parameter(checkType);
- ParameterExpression attribute = Expression.Parameter(attributeType);
- ParameterExpression context = Expression.Parameter(typeof(CommandContext));
- MethodCallExpression call = Expression.Call(
- instance: check,
- method: method,
- arg0: attribute,
- arg1: context
- );
-
- Type delegateType = typeof(Func<,,,>).MakeGenericType(
- checkType,
- attributeType,
- typeof(CommandContext),
- typeof(ValueTask)
- );
-
- CheckFunc func = Unsafe.As(Expression.Lambda(delegateType, call, check, attribute, context).Compile());
- this.checks.Add(new()
- {
- AttributeType = attributeType,
- CheckType = checkType,
- ExecuteCheckAsync = func,
- });
- }
- }
-
- ///
- /// Adds all parameter checks from the provided assembly to the extension.
- ///
- public void AddParameterChecks(Assembly assembly)
- {
- foreach (Type t in assembly.GetTypes())
- {
- if (t.GetInterface("DSharpPlus.Commands.ContextChecks.ParameterChecks.IParameterCheck`1") is not null)
- {
- AddParameterCheck(t);
- }
- }
- }
-
- ///
- /// Adds a new check to the extension.
- ///
- public void AddParameterCheck() where T : IParameterCheck => AddParameterCheck(typeof(T));
-
- ///
- /// Adds a new check to the extension.
- ///
- public void AddParameterCheck(Type checkType)
- {
- // get all implemented check interfaces, we can pretty easily handle having multiple checks in one type
- foreach (Type t in checkType.GetInterfaces())
- {
- if (t.Namespace != "DSharpPlus.Commands.ContextChecks.ParameterChecks" || t.Name != "IParameterCheck`1")
- {
- continue;
- }
-
- Type attributeType = t.GetGenericArguments()[0];
- MethodInfo method = checkType
- .GetMethods(BindingFlags.Public | BindingFlags.Instance)
- .First(x => x.Name == "ExecuteCheckAsync" && x.GetParameters()[0].ParameterType == attributeType);
-
- // create the func for invoking the check here, during startup
- ParameterExpression check = Expression.Parameter(checkType);
- ParameterExpression attribute = Expression.Parameter(attributeType);
- ParameterExpression info = Expression.Parameter(typeof(ParameterCheckInfo));
- ParameterExpression context = Expression.Parameter(typeof(CommandContext));
- MethodCallExpression call = Expression.Call(
- instance: check,
- method: method,
- arg0: attribute,
- arg1: info,
- arg2: context
- );
-
- Type delegateType = typeof(Func<,,,,>).MakeGenericType(
- checkType,
- attributeType,
- typeof(ParameterCheckInfo),
- typeof(CommandContext),
- typeof(ValueTask)
- );
-
- ParameterCheckFunc func = Unsafe.As(Expression.Lambda(delegateType, call, check, attribute, info, context).Compile());
- this.parameterChecks.Add(
- new()
- {
- AttributeType = attributeType,
- CheckType = checkType,
- ExecuteCheckAsync = func,
- }
- );
- }
- }
-
- public async Task RefreshAsync()
- {
- await BuildCommandsAsync();
-
- if (this.RegisterDefaultCommandProcessors)
- {
- this.processors.TryAdd(typeof(TextCommandProcessor), new TextCommandProcessor());
- this.processors.TryAdd(typeof(SlashCommandProcessor), new SlashCommandProcessor());
- this.processors.TryAdd(typeof(MessageCommandProcessor), new MessageCommandProcessor());
- this.processors.TryAdd(typeof(UserCommandProcessor), new UserCommandProcessor());
- }
-
- if (this.processors.TryGetValue(typeof(UserCommandProcessor), out ICommandProcessor? userProcessor))
- {
- await userProcessor.ConfigureAsync(this);
- }
-
- if (this.processors.TryGetValue(typeof(MessageCommandProcessor), out ICommandProcessor? messageProcessor))
- {
- await messageProcessor.ConfigureAsync(this);
- }
-
- foreach (ICommandProcessor processor in this.processors.Values)
- {
- Type type = processor.GetType();
- if (type == typeof(UserCommandProcessor) || type == typeof(MessageCommandProcessor))
- {
- continue;
- }
-
- await processor.ConfigureAsync(this);
- }
- }
-
- internal async ValueTask BuildCommandsAsync()
- {
- await this.configuringCommands.InvokeAsync(this, new ConfigureCommandsEventArgs() { CommandTrees = this.commandBuilders });
-
- Dictionary commands = [];
- foreach (CommandBuilder commandBuilder in this.commandBuilders)
- {
- try
- {
- Command command = commandBuilder.Build();
- commands.Add(command.Name, command);
- }
- catch (Exception error)
- {
- this.logger.LogError(error, "Failed to build command '{CommandBuilder}'", commandBuilder.FullName);
- }
- }
-
- this.Commands = commands.ToFrozenDictionary();
- }
-
- ///
- /// The default command error handler. Only used if is set to true.
- ///
- /// The extension.
- /// The event arguments containing the exception.
- private static async Task DefaultCommandErrorHandlerAsync(CommandsExtension extension, CommandErroredEventArgs eventArgs)
- {
- StringBuilder stringBuilder = new();
- DiscordMessageBuilder messageBuilder = new();
-
- // Error message
- stringBuilder.Append(eventArgs.Exception switch
- {
- CommandNotFoundException commandNotFoundException => $"Command ``{commandNotFoundException.CommandName}`` was not found.",
- CommandRegistrationFailedException => $"Application commands failed to register.",
- ArgumentParseException argumentParseException when argumentParseException.ConversionResult?.Value is not null =>
- $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: ``{argumentParseException.ConversionResult.Value.ToString() ?? ""}`` is not a valid value. {argumentParseException.Message}",
- ArgumentParseException argumentParseException =>
- $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: {argumentParseException.Message}",
- ChecksFailedException checksFailedException when checksFailedException.Errors.Count == 1 =>
- $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
- ChecksFailedException checksFailedException =>
- $"The following context checks failed: ```\n{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}\n```.",
- ParameterChecksFailedException checksFailedException when checksFailedException.Errors.Count == 1 =>
- $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
- ParameterChecksFailedException checksFailedException =>
- $"The following context checks failed: ```\n{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}\n```.",
- DiscordException discordException when discordException.Response is not null && (int)discordException.Response.StatusCode >= 500 && (int)discordException.Response.StatusCode < 600 =>
- $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? "No further information was provided."}",
- DiscordException discordException when discordException.Response is not null =>
- $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? discordException.Message}",
- _ => $"An unexpected error occurred: {eventArgs.Exception.Message}",
- });
-
- // Stack trace
- if (!string.IsNullOrWhiteSpace(eventArgs.Exception.StackTrace))
- {
- // If the stack trace can fit inside a codeblock
- if (8 + eventArgs.Exception.StackTrace.Length + stringBuilder.Length <= 2000)
- {
- stringBuilder.Append($"```\n{eventArgs.Exception.StackTrace}\n```");
- messageBuilder.WithContent(stringBuilder.ToString());
- }
- // If the exception message exceeds the message character limit, cram it all into an attatched file with a simple message in the content.
- else if (stringBuilder.Length >= 2000)
- {
- messageBuilder.WithContent(
- "Exception Message exceeds character limit, see attached file."
- );
- string formattedFile =
- $"{stringBuilder}{Environment.NewLine}{Environment.NewLine}Stack Trace:{Environment.NewLine}{eventArgs.Exception.StackTrace}";
- messageBuilder.AddFile(
- "MessageAndStackTrace.txt",
- new MemoryStream(Encoding.UTF8.GetBytes(formattedFile)),
- AddFileOptions.CloseStream
- );
- }
- // Otherwise, display the exception message in the content and the trace in an attached file
- else
- {
- messageBuilder.WithContent(stringBuilder.ToString());
- messageBuilder.AddFile("StackTrace.txt", new MemoryStream(Encoding.UTF8.GetBytes(eventArgs.Exception.StackTrace)), AddFileOptions.CloseStream);
- }
- }
- // If no stack trace, and the message is still too long, attatch a file with the message and use a simple message in the content.
- else if (stringBuilder.Length >= 2000)
- {
- messageBuilder.WithContent("Exception Message exceeds character limit, see attached file.");
- messageBuilder.AddFile("Message.txt", new MemoryStream(Encoding.UTF8.GetBytes(stringBuilder.ToString())), AddFileOptions.CloseStream);
- }
- // Otherwise, if no stack trace and the Exception message will fit, send the message as content
- else
- {
- messageBuilder.WithContent(stringBuilder.ToString());
- }
-
- if (eventArgs.Context is SlashCommandContext { Interaction.ResponseState: not DiscordInteractionResponseState.Unacknowledged })
- {
- await eventArgs.Context.FollowupAsync(messageBuilder);
- }
- else
- {
- await eventArgs.Context.RespondAsync(messageBuilder);
- }
- }
-}
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+using DSharpPlus.AsyncEvents;
+using DSharpPlus.Commands.ContextChecks;
+using DSharpPlus.Commands.ContextChecks.ParameterChecks;
+using DSharpPlus.Commands.EventArgs;
+using DSharpPlus.Commands.Exceptions;
+using DSharpPlus.Commands.Processors;
+using DSharpPlus.Commands.Processors.MessageCommands;
+using DSharpPlus.Commands.Processors.SlashCommands;
+using DSharpPlus.Commands.Processors.TextCommands;
+using DSharpPlus.Commands.Processors.TextCommands.ContextChecks;
+using DSharpPlus.Commands.Processors.UserCommands;
+using DSharpPlus.Commands.Trees;
+using DSharpPlus.Commands.Trees.Metadata;
+using DSharpPlus.Entities;
+using DSharpPlus.Exceptions;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+using CheckFunc = System.Func
+<
+ object,
+ DSharpPlus.Commands.ContextChecks.ContextCheckAttribute,
+ DSharpPlus.Commands.CommandContext,
+ System.Threading.Tasks.ValueTask
+>;
+
+using ParameterCheckFunc = System.Func
+<
+ object,
+ DSharpPlus.Commands.ContextChecks.ParameterChecks.ParameterCheckAttribute,
+ DSharpPlus.Commands.ContextChecks.ParameterChecks.ParameterCheckInfo,
+ DSharpPlus.Commands.CommandContext,
+ System.Threading.Tasks.ValueTask
+>;
+
+namespace DSharpPlus.Commands;
+
+///
+/// An all in one extension for managing commands.
+///
+public sealed class CommandsExtension
+{
+ public DiscordClient Client { get; private set; }
+
+ ///
+ public IServiceProvider ServiceProvider { get; private set; }
+
+ ///
+ public ulong DebugGuildId { get; init; }
+
+ ///
+ public bool UseDefaultCommandErrorHandler { get; init; }
+
+ ///
+ public bool RegisterDefaultCommandProcessors { get; init; }
+
+ public ICommandExecutor CommandExecutor { get; init; }
+
+ ///
+ /// The registered commands that the users can execute.
+ ///
+ public IReadOnlyDictionary Commands { get; private set; } = new Dictionary();
+ private readonly List commandBuilders = [];
+
+ ///
+ /// All registered command processors.
+ ///
+ public IReadOnlyDictionary Processors => this.processors;
+ private readonly Dictionary processors = [];
+
+ public IReadOnlyList Checks => this.checks;
+ private readonly List checks = [];
+
+ public IReadOnlyList ParameterChecks => this.parameterChecks;
+ private readonly List parameterChecks = [];
+
+ ///
+ /// Executed everytime a command is finished executing.
+ ///
+ public event AsyncEventHandler CommandExecuted
+ {
+ add => this.commandExecuted.Register(value);
+ remove => this.commandExecuted.Unregister(value);
+ }
+
+ internal AsyncEvent commandExecuted;
+
+ ///
+ /// Executed everytime a command has errored.
+ ///
+ public event AsyncEventHandler CommandErrored
+ {
+ add => this.commandErrored.Register(value);
+ remove => this.commandErrored.Unregister(value);
+ }
+
+ internal AsyncEvent commandErrored;
+
+ ///
+ /// Executed before commands are finalized into a read-only state.
+ ///
+ ///
+ /// Apply any mass-mutations to the commands or command parameters here.
+ ///
+ public event AsyncEventHandler ConfiguringCommands
+ {
+ add => this.configuringCommands.Register(value);
+ remove => this.configuringCommands.Unregister(value);
+ }
+
+ private AsyncEvent configuringCommands;
+
+ ///
+ /// Used to log messages from this extension.
+ ///
+ private ILogger logger;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The configuration to use.
+ internal CommandsExtension(CommandsConfiguration configuration)
+ {
+ ArgumentNullException.ThrowIfNull(configuration);
+
+ this.DebugGuildId = configuration.DebugGuildId;
+ this.UseDefaultCommandErrorHandler = configuration.UseDefaultCommandErrorHandler;
+ this.RegisterDefaultCommandProcessors = configuration.RegisterDefaultCommandProcessors;
+ this.CommandExecutor = configuration.CommandExecutor;
+ }
+
+ ///
+ /// Sets up the extension to use the specified .
+ ///
+ /// The client to register our event handlers too.
+ public void Setup(DiscordClient client)
+ {
+ if (client is null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+ else if (this.Client is not null)
+ {
+ throw new InvalidOperationException("Commands Extension is already initialized.");
+ }
+
+ this.Client = client;
+ this.ServiceProvider = client.ServiceProvider;
+ this.logger = client.ServiceProvider.GetRequiredService>();
+
+ DefaultClientErrorHandler errorHandler = new(client.Logger);
+ this.commandErrored = new(errorHandler);
+ this.commandExecuted = new(errorHandler);
+ this.configuringCommands = new(errorHandler);
+
+ // TODO: Move this to the IEventHandler system so the Commands namespace
+ // will have zero awareness of built-in command processors.
+ this.configuringCommands.Register(SlashCommandProcessor.ConfigureCommands);
+ if (this.UseDefaultCommandErrorHandler)
+ {
+ this.CommandErrored += DefaultCommandErrorHandlerAsync;
+ }
+
+ AddCheck();
+ AddCheck();
+ AddCheck();
+ AddCheck();
+ AddCheck();
+ AddCheck();
+
+ AddParameterCheck();
+ AddParameterCheck();
+ AddParameterCheck();
+ AddParameterCheck();
+ }
+
+ public void AddCommand(CommandBuilder command) => this.commandBuilders.Add(command);
+ public void AddCommand(Delegate commandDelegate, params ulong[] guildIds) => this.commandBuilders.Add(CommandBuilder.From(commandDelegate, guildIds));
+ public void AddCommand(Delegate commandDelegate) => this.commandBuilders.Add(CommandBuilder.From(commandDelegate));
+ public void AddCommand(Type type, params ulong[] guildIds) => this.commandBuilders.Add(CommandBuilder.From(type, guildIds));
+ public void AddCommand(Type type) => this.commandBuilders.Add(CommandBuilder.From(type));
+
+ // !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null
+ // This is done to prevent nested classes from being added as commands, while still allowing non-command classes containing commands to be added.
+ // See https://github.com/DSharpPlus/DSharpPlus/pull/2273#discussion_r2009114568 for more information.
+ public void AddCommands(Assembly assembly, params ulong[] guildIds) => AddCommands(assembly.GetTypes().Where(type =>
+ !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null), guildIds);
+
+ public void AddCommands(Assembly assembly) => AddCommands(assembly.GetTypes().Where(type =>
+ !type.IsNested || type.DeclaringType?.GetCustomAttribute() is null));
+
+ public void AddCommands(IEnumerable commands) => this.commandBuilders.AddRange(commands);
+ public void AddCommands(IEnumerable types) => AddCommands(types, []);
+ public void AddCommands(params CommandBuilder[] commands) => this.commandBuilders.AddRange(commands);
+ public void AddCommands(Type type, params ulong[] guildIds) => AddCommands([type], guildIds);
+ public void AddCommands(Type type) => AddCommands([type]);
+ public void AddCommands() => AddCommands([typeof(T)]);
+ public void AddCommands(params ulong[] guildIds) => AddCommands([typeof(T)], guildIds);
+ public void AddCommands(IEnumerable types, params ulong[] guildIds)
+ {
+ foreach (Type type in types)
+ {
+ if (type.GetCustomAttribute() is not null)
+ {
+ this.Client.Logger.LogDebug("Adding command from type {Type}", type.FullName ?? type.Name);
+ this.commandBuilders.Add(CommandBuilder.From(type, guildIds));
+ continue;
+ }
+
+ foreach (MethodInfo method in type.GetMethods())
+ {
+ if (method.GetCustomAttribute() is not null)
+ {
+ this.Client.Logger.LogDebug("Adding command from type {Type}", type.FullName ?? type.Name);
+ this.commandBuilders.Add(CommandBuilder.From(method, guildIds: guildIds));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets a list of commands filtered for a specific command processor
+ ///
+ /// Processor which is calling this method
+ /// Returns a list of valid commands. This list can be empty if no commands are valid for this processor type
+ public IReadOnlyList GetCommandsForProcessor(ICommandProcessor processor)
+ {
+ // Those processors use a different attribute to filter and filter themself
+ if (processor is MessageCommandProcessor or UserCommandProcessor)
+ {
+ return this.Commands.Values.ToList();
+ }
+
+ Type contextType = processor.ContextType;
+ Type processorType = processor.GetType();
+ List commands = new(this.Commands.Values.Count());
+ foreach (Command command in this.Commands.Values)
+ {
+ Command? filteredCommand = FilterCommand(command, processorType, contextType);
+ if (filteredCommand is not null)
+ {
+ commands.Add(filteredCommand);
+ }
+ }
+
+ return commands;
+ }
+
+ private Command? FilterCommand(Command command, Type processorType, Type contextType)
+ {
+ AllowedProcessorsAttribute? allowedProcessorsAttribute = command.Attributes.OfType().FirstOrDefault();
+ if (allowedProcessorsAttribute is not null && !allowedProcessorsAttribute.Processors.Contains(processorType))
+ {
+ return null;
+ }
+ else if (command.Method is not null)
+ {
+ Type methodContextType = command.Method.GetParameters().First().ParameterType;
+ if (!methodContextType.IsAssignableTo(contextType) && methodContextType != typeof(CommandContext))
+ {
+ return null;
+ }
+ }
+
+ List subcommands = new(command.Subcommands.Count);
+ foreach (Command subcommand in command.Subcommands)
+ {
+ Command? filteredSubcommand = FilterCommand(subcommand, processorType, contextType);
+ if (filteredSubcommand is not null)
+ {
+ subcommands.Add(filteredSubcommand);
+ }
+ }
+
+ return command with
+ {
+ Subcommands = subcommands,
+ };
+ }
+
+ public void AddProcessor(ICommandProcessor processor) => this.processors.Add(processor.GetType(), processor);
+ public void AddProcessor() where TProcessor : ICommandProcessor, new() => AddProcessor(new TProcessor());
+ public void AddProcessors(params ICommandProcessor[] processors) => AddProcessors((IEnumerable)processors);
+ public void AddProcessors(IEnumerable processors)
+ {
+ foreach (ICommandProcessor processor in processors)
+ {
+ AddProcessor(processor);
+ }
+ }
+
+ public TProcessor GetProcessor() where TProcessor : ICommandProcessor => (TProcessor)this.processors[typeof(TProcessor)];
+ public bool TryGetProcessor([NotNullWhen(true)] out TProcessor? processor) where TProcessor : ICommandProcessor
+ {
+ if (this.processors.TryGetValue(typeof(TProcessor), out ICommandProcessor? baseProcessor))
+ {
+ processor = (TProcessor)baseProcessor;
+ return true;
+ }
+
+ processor = default;
+ return false;
+ }
+
+ ///
+ /// Adds all public checks from the provided assembly to the extension.
+ ///
+ public void AddChecks(Assembly assembly)
+ {
+ foreach (Type t in assembly.GetTypes())
+ {
+ if (t.GetInterface("DSharpPlus.Commands.ContextChecks.IContextCheck`1") is not null)
+ {
+ AddCheck(t);
+ }
+ }
+ }
+
+ ///
+ /// Adds a new check to the extension.
+ ///
+ public void AddCheck() where T : IContextCheck => AddCheck(typeof(T));
+
+ ///
+ /// Adds a new check to the extension.
+ ///
+ public void AddCheck(Type checkType)
+ {
+ // get all implemented check interfaces, we can pretty easily handle having multiple checks in one type
+ foreach (Type t in checkType.GetInterfaces())
+ {
+ if (t.Namespace != "DSharpPlus.Commands.ContextChecks" || t.Name != "IContextCheck`1")
+ {
+ continue;
+ }
+
+ Type attributeType = t.GetGenericArguments()[0];
+ MethodInfo method = checkType
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .First(x => x.Name == "ExecuteCheckAsync" && x.GetParameters()[0].ParameterType == attributeType);
+
+ // create the func for invoking the check here, during startup
+ ParameterExpression check = Expression.Parameter(checkType);
+ ParameterExpression attribute = Expression.Parameter(attributeType);
+ ParameterExpression context = Expression.Parameter(typeof(CommandContext));
+ MethodCallExpression call = Expression.Call(
+ instance: check,
+ method: method,
+ arg0: attribute,
+ arg1: context
+ );
+
+ Type delegateType = typeof(Func<,,,>).MakeGenericType(
+ checkType,
+ attributeType,
+ typeof(CommandContext),
+ typeof(ValueTask)
+ );
+
+ CheckFunc func = Unsafe.As(Expression.Lambda(delegateType, call, check, attribute, context).Compile());
+ this.checks.Add(new()
+ {
+ AttributeType = attributeType,
+ CheckType = checkType,
+ ExecuteCheckAsync = func,
+ });
+ }
+ }
+
+ ///
+ /// Adds all parameter checks from the provided assembly to the extension.
+ ///
+ public void AddParameterChecks(Assembly assembly)
+ {
+ foreach (Type t in assembly.GetTypes())
+ {
+ if (t.GetInterface("DSharpPlus.Commands.ContextChecks.ParameterChecks.IParameterCheck`1") is not null)
+ {
+ AddParameterCheck(t);
+ }
+ }
+ }
+
+ ///
+ /// Adds a new check to the extension.
+ ///
+ public void AddParameterCheck() where T : IParameterCheck => AddParameterCheck(typeof(T));
+
+ ///
+ /// Adds a new check to the extension.
+ ///
+ public void AddParameterCheck(Type checkType)
+ {
+ // get all implemented check interfaces, we can pretty easily handle having multiple checks in one type
+ foreach (Type t in checkType.GetInterfaces())
+ {
+ if (t.Namespace != "DSharpPlus.Commands.ContextChecks.ParameterChecks" || t.Name != "IParameterCheck`1")
+ {
+ continue;
+ }
+
+ Type attributeType = t.GetGenericArguments()[0];
+ MethodInfo method = checkType
+ .GetMethods(BindingFlags.Public | BindingFlags.Instance)
+ .First(x => x.Name == "ExecuteCheckAsync" && x.GetParameters()[0].ParameterType == attributeType);
+
+ // create the func for invoking the check here, during startup
+ ParameterExpression check = Expression.Parameter(checkType);
+ ParameterExpression attribute = Expression.Parameter(attributeType);
+ ParameterExpression info = Expression.Parameter(typeof(ParameterCheckInfo));
+ ParameterExpression context = Expression.Parameter(typeof(CommandContext));
+ MethodCallExpression call = Expression.Call(
+ instance: check,
+ method: method,
+ arg0: attribute,
+ arg1: info,
+ arg2: context
+ );
+
+ Type delegateType = typeof(Func<,,,,>).MakeGenericType(
+ checkType,
+ attributeType,
+ typeof(ParameterCheckInfo),
+ typeof(CommandContext),
+ typeof(ValueTask)
+ );
+
+ ParameterCheckFunc func = Unsafe.As(Expression.Lambda(delegateType, call, check, attribute, info, context).Compile());
+ this.parameterChecks.Add(
+ new()
+ {
+ AttributeType = attributeType,
+ CheckType = checkType,
+ ExecuteCheckAsync = func,
+ }
+ );
+ }
+ }
+
+ public async Task RefreshAsync()
+ {
+ await BuildCommandsAsync();
+
+ if (this.RegisterDefaultCommandProcessors)
+ {
+ this.processors.TryAdd(typeof(TextCommandProcessor), new TextCommandProcessor());
+ this.processors.TryAdd(typeof(SlashCommandProcessor), new SlashCommandProcessor());
+ this.processors.TryAdd(typeof(MessageCommandProcessor), new MessageCommandProcessor());
+ this.processors.TryAdd(typeof(UserCommandProcessor), new UserCommandProcessor());
+ }
+
+ if (this.processors.TryGetValue(typeof(UserCommandProcessor), out ICommandProcessor? userProcessor))
+ {
+ await userProcessor.ConfigureAsync(this);
+ }
+
+ if (this.processors.TryGetValue(typeof(MessageCommandProcessor), out ICommandProcessor? messageProcessor))
+ {
+ await messageProcessor.ConfigureAsync(this);
+ }
+
+ foreach (ICommandProcessor processor in this.processors.Values)
+ {
+ Type type = processor.GetType();
+ if (type == typeof(UserCommandProcessor) || type == typeof(MessageCommandProcessor))
+ {
+ continue;
+ }
+
+ await processor.ConfigureAsync(this);
+ }
+ }
+
+ internal async ValueTask BuildCommandsAsync()
+ {
+ await this.configuringCommands.InvokeAsync(this, new ConfigureCommandsEventArgs() { CommandTrees = this.commandBuilders });
+
+ Dictionary commands = [];
+ foreach (CommandBuilder commandBuilder in this.commandBuilders)
+ {
+ try
+ {
+ Command command = commandBuilder.Build();
+ commands.Add(command.Name, command);
+ }
+ catch (Exception error)
+ {
+ this.logger.LogError(error, "Failed to build command '{CommandBuilder}'", commandBuilder.FullName);
+ }
+ }
+
+ this.Commands = commands.ToFrozenDictionary();
+ }
+
+ ///
+ /// The default command error handler. Only used if is set to true.
+ ///
+ /// The extension.
+ /// The event arguments containing the exception.
+ private static async Task DefaultCommandErrorHandlerAsync(CommandsExtension extension, CommandErroredEventArgs eventArgs)
+ {
+ StringBuilder stringBuilder = new();
+ DiscordMessageBuilder messageBuilder = new();
+
+ // Error message
+ stringBuilder.Append(eventArgs.Exception switch
+ {
+ CommandNotFoundException commandNotFoundException => $"Command ``{commandNotFoundException.CommandName}`` was not found.",
+ CommandRegistrationFailedException => $"Application commands failed to register.",
+ ArgumentParseException argumentParseException when argumentParseException.ConversionResult?.Value is not null =>
+ $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: ``{argumentParseException.ConversionResult.Value.ToString() ?? ""}`` is not a valid value. {argumentParseException.Message}",
+ ArgumentParseException argumentParseException =>
+ $"Failed to parse argument ``{argumentParseException.Parameter.Name}``: {argumentParseException.Message}",
+ ChecksFailedException checksFailedException when checksFailedException.Errors.Count == 1 =>
+ $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
+ ChecksFailedException checksFailedException =>
+ $"The following context checks failed: ```\n{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}\n```.",
+ ParameterChecksFailedException checksFailedException when checksFailedException.Errors.Count == 1 =>
+ $"The following error occurred: ``{checksFailedException.Errors[0].ErrorMessage}``",
+ ParameterChecksFailedException checksFailedException =>
+ $"The following context checks failed: ```\n{string.Join("\n- ", checksFailedException.Errors.Select(x => x.ErrorMessage)).Trim()}\n```.",
+ DiscordException discordException when discordException.Response is not null && (int)discordException.Response.StatusCode >= 500 && (int)discordException.Response.StatusCode < 600 =>
+ $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? "No further information was provided."}",
+ DiscordException discordException when discordException.Response is not null =>
+ $"Discord API error {discordException.Response.StatusCode} occurred: {discordException.JsonMessage ?? discordException.Message}",
+ _ => $"An unexpected error occurred: {eventArgs.Exception.Message}",
+ });
+
+ // Stack trace
+ if (!string.IsNullOrWhiteSpace(eventArgs.Exception.StackTrace))
+ {
+ // If the stack trace can fit inside a codeblock
+ if (8 + eventArgs.Exception.StackTrace.Length + stringBuilder.Length <= 2000)
+ {
+ stringBuilder.Append($"```\n{eventArgs.Exception.StackTrace}\n```");
+ messageBuilder.WithContent(stringBuilder.ToString());
+ }
+ // If the exception message exceeds the message character limit, cram it all into an attatched file with a simple message in the content.
+ else if (stringBuilder.Length >= 2000)
+ {
+ messageBuilder.WithContent(
+ "Exception Message exceeds character limit, see attached file."
+ );
+ string formattedFile =
+ $"{stringBuilder}{Environment.NewLine}{Environment.NewLine}Stack Trace:{Environment.NewLine}{eventArgs.Exception.StackTrace}";
+ messageBuilder.AddFile(
+ "MessageAndStackTrace.txt",
+ new MemoryStream(Encoding.UTF8.GetBytes(formattedFile)),
+ AddFileOptions.CloseStream
+ );
+ }
+ // Otherwise, display the exception message in the content and the trace in an attached file
+ else
+ {
+ messageBuilder.WithContent(stringBuilder.ToString());
+ messageBuilder.AddFile("StackTrace.txt", new MemoryStream(Encoding.UTF8.GetBytes(eventArgs.Exception.StackTrace)), AddFileOptions.CloseStream);
+ }
+ }
+ // If no stack trace, and the message is still too long, attatch a file with the message and use a simple message in the content.
+ else if (stringBuilder.Length >= 2000)
+ {
+ messageBuilder.WithContent("Exception Message exceeds character limit, see attached file.");
+ messageBuilder.AddFile("Message.txt", new MemoryStream(Encoding.UTF8.GetBytes(stringBuilder.ToString())), AddFileOptions.CloseStream);
+ }
+ // Otherwise, if no stack trace and the Exception message will fit, send the message as content
+ else
+ {
+ messageBuilder.WithContent(stringBuilder.ToString());
+ }
+
+ if (eventArgs.Context is SlashCommandContext { Interaction.ResponseState: not DiscordInteractionResponseState.Unacknowledged })
+ {
+ await eventArgs.Context.FollowupAsync(messageBuilder);
+ }
+ else
+ {
+ await eventArgs.Context.RespondAsync(messageBuilder);
+ }
+ }
+}
diff --git a/DSharpPlus.Commands/ContextChecks/ContextCheckAttribute.cs b/DSharpPlus.Commands/ContextChecks/ContextCheckAttribute.cs
index befc6002c1..92055ae916 100644
--- a/DSharpPlus.Commands/ContextChecks/ContextCheckAttribute.cs
+++ b/DSharpPlus.Commands/ContextChecks/ContextCheckAttribute.cs
@@ -1,6 +1,6 @@
-using System;
-
-namespace DSharpPlus.Commands.ContextChecks;
-
-[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
-public abstract class ContextCheckAttribute : Attribute;
+using System;
+
+namespace DSharpPlus.Commands.ContextChecks;
+
+[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
+public abstract class ContextCheckAttribute : Attribute;
diff --git a/DSharpPlus.Commands/ContextChecks/ContextCheckFailedData.cs b/DSharpPlus.Commands/ContextChecks/ContextCheckFailedData.cs
index 369eb3d348..a973b09700 100644
--- a/DSharpPlus.Commands/ContextChecks/ContextCheckFailedData.cs
+++ b/DSharpPlus.Commands/ContextChecks/ContextCheckFailedData.cs
@@ -1,13 +1,13 @@
-using System;
-
-namespace DSharpPlus.Commands.ContextChecks;
-
-///
-/// Represents data for when a context check fails execution.
-///
-public sealed class ContextCheckFailedData
-{
- public required ContextCheckAttribute ContextCheckAttribute { get; init; }
- public required string ErrorMessage { get; init; }
- public Exception? Exception { get; init; }
-}
+using System;
+
+namespace DSharpPlus.Commands.ContextChecks;
+
+///
+/// Represents data for when a context check fails execution.
+///
+public sealed class ContextCheckFailedData
+{
+ public required ContextCheckAttribute ContextCheckAttribute { get; init; }
+ public required string ErrorMessage { get; init; }
+ public Exception? Exception { get; init; }
+}
diff --git a/DSharpPlus.Commands/ContextChecks/ContextCheckMapEntry.cs b/DSharpPlus.Commands/ContextChecks/ContextCheckMapEntry.cs
index c1bb815c44..9259c82522 100644
--- a/DSharpPlus.Commands/ContextChecks/ContextCheckMapEntry.cs
+++ b/DSharpPlus.Commands/ContextChecks/ContextCheckMapEntry.cs
@@ -1,18 +1,18 @@
-using System;
-using System.Threading.Tasks;
-
-namespace DSharpPlus.Commands.ContextChecks;
-
-///
-/// Represents an entry in a map of attributes to check types. we can't just do this as a dictionary because one attribute may
-/// key multiple different checks.
-///
-public readonly record struct ContextCheckMapEntry
-{
- public required Type AttributeType { get; init; }
-
- public required Type CheckType { get; init; }
-
- // we cache this here so that we don't have to deal with it every invocation.
- public required Func