| @@ -1 +1,3 @@ | |||||
| github: quinchs | |||||
| open_collective: discordnet | open_collective: discordnet | ||||
| custom: https://paypal.me/quinchs | |||||
| @@ -38,7 +38,7 @@ body: | |||||
| id: description | id: description | ||||
| attributes: | attributes: | ||||
| label: Description | label: Description | ||||
| description: A brief explination of the bug. | |||||
| description: A brief explanation of the bug. | |||||
| placeholder: When I start a DiscordSocketClient without stopping it, the gateway thread gets blocked. | placeholder: When I start a DiscordSocketClient without stopping it, the gateway thread gets blocked. | ||||
| validations: | validations: | ||||
| required: true | required: true | ||||
| @@ -62,7 +62,7 @@ body: | |||||
| id: logs | id: logs | ||||
| attributes: | attributes: | ||||
| label: Logs | label: Logs | ||||
| description: Add applicable logs and/or a stacktrace here. | |||||
| description: Add applicable logs and/or a stack trace here. | |||||
| validations: | validations: | ||||
| required: true | required: true | ||||
| - type: textarea | - type: textarea | ||||
| @@ -1,4 +1,65 @@ | |||||
| # Changelog | # Changelog | ||||
| ## [3.7.2] - 2022-06-02 | |||||
| ### Added | |||||
| - #2328 Add method overloads to InteractionService (0fad3e8) | |||||
| - #2336 Add support for attachments on interaction response type 7 (35db22e) | |||||
| - #2338 AddOptions no longer has an uneeded restriction, added AddOptions to SlashCommandOptionBuilder (3a37f89) | |||||
| ### Fixed | |||||
| - #2342 Disable TIV restrictions for rollout of TIV (7adf516) | |||||
| ## [3.7.1] - 2022-05-27 | |||||
| ### Added | |||||
| - #2325 Add missing interaction properties (d3a693a) | |||||
| - #2330 Add better call control in ParseHttpInteraction (a890de9) | |||||
| ### Fixed | |||||
| - #2329 Voice perms not retaining text perms. (712a4ae) | |||||
| - #2331 NRE with Cacheable.DownloadAsync() (e1f9b76) | |||||
| ## [3.7.0] - 2022-05-24 | |||||
| ### Added | |||||
| - #2269 Text-In-Voice (23656e8) | |||||
| - #2281 Optional API calling to RestInteraction (a24dde4) | |||||
| - #2283 Support FailIfNotExists on MessageReference (0ec8938) | |||||
| - #2284 Add Parse & TryParse to EmbedBuilder & Add ToJsonString extension (cea59b5) | |||||
| - #2289 Add UpdateAsync to SocketModal (b333de2) | |||||
| - #2291 Webhook support for threads (b0a3b65) | |||||
| - #2295 Add DefaultArchiveDuration to ITextChannel (1f01881) | |||||
| - #2296 Add `.With` methods to ActionRowBuilder (13ccc7c) | |||||
| - #2307 Add Nullable ComponentTypeConverter and TypeReader (6fbd396) | |||||
| - #2316 Forum channels (7a07fd6) | |||||
| ### Fixed | |||||
| - #2290 Possible NRE in Sanitize (20ffa64) | |||||
| - #2293 Application commands are disabled to everyone except admins by default (b465d60) | |||||
| - #2299 Close-stage bucketId being null (725d255) | |||||
| - #2313 Upload file size limit being incorrectly calculated (54a5af7) | |||||
| - #2319 Use `IDiscordClient.GetUserAsync` impl in `DiscordSocketClient` (f47f319) | |||||
| - #2320 NRE with bot scope and user parameters (88f6168) | |||||
| ## [3.6.1] - 2022-04-30 | |||||
| ### Added | |||||
| - #2272 add 50080 Error code (503e720) | |||||
| ### Fixed | |||||
| - #2267 Permissions v2 Invalid Operation Exception (a8f6075) | |||||
| - #2271 null user on interaction without bot scope (f2bb55e) | |||||
| - #2274 Implement fix for Custom Id Segments NRE (0d74c5c) | |||||
| ### Misc | |||||
| - 3.6.0 (27226f0) | |||||
| ## [3.6.0] - 2022-04-28 | |||||
| ### Added | |||||
| - #2136 Passing CustomId matches into contexts (4ce1801) | |||||
| - #2222 V2 Permissions (d98b3cc) | |||||
| ### Fixed | |||||
| - #2260 Guarding against empty descriptions in `SlashCommandBuilder`/`SlashCommandOptionBuilder` (0554ac2) | |||||
| - #2248 Fix SocketGuild not returning the AudioClient (daba58c) | |||||
| - #2254 Fix browser property (275b833) | |||||
| ## [3.5.0] - 2022-04-05 | ## [3.5.0] - 2022-04-05 | ||||
| @@ -1,12 +1,12 @@ | |||||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <VersionPrefix>3.5.0</VersionPrefix> | |||||
| <VersionPrefix>3.7.2</VersionPrefix> | |||||
| <LangVersion>latest</LangVersion> | <LangVersion>latest</LangVersion> | ||||
| <Authors>Discord.Net Contributors</Authors> | <Authors>Discord.Net Contributors</Authors> | ||||
| <PackageTags>discord;discordapp</PackageTags> | <PackageTags>discord;discordapp</PackageTags> | ||||
| <PackageProjectUrl>https://github.com/Discord-Net/Discord.Net</PackageProjectUrl> | <PackageProjectUrl>https://github.com/Discord-Net/Discord.Net</PackageProjectUrl> | ||||
| <PackageLicenseUrl>http://opensource.org/licenses/MIT</PackageLicenseUrl> | |||||
| <PackageIconUrl>https://github.com/Discord-Net/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png</PackageIconUrl> | |||||
| <PackageLicenseExpression>MIT</PackageLicenseExpression> | |||||
| <PackageIcon>PackageLogo.png</PackageIcon> | |||||
| <RepositoryType>git</RepositoryType> | <RepositoryType>git</RepositoryType> | ||||
| <RepositoryUrl>git://github.com/Discord-Net/Discord.Net</RepositoryUrl> | <RepositoryUrl>git://github.com/Discord-Net/Discord.Net</RepositoryUrl> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| @@ -23,4 +23,7 @@ | |||||
| <WarningsAsErrors>true</WarningsAsErrors> | <WarningsAsErrors>true</WarningsAsErrors> | ||||
| <GenerateDocumentationFile>true</GenerateDocumentationFile> | <GenerateDocumentationFile>true</GenerateDocumentationFile> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | |||||
| <None Include="../../docs/marketing/logo/PackageLogo.png" Pack="true" PackagePath=""/> | |||||
| </ItemGroup> | |||||
| </Project> | </Project> | ||||
| @@ -1,4 +1,3 @@ | |||||
| # Discord.Net | |||||
| <p align="center"> | <p align="center"> | ||||
| <a href="https://discordnet.dev/" title="Click to visit the documentation!"> | <a href="https://discordnet.dev/" title="Click to visit the documentation!"> | ||||
| <img src="https://raw.githubusercontent.com/discord-net/Discord.Net/dev/docs/marketing/logo/SVG/Combinationmark%20White%20Border.svg" alt="Logo"> | <img src="https://raw.githubusercontent.com/discord-net/Discord.Net/dev/docs/marketing/logo/SVG/Combinationmark%20White%20Border.svg" alt="Logo"> | ||||
| @@ -18,7 +17,7 @@ | |||||
| <img src="https://discord.com/api/guilds/848176216011046962/widget.png" alt="Discord"> | <img src="https://discord.com/api/guilds/848176216011046962/widget.png" alt="Discord"> | ||||
| </a> | </a> | ||||
| </p> | </p> | ||||
| Discord NET is an unofficial .NET API Wrapper for the Discord client (https://discord.com). | |||||
| Discord.Net is an unofficial .NET API Wrapper for the Discord client (https://discord.com). | |||||
| ## Documentation | ## Documentation | ||||
| @@ -60,7 +60,7 @@ | |||||
| "overwrite": "_overwrites/**/**.md", | "overwrite": "_overwrites/**/**.md", | ||||
| "globalMetadata": { | "globalMetadata": { | ||||
| "_appTitle": "Discord.Net Documentation", | "_appTitle": "Discord.Net Documentation", | ||||
| "_appFooter": "Discord.Net (c) 2015-2022 3.5.0", | |||||
| "_appFooter": "Discord.Net (c) 2015-2022 3.7.2", | |||||
| "_enableSearch": true, | "_enableSearch": true, | ||||
| "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", | ||||
| "_appFaviconPath": "favicon.ico" | "_appFaviconPath": "favicon.ico" | ||||
| @@ -202,7 +202,7 @@ online in Discord. | |||||
| To create commands for your bot, you may choose from a variety of | To create commands for your bot, you may choose from a variety of | ||||
| command processors available. Throughout the guides, we will be using | command processors available. Throughout the guides, we will be using | ||||
| the one that Discord.Net ships with. @Guides.Commands.Intro will | |||||
| the one that Discord.Net ships with. @Guides.TextCommands.Intro will | |||||
| guide you through how to setup a program that is ready for | guide you through how to setup a program that is ready for | ||||
| [CommandService]. | [CommandService]. | ||||
| @@ -15,9 +15,10 @@ Slash commands can have a bunch of parameters, each their own type. Let's first | |||||
| | Integer | A number. | | | Integer | A number. | | ||||
| | Boolean | True or False. | | | Boolean | True or False. | | ||||
| | User | A user | | | User | A user | | ||||
| | Channel | A channel, this includes voice text and categories | | |||||
| | Role | A role. | | | Role | A role. | | ||||
| | Channel | A channel, this includes voice text and categories | | |||||
| | Mentionable | A role or a user. | | | Mentionable | A role or a user. | | ||||
| | File | A file | | |||||
| Each one of the parameter types has its own DNET type in the `SocketSlashCommandDataOption`'s Value field: | Each one of the parameter types has its own DNET type in the `SocketSlashCommandDataOption`'s Value field: | ||||
| | Name | C# Type | | | Name | C# Type | | ||||
| @@ -31,6 +32,7 @@ Each one of the parameter types has its own DNET type in the `SocketSlashCommand | |||||
| | Role | `SocketRole` | | | Role | `SocketRole` | | ||||
| | Channel | `SocketChannel` | | | Channel | `SocketChannel` | | ||||
| | Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` | | | Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` | | ||||
| | File | `IAttachment` | | |||||
| Let's start by making a command that takes in a user and lists their roles. | Let's start by making a command that takes in a user and lists their roles. | ||||
| @@ -99,7 +99,7 @@ When we run the command, our modal should pop up: | |||||
| ### Respond to modals | ### Respond to modals | ||||
| > [!WARNING] | > [!WARNING] | ||||
| > Modals can not be sent when respoding to a modal. | |||||
| > Modals can not be sent when responding to a modal. | |||||
| Once a user has submitted the modal, we need to let everyone know what | Once a user has submitted the modal, we need to let everyone know what | ||||
| their favorite food is. We can start by hooking a task to the client's | their favorite food is. We can start by hooking a task to the client's | ||||
| @@ -18,6 +18,8 @@ AutocompleteHandlers raise the `AutocompleteHandlerExecuted` event on execution. | |||||
| A valid AutocompleteHandlers must inherit [AutocompleteHandler] base type and implement all of its abstract methods. | A valid AutocompleteHandlers must inherit [AutocompleteHandler] base type and implement all of its abstract methods. | ||||
| [!code-csharp[Autocomplete Command Example](samples/autocompletion/autocomplete-example.cs)] | |||||
| ### GenerateSuggestionsAsync() | ### GenerateSuggestionsAsync() | ||||
| The Interactions Service uses this method to generate a response of an Autocomplete Interaction. | The Interactions Service uses this method to generate a response of an Autocomplete Interaction. | ||||
| @@ -86,6 +86,7 @@ By default, your methods can feature the following parameter types: | |||||
| - Implementations of [IChannel] | - Implementations of [IChannel] | ||||
| - Implementations of [IRole] | - Implementations of [IRole] | ||||
| - Implementations of [IMentionable] | - Implementations of [IMentionable] | ||||
| - Implementations of [IAttachment] | |||||
| - `string` | - `string` | ||||
| - `float`, `double`, `decimal` | - `float`, `double`, `decimal` | ||||
| - `bool` | - `bool` | ||||
| @@ -158,6 +159,14 @@ Interaction service complex parameter constructors are prioritized in the follow | |||||
| 2. Constuctor tagged with `[ComplexParameterCtor]`. | 2. Constuctor tagged with `[ComplexParameterCtor]`. | ||||
| 3. Type's only public constuctor. | 3. Type's only public constuctor. | ||||
| #### DM Permissions | |||||
| You can use the [EnabledInDmAttribute] to configure whether a globally-scoped top level command should be enabled in Dms or not. Only works on top level commands. | |||||
| #### Default Member Permissions | |||||
| [DefaultMemberPermissionsAttribute] can be used when creating a command to set the permissions a user must have to use the command. Permission overwrites can be configured from the Integrations page of Guild Settings. [DefaultMemberPermissionsAttribute] cumulatively propagates down the class hierarchy until it reaches a top level command. This attribute can be only used on top level commands and will not work on commands that are nested in command groups. | |||||
| ## User Commands | ## User Commands | ||||
| A valid User Command must have the following structure: | A valid User Command must have the following structure: | ||||
| @@ -282,6 +291,8 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can | |||||
| > Although creating nested module stuctures are allowed, | > Although creating nested module stuctures are allowed, | ||||
| > you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy. | > you are not permitted to use more than 2 [GroupAttribute]'s in module hierarchy. | ||||
| [!code-csharp[Command Group Example](samples/intro/groupmodule.cs)] | |||||
| ## Executing Commands | ## Executing Commands | ||||
| Any of the following socket events can be used to execute commands: | Any of the following socket events can be used to execute commands: | ||||
| @@ -0,0 +1,59 @@ | |||||
| --- | |||||
| uid: Guides.IntFw.Perms | |||||
| title: How to handle permissions. | |||||
| --- | |||||
| # Permissions | |||||
| This page covers everything to know about setting up permissions for Slash & context commands. | |||||
| Application command (Slash, User & Message) permissions are set up at creation. | |||||
| When you add your commands to a guild or globally, the permissions will be set up from the attributes you defined. | |||||
| Commands that are added will only show up for members that meet the required permissions. | |||||
| There is no further internal handling, as Discord deals with this on its own. | |||||
| > [!WARNING] | |||||
| > Permissions can only be configured at top level commands. Not in subcommands. | |||||
| ## Disallowing commands in DM | |||||
| Commands can be blocked from being executed in DM if a guild is required to execute them in as followed: | |||||
| [!code-csharp[no-DM permission](samples/permissions/guild-only.cs)] | |||||
| > [!TIP] | |||||
| > This attribute only works on global-level commands. Commands that are registered in guilds alone do not have a need for it. | |||||
| ## Server permissions | |||||
| As previously shown, a command like ban can be blocked from being executed inside DMs, | |||||
| as there are no members to ban inside of a DM. However, for a command like this, | |||||
| we'll also want to make block it from being used by members that do not have the [permissions]. | |||||
| To do this, we can use the `DefaultMemberPermissions` attribute: | |||||
| [!code-csharp[Server permissions](samples/permissions/guild-perms.cs)] | |||||
| ### Stacking permissions | |||||
| If you want a user to have multiple [permissions] in order to execute a command, you can use the `|` operator, just like with setting up intents: | |||||
| [!code-csharp[Permission stacking](samples/permissions/perm-stacking.cs)] | |||||
| ### Nesting permissions | |||||
| Alternatively, permissions can also be nested. | |||||
| It will look for all uses of `DefaultMemberPermissions` up until the highest level class. | |||||
| The `EnabledInDm` attribute can be defined at top level as well, | |||||
| and will be set up for all of the commands & nested modules inside this class. | |||||
| [!code-csharp[Permission stacking](samples/permissions/perm-nesting.cs)] | |||||
| The amount of nesting you can do is realistically endless. | |||||
| > [!NOTE] | |||||
| > If the nested class is marked with `Group`, as required for setting up subcommands, this example will not work. | |||||
| > As mentioned before, subcommands cannot have seperate permissions from the top level command. | |||||
| [permissions]: xref:Discord.GuildPermission | |||||
| @@ -0,0 +1,20 @@ | |||||
| // you need to add `Autocomplete` attribute before parameter to add autocompletion to it | |||||
| [SlashCommand("command_name", "command_description")] | |||||
| public async Task ExampleCommand([Summary("parameter_name"), Autocomplete(typeof(ExampleAutocompleteHandler))] string parameterWithAutocompletion) | |||||
| => await RespondAsync($"Your choice: {parameterWithAutocompletion}"); | |||||
| public class ExampleAutocompleteHandler : AutocompleteHandler | |||||
| { | |||||
| public override async Task<AutocompletionResult> GenerateSuggestionsAsync(IInteractionContext context, IAutocompleteInteraction autocompleteInteraction, IParameterInfo parameter, IServiceProvider services) | |||||
| { | |||||
| // Create a collection with suggestions for autocomplete | |||||
| IEnumerable<AutocompleteResult> results = new[] | |||||
| { | |||||
| new AutocompleteResult("Name1", "value111"), | |||||
| new AutocompleteResult("Name2", "value2") | |||||
| }; | |||||
| // max - 25 suggestions at a time (API limit) | |||||
| return AutocompletionResult.FromSuccess(results.Take(25)); | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,21 @@ | |||||
| [AutocompleteCommand("parameter_name", "command_name")] | [AutocompleteCommand("parameter_name", "command_name")] | ||||
| public async Task Autocomplete() | public async Task Autocomplete() | ||||
| { | { | ||||
| IEnumerable<AutocompleteResult> results; | |||||
| string userInput = (Context.Interaction as SocketAutocompleteInteraction).Data.Current.Value.ToString(); | |||||
| ... | |||||
| IEnumerable<AutocompleteResult> results = new[] | |||||
| { | |||||
| new AutocompleteResult("foo", "foo_value"), | |||||
| new AutocompleteResult("bar", "bar_value"), | |||||
| new AutocompleteResult("baz", "baz_value"), | |||||
| }.Where(x => x.Name.StartsWith(userInput, StringComparison.InvariantCultureIgnoreCase)); // only send suggestions that starts with user's input; use case insensitive matching | |||||
| await (Context.Interaction as SocketAutocompleteInteraction).RespondAsync(results); | |||||
| // max - 25 suggestions at a time | |||||
| await (Context.Interaction as SocketAutocompleteInteraction).RespondAsync(results.Take(25)); | |||||
| } | } | ||||
| // you need to add `Autocomplete` attribute before parameter to add autocompletion to it | |||||
| [SlashCommand("command_name", "command_description")] | |||||
| public async Task ExampleCommand([Summary("parameter_name"), Autocomplete] string parameterWithAutocompletion) | |||||
| => await RespondAsync($"Your choice: {parameterWithAutocompletion}"); | |||||
| @@ -0,0 +1,21 @@ | |||||
| // You can put commands in groups | |||||
| [Group("group-name", "Group description")] | |||||
| public class CommandGroupModule : InteractionModuleBase<SocketInteractionContext> | |||||
| { | |||||
| // This command will look like | |||||
| // group-name ping | |||||
| [SlashCommand("ping", "Get a pong")] | |||||
| public async Task PongSubcommand() | |||||
| => await RespondAsync("Pong!"); | |||||
| // And even in sub-command groups | |||||
| [Group("subcommand-group-name", "Subcommand group description")] | |||||
| public class SubСommandGroupModule : InteractionModuleBase<SocketInteractionContext> | |||||
| { | |||||
| // This command will look like | |||||
| // group-name subcommand-group-name echo | |||||
| [SlashCommand("echo", "Echo an input")] | |||||
| public async Task EchoSubcommand(string input) | |||||
| => await RespondAsync(input); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| [EnabledInDm(false)] | |||||
| [SlashCommand("ban", "Bans a user in this guild")] | |||||
| public async Task BanAsync(...) | |||||
| { | |||||
| ... | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| [EnabledInDm(false)] | |||||
| [DefaultMemberPermissions(GuildPermission.BanMembers)] | |||||
| [SlashCommand("ban", "Bans a user in this guild")] | |||||
| public async Task BanAsync(...) | |||||
| { | |||||
| ... | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| [EnabledInDm(true)] | |||||
| [DefaultMemberPermissions(GuildPermission.ViewChannels)] | |||||
| public class Module : InteractionModuleBase<SocketInteractionContext> | |||||
| { | |||||
| [DefaultMemberPermissions(GuildPermission.SendMessages)] | |||||
| public class NestedModule : InteractionModuleBase<SocketInteractionContext> | |||||
| { | |||||
| // While looking for more permissions, it has found 'ViewChannels' and 'SendMessages'. The result of this lookup will be: | |||||
| // ViewChannels + SendMessages + ManageMessages. | |||||
| // If these together are not found for target user, the command will not show up for them. | |||||
| [DefaultMemberPermissions(GuildPermission.ManageMessages)] | |||||
| [SlashCommand("ping", "Pong!")] | |||||
| public async Task Ping() | |||||
| => await RespondAsync("pong"); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,4 @@ | |||||
| [DefaultMemberPermissions(GuildPermission.SendMessages | GuildPermission.ViewChannels)] | |||||
| [SlashCommand("ping", "Pong!")] | |||||
| public async Task Ping() | |||||
| => await RespondAsync("pong"); | |||||
| @@ -57,6 +57,8 @@ | |||||
| topicUid: Guides.IntFw.DI | topicUid: Guides.IntFw.DI | ||||
| - name: Post-execution Handling | - name: Post-execution Handling | ||||
| topicUid: Guides.IntFw.PostExecution | topicUid: Guides.IntFw.PostExecution | ||||
| - name: Permissions | |||||
| topicUid: Guides.IntFw.Perms | |||||
| - name: Slash Command Basics | - name: Slash Command Basics | ||||
| items: | items: | ||||
| - name: Introduction | - name: Introduction | ||||
| @@ -1,12 +1,12 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
| <PackageReference Include="Discord.Net.WebSocket" Version="3.6.1"/> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -2,7 +2,7 @@ | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| <RootNamespace>InteractionFramework</RootNamespace> | <RootNamespace>InteractionFramework</RootNamespace> | ||||
| <StartupObject></StartupObject> | <StartupObject></StartupObject> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| @@ -13,13 +13,7 @@ | |||||
| <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> | <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> | ||||
| <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Core\Discord.Net.Core.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Interactions\Discord.Net.Interactions.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
| <PackageReference Include="Discord.Net.Interactions" Version="3.6.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -1,16 +0,0 @@ | |||||
| | |||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRSample", "MediatRSample\MediatRSample.csproj", "{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}" | |||||
| EndProject | |||||
| Global | |||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||||
| Debug|Any CPU = Debug|Any CPU | |||||
| Release|Any CPU = Release|Any CPU | |||||
| EndGlobalSection | |||||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||||
| {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| EndGlobalSection | |||||
| EndGlobal | |||||
| @@ -2,18 +2,13 @@ | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| <RootNamespace>ShardedClient</RootNamespace> | <RootNamespace>ShardedClient</RootNamespace> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Commands\Discord.Net.Commands.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Interactions\Discord.Net.Interactions.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
| <PackageReference Include="Discord.Net" Version="3.6.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -2,17 +2,14 @@ | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| <RootNamespace>TextCommandFramework</RootNamespace> | <RootNamespace>TextCommandFramework</RootNamespace> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" /> | ||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.Commands\Discord.Net.Commands.csproj" /> | |||||
| <ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" /> | |||||
| <PackageReference Include="Discord.Net.Commands" Version="3.6.1" /> | |||||
| <PackageReference Include="Discord.Net.Websocket" Version="3.6.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -2,12 +2,12 @@ | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <OutputType>Exe</OutputType> | <OutputType>Exe</OutputType> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| <RootNamespace>WebHookClient</RootNamespace> | <RootNamespace>WebHookClient</RootNamespace> | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\..\src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" /> | |||||
| <PackageReference Include="Discord.Net.Webhook" Version="3.6.1" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| </Project> | </Project> | ||||
| @@ -7,6 +7,8 @@ | |||||
| <Description>A Discord.Net extension adding support for bot commands.</Description> | <Description>A Discord.Net extension adding support for bot commands.</Description> | ||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <WarningLevel>5</WarningLevel> | |||||
| <TreatWarningsAsErrors>True</TreatWarningsAsErrors> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| @@ -1,4 +1,4 @@ | |||||
| using System; | |||||
| using System; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -12,7 +12,7 @@ namespace Discord.Commands | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets on which pipeline stage the command may have matched or failed. | /// Gets on which pipeline stage the command may have matched or failed. | ||||
| /// </summary> | /// </summary> | ||||
| public IResult? Pipeline { get; } | |||||
| public IResult Pipeline { get; } | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public CommandError? Error { get; } | public CommandError? Error { get; } | ||||
| @@ -21,7 +21,7 @@ namespace Discord.Commands | |||||
| /// <inheritdoc /> | /// <inheritdoc /> | ||||
| public bool IsSuccess => !Error.HasValue; | public bool IsSuccess => !Error.HasValue; | ||||
| private MatchResult(CommandMatch? match, IResult? pipeline, CommandError? error, string errorReason) | |||||
| private MatchResult(CommandMatch? match, IResult pipeline, CommandError? error, string errorReason) | |||||
| { | { | ||||
| Match = match; | Match = match; | ||||
| Error = error; | Error = error; | ||||
| @@ -7,6 +7,8 @@ | |||||
| <Description>The core components for the Discord.Net library.</Description> | <Description>The core components for the Discord.Net library.</Description> | ||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <WarningLevel>5</WarningLevel> | |||||
| <TreatWarningsAsErrors>True</TreatWarningsAsErrors> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | ||||
| @@ -132,6 +132,16 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| public const int MaxAuditLogEntriesPerBatch = 100; | public const int MaxAuditLogEntriesPerBatch = 100; | ||||
| /// <summary> | |||||
| /// Returns the max number of stickers that can be sent with a message. | |||||
| /// </summary> | |||||
| public const int MaxStickersPerMessage = 3; | |||||
| /// <summary> | |||||
| /// Returns the max number of embeds that can be sent with a message. | |||||
| /// </summary> | |||||
| public const int MaxEmbedsPerMessage = 10; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets how a request should act in the case of an error, by default. | /// Gets or sets how a request should act in the case of an error, by default. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -152,6 +152,7 @@ namespace Discord | |||||
| InvalidMessageType = 50068, | InvalidMessageType = 50068, | ||||
| PaymentSourceRequiredForGift = 50070, | PaymentSourceRequiredForGift = 50070, | ||||
| CannotDeleteRequiredCommunityChannel = 50074, | CannotDeleteRequiredCommunityChannel = 50074, | ||||
| CannotEditStickersWithinAMessage = 50080, | |||||
| InvalidSticker = 50081, | InvalidSticker = 50081, | ||||
| CannotExecuteOnArchivedThread = 50083, | CannotExecuteOnArchivedThread = 50083, | ||||
| InvalidThreadNotificationSettings = 50084, | InvalidThreadNotificationSettings = 50084, | ||||
| @@ -164,6 +165,7 @@ namespace Discord | |||||
| #endregion | #endregion | ||||
| #region 2FA (60XXX) | #region 2FA (60XXX) | ||||
| MissingPermissionToSendThisSticker = 50600, | |||||
| Requires2FA = 60003, | Requires2FA = 60003, | ||||
| #endregion | #endregion | ||||
| @@ -26,6 +26,8 @@ namespace Discord | |||||
| /// <summary> The channel is a stage voice channel. </summary> | /// <summary> The channel is a stage voice channel. </summary> | ||||
| Stage = 13, | Stage = 13, | ||||
| /// <summary> The channel is a guild directory used in hub servers. (Unreleased)</summary> | /// <summary> The channel is a guild directory used in hub servers. (Unreleased)</summary> | ||||
| GuildDirectory = 14 | |||||
| GuildDirectory = 14, | |||||
| /// <summary> The channel is a forum channel containing multiple threads. </summary> | |||||
| Forum = 15 | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,216 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| public interface IForumChannel : IGuildChannel, IMentionable | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets a value that indicates whether the channel is NSFW. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// <c>true</c> if the channel has the NSFW flag enabled; otherwise <c>false</c>. | |||||
| /// </returns> | |||||
| bool IsNsfw { get; } | |||||
| /// <summary> | |||||
| /// Gets the current topic for this text channel. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A string representing the topic set in the channel; <c>null</c> if none is set. | |||||
| /// </returns> | |||||
| string Topic { get; } | |||||
| /// <summary> | |||||
| /// Gets the default archive duration for a newly created post. | |||||
| /// </summary> | |||||
| ThreadArchiveDuration DefaultAutoArchiveDuration { get; } | |||||
| /// <summary> | |||||
| /// Gets a collection of tags inside of this forum channel. | |||||
| /// </summary> | |||||
| IReadOnlyCollection<ForumTag> Tags { get; } | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the message.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| Task<IThreadChannel> CreatePostAsync(string title, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, int? slowmode = null, | |||||
| string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="filePath">The file path of the file.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich" /> <see cref="Embed" /> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| Task<IThreadChannel> CreatePostWithFileAsync(string title, string filePath, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | |||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | |||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | |||||
| ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="stream">The <see cref="Stream" /> of the file to be sent.</param> | |||||
| /// <param name="filename">The name of the attachment.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="isSpoiler">Whether the message attachment should be hidden as a spoiler.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| public Task<IThreadChannel> CreatePostWithFileAsync(string title, Stream stream, string filename, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | |||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, | |||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, | |||||
| ISticker[] stickers = null, Embed[] embeds = null,MessageFlags flags = MessageFlags.None); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="attachment">The attachment containing the file and description.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| public Task<IThreadChannel> CreatePostWithFileAsync(string title, FileAttachment attachment, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | |||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| /// <summary> | |||||
| /// Creates a new post (thread) within the forum. | |||||
| /// </summary> | |||||
| /// <param name="title">The title of the post.</param> | |||||
| /// <param name="attachments">A collection of attachments to upload.</param> | |||||
| /// <param name="archiveDuration">The archive duration of the post.</param> | |||||
| /// <param name="slowmode">The slowmode for the posts thread.</param> | |||||
| /// <param name="text">The message to be sent.</param> | |||||
| /// <param name="embed">The <see cref="Discord.EmbedType.Rich"/> <see cref="Embed"/> to be sent.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <param name="allowedMentions"> | |||||
| /// Specifies if notifications are sent for mentioned users and roles in the message <paramref name="text"/>. | |||||
| /// If <c>null</c>, all mentioned roles and users will be notified. | |||||
| /// </param> | |||||
| /// <param name="components">The message components to be included with this message. Used for interactions.</param> | |||||
| /// <param name="stickers">A collection of stickers to send with the file.</param> | |||||
| /// <param name="embeds">A array of <see cref="Embed"/>s to send with this response. Max 10.</param> | |||||
| /// <param name="flags">A message flag to be applied to the sent message, only <see cref="MessageFlags.SuppressEmbeds"/> is permitted.</param> | |||||
| /// <returns> | |||||
| /// A task that represents the asynchronous creation operation. | |||||
| /// </returns> | |||||
| public Task<IThreadChannel> CreatePostWithFilesAsync(string title, IEnumerable<FileAttachment> attachments, ThreadArchiveDuration archiveDuration = ThreadArchiveDuration.OneDay, | |||||
| int? slowmode = null, string text = null, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, | |||||
| MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None); | |||||
| /// <summary> | |||||
| /// Gets a collection of active threads within this forum channel. | |||||
| /// </summary> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains | |||||
| /// a collection of active threads. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IThreadChannel>> GetActiveThreadsAsync(RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets a collection of publicly archived threads within this forum channel. | |||||
| /// </summary> | |||||
| /// <param name="limit">The optional limit of how many to get.</param> | |||||
| /// <param name="before">The optional date to return threads created before this timestamp.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains | |||||
| /// a collection of publicly archived threads. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IThreadChannel>> GetPublicArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets a collection of privately archived threads within this forum channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// The bot requires the <see cref="GuildPermission.ManageThreads"/> permission in order to execute this request. | |||||
| /// </remarks> | |||||
| /// <param name="limit">The optional limit of how many to get.</param> | |||||
| /// <param name="before">The optional date to return threads created before this timestamp.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains | |||||
| /// a collection of privately archived threads. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IThreadChannel>> GetPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null); | |||||
| /// <summary> | |||||
| /// Gets a collection of privately archived threads that the current bot has joined within this forum channel. | |||||
| /// </summary> | |||||
| /// <param name="limit">The optional limit of how many to get.</param> | |||||
| /// <param name="before">The optional date to return threads created before this timestamp.</param> | |||||
| /// <param name="options">The options to be used when sending the request.</param> | |||||
| /// <returns> | |||||
| /// A task that represents an asynchronous get operation for retrieving the threads. The task result contains | |||||
| /// a collection of privately archived threads. | |||||
| /// </returns> | |||||
| Task<IReadOnlyCollection<IThreadChannel>> GetJoinedPrivateArchivedThreadsAsync(int? limit = null, DateTimeOffset? before = null, RequestOptions options = null); | |||||
| } | |||||
| } | |||||
| @@ -35,6 +35,17 @@ namespace Discord | |||||
| /// </returns> | /// </returns> | ||||
| int SlowModeInterval { get; } | int SlowModeInterval { get; } | ||||
| /// <summary> | |||||
| /// Gets the default auto-archive duration for client-created threads in this channel. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// The value of this property does not affect API thread creation, it will not respect this value. | |||||
| /// </remarks> | |||||
| /// <returns> | |||||
| /// The default auto-archive duration for thread creation in this channel. | |||||
| /// </returns> | |||||
| ThreadArchiveDuration DefaultArchiveDuration { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Bulk-deletes multiple messages. | /// Bulk-deletes multiple messages. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -6,7 +6,7 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Represents a generic voice channel in a guild. | /// Represents a generic voice channel in a guild. | ||||
| /// </summary> | /// </summary> | ||||
| public interface IVoiceChannel : INestedChannel, IAudioChannel, IMentionable | |||||
| public interface IVoiceChannel : IMessageChannel, INestedChannel, IAudioChannel, IMentionable | |||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the bit-rate that the clients in this voice channel are requested to use. | /// Gets the bit-rate that the clients in this voice channel are requested to use. | ||||
| @@ -0,0 +1,42 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// A struct representing a forum channel tag. | |||||
| /// </summary> | |||||
| public struct ForumTag | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the Id of the tag. | |||||
| /// </summary> | |||||
| public ulong Id { get; } | |||||
| /// <summary> | |||||
| /// Gets the name of the tag. | |||||
| /// </summary> | |||||
| public string Name { get; } | |||||
| /// <summary> | |||||
| /// Gets the emoji of the tag or <see langword="null"/> if none is set. | |||||
| /// </summary> | |||||
| public IEmote Emoji { get; } | |||||
| internal ForumTag(ulong id, string name, ulong? emojiId, string emojiName) | |||||
| { | |||||
| if (emojiId.HasValue && emojiId.Value != 0) | |||||
| Emoji = new Emote(emojiId.Value, emojiName, false); | |||||
| else if (emojiName != null) | |||||
| Emoji = new Emoji(name); | |||||
| else | |||||
| Emoji = null; | |||||
| Id = id; | |||||
| Name = name; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1173,7 +1173,6 @@ namespace Discord | |||||
| /// in order to use this property. | /// in order to use this property. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// </param> | /// </param> | ||||
| /// <param name="speakers">A collection of speakers for the event.</param> | |||||
| /// <param name="location">The location of the event; links are supported</param> | /// <param name="location">The location of the event; links are supported</param> | ||||
| /// <param name="coverImage">The optional banner image for the event.</param> | /// <param name="coverImage">The optional banner image for the event.</param> | ||||
| /// <param name="options">The options to be used when sending the request.</param> | /// <param name="options">The options to be used when sending the request.</param> | ||||
| @@ -89,7 +89,7 @@ namespace Discord | |||||
| /// Gets this events banner image url. | /// Gets this events banner image url. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="format">The format to return.</param> | /// <param name="format">The format to return.</param> | ||||
| /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | |||||
| /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048.</param> | |||||
| /// <returns>The cover images url.</returns> | /// <returns>The cover images url.</returns> | ||||
| string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024); | string GetCoverImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 1024); | ||||
| @@ -56,7 +56,7 @@ namespace Discord | |||||
| Number = 10, | Number = 10, | ||||
| /// <summary> | /// <summary> | ||||
| /// A <see cref="Discord.Attachment"/>. | |||||
| /// A <see cref="IAttachment"/>. | |||||
| /// </summary> | /// </summary> | ||||
| Attachment = 11 | Attachment = 11 | ||||
| } | } | ||||
| @@ -17,6 +17,16 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<bool> IsDefaultPermission { get; set; } | public Optional<bool> IsDefaultPermission { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not this command can be used in DMs. | |||||
| /// </summary> | |||||
| public Optional<bool> IsDMEnabled { get; set; } | |||||
| /// <summary> | |||||
| /// Gets or sets the default permissions required by a user to execute this application command. | |||||
| /// </summary> | |||||
| public Optional<GuildPermission> DefaultMemberPermissions { get; set; } | |||||
| internal ApplicationCommandProperties() { } | internal ApplicationCommandProperties() { } | ||||
| } | } | ||||
| } | } | ||||
| @@ -31,6 +31,16 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public bool IsDefaultPermission { get; set; } = true; | public bool IsDefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsDMEnabled { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets or sets the default permission required to use this slash command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } | |||||
| private string _name; | private string _name; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -44,7 +54,9 @@ namespace Discord | |||||
| var props = new MessageCommandProperties | var props = new MessageCommandProperties | ||||
| { | { | ||||
| Name = Name, | Name = Name, | ||||
| IsDefaultPermission = IsDefaultPermission | |||||
| IsDefaultPermission = IsDefaultPermission, | |||||
| IsDMEnabled = IsDMEnabled, | |||||
| DefaultMemberPermissions = DefaultMemberPermissions ?? Optional<GuildPermission>.Unspecified | |||||
| }; | }; | ||||
| return props; | return props; | ||||
| @@ -73,5 +85,27 @@ namespace Discord | |||||
| IsDefaultPermission = isDefaultPermission; | IsDefaultPermission = isDefaultPermission; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets whether or not this command can be used in dms | |||||
| /// </summary> | |||||
| /// <param name="permission"><see langword="true"/> if the command is available in dms, otherwise <see langword="false"/>.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public MessageCommandBuilder WithDMPermission(bool permission) | |||||
| { | |||||
| IsDMEnabled = permission; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the default member permissions required to use this application command. | |||||
| /// </summary> | |||||
| /// <param name="permissions">The permissions required to use this command.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public MessageCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -31,6 +31,16 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public bool IsDefaultPermission { get; set; } = true; | public bool IsDefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsDMEnabled { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets or sets the default permission required to use this slash command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } | |||||
| private string _name; | private string _name; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -42,7 +52,9 @@ namespace Discord | |||||
| var props = new UserCommandProperties | var props = new UserCommandProperties | ||||
| { | { | ||||
| Name = Name, | Name = Name, | ||||
| IsDefaultPermission = IsDefaultPermission | |||||
| IsDefaultPermission = IsDefaultPermission, | |||||
| IsDMEnabled = IsDMEnabled, | |||||
| DefaultMemberPermissions = DefaultMemberPermissions ?? Optional<GuildPermission>.Unspecified | |||||
| }; | }; | ||||
| return props; | return props; | ||||
| @@ -71,5 +83,27 @@ namespace Discord | |||||
| IsDefaultPermission = isDefaultPermission; | IsDefaultPermission = isDefaultPermission; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets whether or not this command can be used in dms | |||||
| /// </summary> | |||||
| /// <param name="permission"><see langword="true"/> if the command is available in dms, otherwise <see langword="false"/>.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public UserCommandBuilder WithDMPermission(bool permission) | |||||
| { | |||||
| IsDMEnabled = permission; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the default member permissions required to use this application command. | |||||
| /// </summary> | |||||
| /// <param name="permissions">The permissions required to use this command.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public UserCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -34,6 +34,19 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| bool IsDefaultPermission { get; } | bool IsDefaultPermission { get; } | ||||
| /// <summary> | |||||
| /// Indicates whether the command is available in DMs with the app. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Only for globally-scoped commands. | |||||
| /// </remarks> | |||||
| bool IsEnabledInDm { get; } | |||||
| /// <summary> | |||||
| /// Set of default <see cref="GuildPermission"/> required to invoke the command. | |||||
| /// </summary> | |||||
| GuildPermissions DefaultMemberPermissions { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets a collection of options for this application command. | /// Gets a collection of options for this application command. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -52,10 +52,13 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the preferred locale of the invoking User. | /// Gets the preferred locale of the invoking User. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | |||||
| /// This property returns <see langword="null"/> if the interaction is a REST ping interaction. | |||||
| /// </remarks> | |||||
| string UserLocale { get; } | string UserLocale { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the preferred locale of the guild this interaction was executed in. <see cref="null"/> if not executed in a guild. | |||||
| /// Gets the preferred locale of the guild this interaction was executed in. <see langword="null"/> if not executed in a guild. | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Non-community guilds (With no locale setting available) will have en-US as the default value sent by Discord. | /// Non-community guilds (With no locale setting available) will have en-US as the default value sent by Discord. | ||||
| @@ -67,6 +70,27 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| bool IsDMInteraction { get; } | bool IsDMInteraction { get; } | ||||
| /// <summary> | |||||
| /// Gets the ID of the channel this interaction was executed in. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property returns <see langword="null"/> if the interaction is a REST ping interaction. | |||||
| /// </remarks> | |||||
| ulong? ChannelId { get; } | |||||
| /// <summary> | |||||
| /// Gets the ID of the guild this interaction was executed in. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// This property returns <see langword="null"/> if the interaction was not executed in a guild. | |||||
| /// </remarks> | |||||
| ulong? GuildId { get; } | |||||
| /// <summary> | |||||
| /// Gets the ID of the application this interaction is for. | |||||
| /// </summary> | |||||
| ulong ApplicationId { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>. | /// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -195,7 +195,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="button">The button to add.</param> | /// <param name="button">The button to add.</param> | ||||
| /// <param name="row">The row to add the button.</param> | /// <param name="row">The row to add the button.</param> | ||||
| /// <exception cref="InvalidOperationException">There is no more row to add a menu.</exception> | |||||
| /// <exception cref="InvalidOperationException">There is no more row to add a button.</exception> | |||||
| /// <exception cref="ArgumentException"><paramref name="row"/> must be less than <see cref="MaxActionRowCount"/>.</exception> | /// <exception cref="ArgumentException"><paramref name="row"/> must be less than <see cref="MaxActionRowCount"/>.</exception> | ||||
| /// <returns>The current builder.</returns> | /// <returns>The current builder.</returns> | ||||
| public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) | public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) | ||||
| @@ -348,6 +348,100 @@ namespace Discord | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Adds a <see cref="SelectMenuBuilder"/> to the <see cref="ActionRowBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="customId">The custom id of the menu.</param> | |||||
| /// <param name="options">The options of the menu.</param> | |||||
| /// <param name="placeholder">The placeholder of the menu.</param> | |||||
| /// <param name="minValues">The min values of the placeholder.</param> | |||||
| /// <param name="maxValues">The max values of the placeholder.</param> | |||||
| /// <param name="disabled">Whether or not the menu is disabled.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options, | |||||
| string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false) | |||||
| { | |||||
| return WithSelectMenu(new SelectMenuBuilder() | |||||
| .WithCustomId(customId) | |||||
| .WithOptions(options) | |||||
| .WithPlaceholder(placeholder) | |||||
| .WithMaxValues(maxValues) | |||||
| .WithMinValues(minValues) | |||||
| .WithDisabled(disabled)); | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a <see cref="SelectMenuBuilder"/> to the <see cref="ActionRowBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="menu">The menu to add.</param> | |||||
| /// <exception cref="InvalidOperationException">A Select Menu cannot exist in a pre-occupied ActionRow.</exception> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu) | |||||
| { | |||||
| if (menu.Options.Distinct().Count() != menu.Options.Count) | |||||
| throw new InvalidOperationException("Please make sure that there is no duplicates values."); | |||||
| var builtMenu = menu.Build(); | |||||
| if (Components.Count != 0) | |||||
| throw new InvalidOperationException($"A Select Menu cannot exist in a pre-occupied ActionRow."); | |||||
| AddComponent(builtMenu); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a <see cref="ButtonBuilder"/> with specified parameters to the <see cref="ActionRowBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="label">The label text for the newly added button.</param> | |||||
| /// <param name="style">The style of this newly added button.</param> | |||||
| /// <param name="emote">A <see cref="IEmote"/> to be used with this button.</param> | |||||
| /// <param name="customId">The custom id of the newly added button.</param> | |||||
| /// <param name="url">A URL to be used only if the <see cref="ButtonStyle"/> is a Link.</param> | |||||
| /// <param name="disabled">Whether or not the newly created button is disabled.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithButton( | |||||
| string label = null, | |||||
| string customId = null, | |||||
| ButtonStyle style = ButtonStyle.Primary, | |||||
| IEmote emote = null, | |||||
| string url = null, | |||||
| bool disabled = false) | |||||
| { | |||||
| var button = new ButtonBuilder() | |||||
| .WithLabel(label) | |||||
| .WithStyle(style) | |||||
| .WithEmote(emote) | |||||
| .WithCustomId(customId) | |||||
| .WithUrl(url) | |||||
| .WithDisabled(disabled); | |||||
| return WithButton(button); | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a <see cref="ButtonBuilder"/> to the <see cref="ActionRowBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="button">The button to add.</param> | |||||
| /// <exception cref="InvalidOperationException">Components count reached <see cref="MaxChildCount"/>.</exception> | |||||
| /// <exception cref="InvalidOperationException">A button cannot be added to a row with a SelectMenu.</exception> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithButton(ButtonBuilder button) | |||||
| { | |||||
| var builtButton = button.Build(); | |||||
| if(Components.Count >= 5) | |||||
| throw new InvalidOperationException($"Components count reached {MaxChildCount}"); | |||||
| if (Components.Any(x => x.Type == ComponentType.SelectMenu)) | |||||
| throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu"); | |||||
| AddComponent(builtButton); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> | /// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1194,9 +1288,9 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the default value of the text input. | /// Gets or sets the default value of the text input. | ||||
| /// </summary> | /// </summary> | ||||
| /// <exception cref="ArgumentOutOfRangeException"><see cref="Value.Length"/> is less than 0.</exception> | |||||
| /// <exception cref="ArgumentOutOfRangeException"><see cref="Value"/>.Length is less than 0.</exception> | |||||
| /// <exception cref="ArgumentOutOfRangeException"> | /// <exception cref="ArgumentOutOfRangeException"> | ||||
| /// <see cref="Value.Length"/> is greater than <see cref="LargestMaxLength"/> or <see cref="MaxLength"/>. | |||||
| /// <see cref="Value"/>.Length is greater than <see cref="LargestMaxLength"/> or <see cref="MaxLength"/>. | |||||
| /// </exception> | /// </exception> | ||||
| public string Value | public string Value | ||||
| { | { | ||||
| @@ -1227,7 +1321,7 @@ namespace Discord | |||||
| /// <param name="minLength">The text input's minimum length.</param> | /// <param name="minLength">The text input's minimum length.</param> | ||||
| /// <param name="maxLength">The text input's maximum length.</param> | /// <param name="maxLength">The text input's maximum length.</param> | ||||
| /// <param name="required">The text input's required value.</param> | /// <param name="required">The text input's required value.</param> | ||||
| public TextInputBuilder (string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null, | |||||
| public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null, | |||||
| int? minLength = null, int? maxLength = null, bool? required = null, string value = null) | int? minLength = null, int? maxLength = null, bool? required = null, string value = null) | ||||
| { | { | ||||
| Label = label; | Label = label; | ||||
| @@ -1291,7 +1385,7 @@ namespace Discord | |||||
| Placeholder = placeholder; | Placeholder = placeholder; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the value of the current builder. | /// Sets the value of the current builder. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -1306,18 +1400,18 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets the minimum length of the current builder. | /// Sets the minimum length of the current builder. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="placeholder">The value to set.</param> | |||||
| /// <param name="minLength">The value to set.</param> | |||||
| /// <returns>The current builder. </returns> | /// <returns>The current builder. </returns> | ||||
| public TextInputBuilder WithMinLength(int minLength) | public TextInputBuilder WithMinLength(int minLength) | ||||
| { | { | ||||
| MinLength = minLength; | MinLength = minLength; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the maximum length of the current builder. | /// Sets the maximum length of the current builder. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="placeholder">The value to set.</param> | |||||
| /// <param name="maxLength">The value to set.</param> | |||||
| /// <returns>The current builder. </returns> | /// <returns>The current builder. </returns> | ||||
| public TextInputBuilder WithMaxLength(int maxLength) | public TextInputBuilder WithMaxLength(int maxLength) | ||||
| { | { | ||||
| @@ -64,18 +64,18 @@ namespace Discord | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets the custom id of the current modal. | /// Sets the custom id of the current modal. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="title">The value to set the custom id to.</param> | |||||
| /// <param name="customId">The value to set the custom id to.</param> | |||||
| /// <returns>The current builder.</returns> | /// <returns>The current builder.</returns> | ||||
| public ModalBuilder WithCustomId(string customId) | public ModalBuilder WithCustomId(string customId) | ||||
| { | { | ||||
| CustomId = customId; | CustomId = customId; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds a component to the current builder. | /// Adds a component to the current builder. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="title">The component to add.</param> | |||||
| /// <param name="component">The component to add.</param> | |||||
| /// <returns>The current builder.</returns> | /// <returns>The current builder.</returns> | ||||
| public ModalBuilder AddTextInput(TextInputBuilder component) | public ModalBuilder AddTextInput(TextInputBuilder component) | ||||
| { | { | ||||
| @@ -213,7 +213,7 @@ namespace Discord | |||||
| /// Adds a <see cref="TextInputBuilder"/> to the <see cref="ModalComponentBuilder"/> at the specific row. | /// Adds a <see cref="TextInputBuilder"/> to the <see cref="ModalComponentBuilder"/> at the specific row. | ||||
| /// If the row cannot accept the component then it will add it to a row that can. | /// If the row cannot accept the component then it will add it to a row that can. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="text">The <see cref="TextInputBuilder"> to add.</param> | |||||
| /// <param name="text">The <see cref="TextInputBuilder"/> to add.</param> | |||||
| /// <param name="row">The row to add the text input.</param> | /// <param name="row">The row to add the text input.</param> | ||||
| /// <exception cref="InvalidOperationException">There are no more rows to add a text input to.</exception> | /// <exception cref="InvalidOperationException">There are no more rows to add a text input to.</exception> | ||||
| /// <exception cref="ArgumentException"><paramref name="row"/> must be less than <see cref="MaxActionRowCount"/>.</exception> | /// <exception cref="ArgumentException"><paramref name="row"/> must be less than <see cref="MaxActionRowCount"/>.</exception> | ||||
| @@ -81,6 +81,16 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public bool IsDefaultPermission { get; set; } = true; | public bool IsDefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets or sets whether or not this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsDMEnabled { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets or sets the default permission required to use this slash command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } | |||||
| private string _name; | private string _name; | ||||
| private string _description; | private string _description; | ||||
| private List<SlashCommandOptionBuilder> _options; | private List<SlashCommandOptionBuilder> _options; | ||||
| @@ -96,6 +106,8 @@ namespace Discord | |||||
| Name = Name, | Name = Name, | ||||
| Description = Description, | Description = Description, | ||||
| IsDefaultPermission = IsDefaultPermission, | IsDefaultPermission = IsDefaultPermission, | ||||
| IsDMEnabled = IsDMEnabled, | |||||
| DefaultMemberPermissions = DefaultMemberPermissions ?? Optional<GuildPermission>.Unspecified | |||||
| }; | }; | ||||
| if (Options != null && Options.Any()) | if (Options != null && Options.Any()) | ||||
| @@ -145,6 +157,28 @@ namespace Discord | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets whether or not this command can be used in dms | |||||
| /// </summary> | |||||
| /// <param name="permission"><see langword="true"/> if the command is available in dms, otherwise <see langword="false"/>.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder WithDMPermission(bool permission) | |||||
| { | |||||
| IsDMEnabled = permission; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets the default member permissions required to use this application command. | |||||
| /// </summary> | |||||
| /// <param name="permissions">The permissions required to use this command.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds an option to the current slash command. | /// Adds an option to the current slash command. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -164,21 +198,13 @@ namespace Discord | |||||
| string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, | string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, | ||||
| List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) | List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) | ||||
| { | { | ||||
| // Make sure the name matches the requirements from discord | |||||
| Preconditions.NotNullOrEmpty(name, nameof(name)); | |||||
| Preconditions.AtLeast(name.Length, 1, nameof(name)); | |||||
| Preconditions.AtMost(name.Length, MaxNameLength, nameof(name)); | |||||
| Preconditions.Options(name, description); | |||||
| // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, | // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, | ||||
| // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | ||||
| if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) | if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) | ||||
| throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); | throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); | ||||
| // same with description | |||||
| Preconditions.NotNullOrEmpty(description, nameof(description)); | |||||
| Preconditions.AtLeast(description.Length, 1, nameof(description)); | |||||
| Preconditions.AtMost(description.Length, MaxDescriptionLength, nameof(description)); | |||||
| // make sure theres only one option with default set to true | // make sure theres only one option with default set to true | ||||
| if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true) | if (isDefault == true && Options?.Any(x => x.IsDefault == true) == true) | ||||
| throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); | throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); | ||||
| @@ -214,6 +240,7 @@ namespace Discord | |||||
| throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!"); | throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!"); | ||||
| Preconditions.NotNull(option, nameof(option)); | Preconditions.NotNull(option, nameof(option)); | ||||
| Preconditions.Options(option.Name, option.Description); // this is a double-check when this method is called via AddOption(string name... ) | |||||
| Options.Add(option); | Options.Add(option); | ||||
| return this; | return this; | ||||
| @@ -228,14 +255,14 @@ namespace Discord | |||||
| if (options == null) | if (options == null) | ||||
| throw new ArgumentNullException(nameof(options), "Options cannot be null!"); | throw new ArgumentNullException(nameof(options), "Options cannot be null!"); | ||||
| if (options.Length == 0) | |||||
| throw new ArgumentException("Options cannot be empty!", nameof(options)); | |||||
| Options ??= new List<SlashCommandOptionBuilder>(); | Options ??= new List<SlashCommandOptionBuilder>(); | ||||
| if (Options.Count + options.Length > MaxOptionsCount) | if (Options.Count + options.Length > MaxOptionsCount) | ||||
| throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!"); | throw new ArgumentOutOfRangeException(nameof(options), $"Cannot have more than {MaxOptionsCount} options!"); | ||||
| foreach (var option in options) | |||||
| Preconditions.Options(option.Name, option.Description); | |||||
| Options.AddRange(options); | Options.AddRange(options); | ||||
| return this; | return this; | ||||
| } | } | ||||
| @@ -400,21 +427,13 @@ namespace Discord | |||||
| string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, | string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, | ||||
| List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) | List<SlashCommandOptionBuilder> options = null, List<ChannelType> channelTypes = null, params ApplicationCommandOptionChoiceProperties[] choices) | ||||
| { | { | ||||
| // Make sure the name matches the requirements from discord | |||||
| Preconditions.NotNullOrEmpty(name, nameof(name)); | |||||
| Preconditions.AtLeast(name.Length, 1, nameof(name)); | |||||
| Preconditions.AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name)); | |||||
| Preconditions.Options(name, description); | |||||
| // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, | // Discord updated the docs, this regex prevents special characters like @!$%( and s p a c e s.. etc, | ||||
| // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand | ||||
| if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) | if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) | ||||
| throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); | throw new ArgumentException("Command name cannot contain any special characters or whitespaces!", nameof(name)); | ||||
| // same with description | |||||
| Preconditions.NotNullOrEmpty(description, nameof(description)); | |||||
| Preconditions.AtLeast(description.Length, 1, nameof(description)); | |||||
| Preconditions.AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description)); | |||||
| // make sure theres only one option with default set to true | // make sure theres only one option with default set to true | ||||
| if (isDefault && Options?.Any(x => x.IsDefault == true) == true) | if (isDefault && Options?.Any(x => x.IsDefault == true) == true) | ||||
| throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); | throw new ArgumentException("There can only be one command option with default set to true!", nameof(isDefault)); | ||||
| @@ -449,11 +468,32 @@ namespace Discord | |||||
| throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); | throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); | ||||
| Preconditions.NotNull(option, nameof(option)); | Preconditions.NotNull(option, nameof(option)); | ||||
| Preconditions.Options(option.Name, option.Description); // double check again | |||||
| Options.Add(option); | Options.Add(option); | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Adds a collection of options to the current option. | |||||
| /// </summary> | |||||
| /// <param name="options">The collection of options to add.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public SlashCommandOptionBuilder AddOptions(params SlashCommandOptionBuilder[] options) | |||||
| { | |||||
| if (options == null) | |||||
| throw new ArgumentNullException(nameof(options), "Options cannot be null!"); | |||||
| if ((Options?.Count ?? 0) + options.Length > SlashCommandBuilder.MaxOptionsCount) | |||||
| throw new ArgumentOutOfRangeException(nameof(options), $"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); | |||||
| foreach (var option in options) | |||||
| Preconditions.Options(option.Name, option.Description); | |||||
| Options.AddRange(options); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds a choice to the current option. | /// Adds a choice to the current option. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -617,7 +657,7 @@ namespace Discord | |||||
| MinValue = value; | MinValue = value; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Sets the current builders max value field. | /// Sets the current builders max value field. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -3,6 +3,7 @@ using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| using Discord.Utils; | using Discord.Utils; | ||||
| using Newtonsoft.Json; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -155,6 +156,55 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Tries to parse a string into an <see cref="EmbedBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="json">The json string to parse.</param> | |||||
| /// <param name="builder">The <see cref="EmbedBuilder"/> with populated values. An empty instance if method returns <see langword="false"/>.</param> | |||||
| /// <returns><see langword="true"/> if <paramref name="json"/> was succesfully parsed. <see langword="false"/> if not.</returns> | |||||
| public static bool TryParse(string json, out EmbedBuilder builder) | |||||
| { | |||||
| builder = new EmbedBuilder(); | |||||
| try | |||||
| { | |||||
| var model = JsonConvert.DeserializeObject<Embed>(json); | |||||
| if (model is not null) | |||||
| { | |||||
| builder = model.ToEmbedBuilder(); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| catch | |||||
| { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Parses a string into an <see cref="EmbedBuilder"/>. | |||||
| /// </summary> | |||||
| /// <param name="json">The json string to parse.</param> | |||||
| /// <returns>An <see cref="EmbedBuilder"/> with populated values from the passed <paramref name="json"/>.</returns> | |||||
| /// <exception cref="InvalidOperationException">Thrown if the string passed is not valid json.</exception> | |||||
| public static EmbedBuilder Parse(string json) | |||||
| { | |||||
| try | |||||
| { | |||||
| var model = JsonConvert.DeserializeObject<Embed>(json); | |||||
| if (model is not null) | |||||
| return model.ToEmbedBuilder(); | |||||
| return new EmbedBuilder(); | |||||
| } | |||||
| catch | |||||
| { | |||||
| throw; | |||||
| } | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets the title of an <see cref="Embed"/>. | /// Sets the title of an <see cref="Embed"/>. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -27,6 +27,12 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| public Optional<ulong> GuildId { get; internal set; } | public Optional<ulong> GuildId { get; internal set; } | ||||
| /// <summary> | |||||
| /// Gets whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message | |||||
| /// Defaults to true. | |||||
| /// </summary> | |||||
| public Optional<bool> FailIfNotExists { get; internal set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a new instance of the <see cref="MessageReference"/> class. | /// Initializes a new instance of the <see cref="MessageReference"/> class. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -39,16 +45,21 @@ namespace Discord | |||||
| /// <param name="guildId"> | /// <param name="guildId"> | ||||
| /// The ID of the guild that will be referenced. It will be validated if sent. | /// The ID of the guild that will be referenced. It will be validated if sent. | ||||
| /// </param> | /// </param> | ||||
| public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null) | |||||
| /// <param name="failIfNotExists"> | |||||
| /// Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message. Defaults to true. | |||||
| /// </param> | |||||
| public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null, bool? failIfNotExists = null) | |||||
| { | { | ||||
| MessageId = messageId ?? Optional.Create<ulong>(); | MessageId = messageId ?? Optional.Create<ulong>(); | ||||
| InternalChannelId = channelId ?? Optional.Create<ulong>(); | InternalChannelId = channelId ?? Optional.Create<ulong>(); | ||||
| GuildId = guildId ?? Optional.Create<ulong>(); | GuildId = guildId ?? Optional.Create<ulong>(); | ||||
| FailIfNotExists = failIfNotExists ?? Optional.Create<bool>(); | |||||
| } | } | ||||
| private string DebuggerDisplay | private string DebuggerDisplay | ||||
| => $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" + | => $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" + | ||||
| $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}"; | |||||
| $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}" + | |||||
| $"{(FailIfNotExists.IsSpecified ? $", FailIfNotExists: ({FailIfNotExists.Value})" : "")}"; | |||||
| public override string ToString() | public override string ToString() | ||||
| => DebuggerDisplay; | => DebuggerDisplay; | ||||
| @@ -7,30 +7,55 @@ namespace Discord | |||||
| [DebuggerDisplay("{DebuggerDisplay,nq}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public struct ChannelPermissions | public struct ChannelPermissions | ||||
| { | { | ||||
| /// <summary> Gets a blank <see cref="ChannelPermissions"/> that grants no permissions.</summary> | |||||
| /// <returns> A <see cref="ChannelPermissions"/> structure that does not contain any set permissions.</returns> | |||||
| public static readonly ChannelPermissions None = new ChannelPermissions(); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for text channels.</summary> | |||||
| public static readonly ChannelPermissions Text = new ChannelPermissions(0b0_11111_0101100_0000000_1111111110001_010001); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels.</summary> | |||||
| public static readonly ChannelPermissions Voice = new ChannelPermissions(0b1_00000_0000100_1111110_0000000011100_010001); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for stage channels.</summary> | |||||
| public static readonly ChannelPermissions Stage = new ChannelPermissions(0b0_00000_1000100_0111010_0000000010000_010001); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for category channels.</summary> | |||||
| public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for direct message channels.</summary> | |||||
| public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110001_000000); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels.</summary> | |||||
| public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); | |||||
| /// <summary> Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type.</summary> | |||||
| /// <summary> | |||||
| /// Gets a blank <see cref="ChannelPermissions"/> that grants no permissions. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A <see cref="ChannelPermissions"/> structure that does not contain any set permissions. | |||||
| /// </returns> | |||||
| public static readonly ChannelPermissions None = new(); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for text channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions Text = new(0b0_11111_0101100_0000000_1111111110001_010001); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for voice channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions Voice = new(0b1_11111_0101100_1111110_1111111111101_010001); // (0b1_00000_0000100_1111110_0000000011100_010001 (<- voice only perms) |= Text) | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for stage channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions Stage = new(0b0_00000_1000100_0111010_0000000010000_010001); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for category channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions Category = new(0b01100_1111110_1111111110001_010001); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for direct message channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions DM = new(0b00000_1000110_1011100110001_000000); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for group channels. | |||||
| /// </summary> | |||||
| public static readonly ChannelPermissions Group = new(0b00000_1000110_0001101100000_000000); | |||||
| /// <summary> | |||||
| /// Gets a <see cref="ChannelPermissions"/> that grants all permissions for a given channel type. | |||||
| /// </summary> | |||||
| /// <exception cref="ArgumentException">Unknown channel type.</exception> | /// <exception cref="ArgumentException">Unknown channel type.</exception> | ||||
| public static ChannelPermissions All(IChannel channel) | public static ChannelPermissions All(IChannel channel) | ||||
| { | { | ||||
| return channel switch | return channel switch | ||||
| { | { | ||||
| ITextChannel _ => Text, | |||||
| IStageChannel _ => Stage, | IStageChannel _ => Stage, | ||||
| IVoiceChannel _ => Voice, | IVoiceChannel _ => Voice, | ||||
| ITextChannel _ => Text, | |||||
| ICategoryChannel _ => Category, | ICategoryChannel _ => Category, | ||||
| IDMChannel _ => DM, | IDMChannel _ => DM, | ||||
| IGroupChannel _ => Group, | IGroupChannel _ => Group, | ||||
| @@ -79,7 +79,7 @@ namespace Discord | |||||
| /// Sets a timestamp how long a user should be timed out for. | /// Sets a timestamp how long a user should be timed out for. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// <see cref="null"/> or a time in the past to clear a currently existing timeout. | |||||
| /// <see langword="null"/> or a time in the past to clear a currently existing timeout. | |||||
| /// </remarks> | /// </remarks> | ||||
| public Optional<DateTimeOffset?> TimedOutUntil { get; set; } | public Optional<DateTimeOffset?> TimedOutUntil { get; set; } | ||||
| } | } | ||||
| @@ -104,7 +104,7 @@ namespace Discord | |||||
| /// Gets the date and time that indicates if and for how long a user has been timed out. | /// Gets the date and time that indicates if and for how long a user has been timed out. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// <see cref="null"/> or a timestamp in the past if the user is not timed out. | |||||
| /// <see langword="null"/> or a timestamp in the past if the user is not timed out. | |||||
| /// </remarks> | /// </remarks> | ||||
| /// <returns> | /// <returns> | ||||
| /// A <see cref="DateTimeOffset"/> indicating how long the user will be timed out for. | /// A <see cref="DateTimeOffset"/> indicating how long the user will be timed out for. | ||||
| @@ -116,7 +116,7 @@ namespace Discord | |||||
| /// </summary> | /// </summary> | ||||
| /// <example> | /// <example> | ||||
| /// <para>The following example checks if the current user has the ability to send a message with attachment in | /// <para>The following example checks if the current user has the ability to send a message with attachment in | ||||
| /// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference)"/>.</para> | |||||
| /// this channel; if so, uploads a file via <see cref="IMessageChannel.SendFileAsync(string, string, bool, Embed, RequestOptions, bool, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/>.</para> | |||||
| /// <code language="cs"> | /// <code language="cs"> | ||||
| /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) | /// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles) | ||||
| /// await targetChannel.SendFileAsync("fortnite.png"); | /// await targetChannel.SendFileAsync("fortnite.png"); | ||||
| @@ -151,7 +151,7 @@ namespace Discord | |||||
| /// If the user does not have a guild avatar, this will be the user's regular avatar. | /// If the user does not have a guild avatar, this will be the user's regular avatar. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="format">The format to return.</param> | /// <param name="format">The format to return.</param> | ||||
| /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048. | |||||
| /// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// A string representing the URL of the displayed avatar for this user. <see langword="null"/> if the user does not have an avatar in place. | /// A string representing the URL of the displayed avatar for this user. <see langword="null"/> if the user does not have an avatar in place. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -37,8 +37,9 @@ namespace Discord | |||||
| /// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | /// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | ||||
| public static string Sanitize(string text) | public static string Sanitize(string text) | ||||
| { | { | ||||
| foreach (string unsafeChar in SensitiveCharacters) | |||||
| text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | |||||
| if (text != null) | |||||
| foreach (string unsafeChar in SensitiveCharacters) | |||||
| text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | |||||
| return text; | return text; | ||||
| } | } | ||||
| @@ -0,0 +1,24 @@ | |||||
| using System.Collections.Generic; | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents a container for temporarily storing CustomId wild card matches of a component. | |||||
| /// </summary> | |||||
| public interface IRouteMatchContainer | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the collection of captured route segments in this container. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// A collection of captured route segments. | |||||
| ///</returns> | |||||
| IEnumerable<IRouteSegmentMatch> SegmentMatches { get; } | |||||
| /// <summary> | |||||
| /// Sets the <see cref="SegmentMatches"/> property of this container. | |||||
| /// </summary> | |||||
| /// <param name="segmentMatches">The collection of captured route segments.</param> | |||||
| void SetSegmentMatches(IEnumerable<IRouteSegmentMatch> segmentMatches); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents an object for storing a CustomId wild card match. | |||||
| /// </summary> | |||||
| public interface IRouteSegmentMatch | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the captured value of this wild card match. | |||||
| /// </summary> | |||||
| /// <returns> | |||||
| /// The value of this wild card. | |||||
| /// </returns> | |||||
| string Value { get; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| namespace Discord | |||||
| { | |||||
| /// <summary> | |||||
| /// Represents an object for storing a CustomId wild card match. | |||||
| /// </summary> | |||||
| internal record RouteSegmentMatch : IRouteSegmentMatch | |||||
| { | |||||
| /// <inheritdoc/> | |||||
| public string Value { get; } | |||||
| public RouteSegmentMatch(string value) | |||||
| { | |||||
| Value = value; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -297,5 +297,22 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| #endregion | #endregion | ||||
| #region SlashCommandOptions | |||||
| /// <exception cref="ArgumentNullException"><paramref name="description"/> or <paramref name="name"/> is null.</exception> | |||||
| /// <exception cref="ArgumentException"><paramref name="description"/> or <paramref name="name"/> are either empty or their length exceed limits.</exception> | |||||
| public static void Options(string name, string description) | |||||
| { | |||||
| // Make sure the name matches the requirements from discord | |||||
| NotNullOrEmpty(name, nameof(name)); | |||||
| NotNullOrEmpty(description, nameof(description)); | |||||
| AtLeast(name.Length, 1, nameof(name)); | |||||
| AtMost(name.Length, SlashCommandBuilder.MaxNameLength, nameof(name)); | |||||
| AtLeast(description.Length, 1, nameof(description)); | |||||
| AtMost(description.Length, SlashCommandBuilder.MaxDescriptionLength, nameof(description)); | |||||
| } | |||||
| #endregion | |||||
| } | } | ||||
| } | } | ||||
| @@ -23,7 +23,7 @@ namespace Discord.Utils | |||||
| /// <summary> | /// <summary> | ||||
| /// Not full URL validation right now. Just Ensures the protocol is either http, https, or discord | /// Not full URL validation right now. Just Ensures the protocol is either http, https, or discord | ||||
| /// <see cref="Validate(string)"/> should be used everything other than url buttons. | |||||
| /// <see cref="Validate(string, bool)"/> should be used everything other than url buttons. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="url">The URL to validate before sending to discord.</param> | /// <param name="url">The URL to validate before sending to discord.</param> | ||||
| /// <exception cref="InvalidOperationException">A URL must include a protocol (either http, https, or discord).</exception> | /// <exception cref="InvalidOperationException">A URL must include a protocol (either http, https, or discord).</exception> | ||||
| @@ -1,7 +1,7 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <TargetFramework>net5.0</TargetFramework> | |||||
| <TargetFramework>net6.0</TargetFramework> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -3,7 +3,7 @@ using System; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Set the <see cref="ApplicationCommandOptionProperties.Autocomplete"/> to <see langword="true"/>. | |||||
| /// Set the <see cref="ApplicationCommandOptionProperties.IsAutocomplete"/> to <see langword="true"/>. | |||||
| /// </summary> | /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] | ||||
| public class AutocompleteAttribute : Attribute | public class AutocompleteAttribute : Attribute | ||||
| @@ -14,7 +14,7 @@ namespace Discord.Interactions | |||||
| public Type AutocompleteHandlerType { get; } | public Type AutocompleteHandlerType { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Set the <see cref="ApplicationCommandOptionProperties.Autocomplete"/> to <see langword="true"/> and define a <see cref="AutocompleteHandler"/> to handle | |||||
| /// Set the <see cref="ApplicationCommandOptionProperties.IsAutocomplete"/> to <see langword="true"/> and define a <see cref="AutocompleteHandler"/> to handle | |||||
| /// Autocomplete interactions targeting the parameter this <see cref="Attribute"/> is applied to. | /// Autocomplete interactions targeting the parameter this <see cref="Attribute"/> is applied to. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| @@ -29,7 +29,7 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Set the <see cref="ApplicationCommandOptionProperties.Autocomplete"/> to <see langword="true"/> without specifying a <see cref="AutocompleteHandler"/>. | |||||
| /// Set the <see cref="ApplicationCommandOptionProperties.IsAutocomplete"/> to <see langword="true"/> without specifying a <see cref="AutocompleteHandler"/>. | |||||
| /// </summary> | /// </summary> | ||||
| public AutocompleteAttribute() { } | public AutocompleteAttribute() { } | ||||
| } | } | ||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| /// <summary> | |||||
| /// Sets the <see cref="IApplicationCommandInfo.DefaultMemberPermissions"/> of an application command or module. | |||||
| /// </summary> | |||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||||
| public class DefaultMemberPermissionsAttribute : Attribute | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the default permission required to use this command. | |||||
| /// </summary> | |||||
| public GuildPermission Permissions { get; } | |||||
| /// <summary> | |||||
| /// Sets the <see cref="IApplicationCommandInfo.DefaultMemberPermissions"/> of an application command or module. | |||||
| /// </summary> | |||||
| /// <param name="permissions">The default permission required to use this command.</param> | |||||
| public DefaultMemberPermissionsAttribute(GuildPermission permissions) | |||||
| { | |||||
| Permissions = permissions; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -6,6 +6,7 @@ namespace Discord.Interactions | |||||
| /// Set the "Default Permission" property of an Application Command. | /// Set the "Default Permission" property of an Application Command. | ||||
| /// </summary> | /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | ||||
| [Obsolete($"Soon to be deprecated, use Permissions-v2 attributes like {nameof(EnabledInDmAttribute)} and {nameof(DefaultMemberPermissionsAttribute)}")] | |||||
| public class DefaultPermissionAttribute : Attribute | public class DefaultPermissionAttribute : Attribute | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| /// <summary> | |||||
| /// Sets the <see cref="IApplicationCommandInfo.IsEnabledInDm"/> property of an application command or module. | |||||
| /// </summary> | |||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | |||||
| public class EnabledInDmAttribute : Attribute | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets whether or not this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabled { get; } | |||||
| /// <summary> | |||||
| /// Sets the <see cref="IApplicationCommandInfo.IsEnabledInDm"/> property of an application command or module. | |||||
| /// </summary> | |||||
| /// <param name="isEnabled">Whether or not this command can be used in DMs.</param> | |||||
| public EnabledInDmAttribute(bool isEnabled) | |||||
| { | |||||
| IsEnabled = isEnabled; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -21,9 +21,7 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Create a new <see cref="ModalInputAttribute"/>. | /// Create a new <see cref="ModalInputAttribute"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="label">The label of the input.</param> | |||||
| /// <param name="customId">The custom id of the input.</param> | /// <param name="customId">The custom id of the input.</param> | ||||
| /// <param name="required">Whether the user is required to input a value.></param> | |||||
| protected ModalInputAttribute(string customId) | protected ModalInputAttribute(string customId) | ||||
| { | { | ||||
| CustomId = customId; | CustomId = customId; | ||||
| @@ -36,7 +36,7 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Create a new <see cref="ModalTextInputAttribute"/>. | /// Create a new <see cref="ModalTextInputAttribute"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="customId"The custom id of the text input.></param> | |||||
| /// <param name="customId">The custom id of the text input.></param> | |||||
| /// <param name="style">The style of the text input.</param> | /// <param name="style">The style of the text input.</param> | ||||
| /// <param name="placeholder">The placeholder of the text input.</param> | /// <param name="placeholder">The placeholder of the text input.</param> | ||||
| /// <param name="minLength">The minimum length of the text input's content.</param> | /// <param name="minLength">The minimum length of the text input's content.</param> | ||||
| @@ -29,7 +29,7 @@ namespace Discord.Interactions | |||||
| /// <remarks> | /// <remarks> | ||||
| /// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>. | /// This precondition will always fail if the command is being invoked in a <see cref="IPrivateChannel"/>. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="permission"> | |||||
| /// <param name="guildPermission"> | |||||
| /// The <see cref="Discord.GuildPermission" /> that the user must have. Multiple permissions can be | /// The <see cref="Discord.GuildPermission" /> that the user must have. Multiple permissions can be | ||||
| /// specified by ORing the permissions together. | /// specified by ORing the permissions together. | ||||
| /// </param> | /// </param> | ||||
| @@ -41,7 +41,7 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Requires that the user invoking the command to have a specific <see cref="Discord.ChannelPermission"/>. | /// Requires that the user invoking the command to have a specific <see cref="Discord.ChannelPermission"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="permission"> | |||||
| /// <param name="channelPermission"> | |||||
| /// The <see cref="Discord.ChannelPermission"/> that the user must have. Multiple permissions can be | /// The <see cref="Discord.ChannelPermission"/> that the user must have. Multiple permissions can be | ||||
| /// specified by ORing the permissions together. | /// specified by ORing the permissions together. | ||||
| /// </param> | /// </param> | ||||
| @@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default permission of this command. | /// Gets the default permission of this command. | ||||
| /// </summary> | /// </summary> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] | |||||
| public bool DefaultPermission { get; set; } = true; | public bool DefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets whether this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabledInDm { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets the default permissions needed for executing this command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } = null; | |||||
| internal ContextCommandBuilder (ModuleBuilder module) : base(module) { } | internal ContextCommandBuilder (ModuleBuilder module) : base(module) { } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -49,6 +60,7 @@ namespace Discord.Interactions.Builders | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] | |||||
| public ContextCommandBuilder SetDefaultPermission (bool defaultPermision) | public ContextCommandBuilder SetDefaultPermission (bool defaultPermision) | ||||
| { | { | ||||
| DefaultPermission = defaultPermision; | DefaultPermission = defaultPermision; | ||||
| @@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets <see cref="IsEnabledInDm"/>. | |||||
| /// </summary> | |||||
| /// <param name="isEnabled">New value of the <see cref="IsEnabledInDm"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public ContextCommandBuilder SetEnabledInDm(bool isEnabled) | |||||
| { | |||||
| IsEnabledInDm = isEnabled; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets <see cref="DefaultMemberPermissions"/>. | |||||
| /// </summary> | |||||
| /// <param name="permissions">New value of the <see cref="DefaultMemberPermissions"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public ContextCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| internal override ContextCommandInfo Build (ModuleInfo module, InteractionService commandService) => | internal override ContextCommandInfo Build (ModuleInfo module, InteractionService commandService) => | ||||
| ContextCommandInfo.Create(this, module, commandService); | ContextCommandInfo.Create(this, module, commandService); | ||||
| } | } | ||||
| @@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets and sets the default permission of this command. | /// Gets and sets the default permission of this command. | ||||
| /// </summary> | /// </summary> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] | |||||
| public bool DefaultPermission { get; set; } = true; | public bool DefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets whether this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabledInDm { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets the default permissions needed for executing this command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } = null; | |||||
| internal SlashCommandBuilder (ModuleBuilder module) : base(module) { } | internal SlashCommandBuilder (ModuleBuilder module) : base(module) { } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -45,10 +56,11 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets <see cref="DefaultPermission"/>. | /// Sets <see cref="DefaultPermission"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="defaultPermision">New value of the <see cref="DefaultPermission"/>.</param> | |||||
| /// <param name="permission">New value of the <see cref="DefaultPermission"/>.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] | |||||
| public SlashCommandBuilder WithDefaultPermission (bool permission) | public SlashCommandBuilder WithDefaultPermission (bool permission) | ||||
| { | { | ||||
| DefaultPermission = permission; | DefaultPermission = permission; | ||||
| @@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets <see cref="IsEnabledInDm"/>. | |||||
| /// </summary> | |||||
| /// <param name="isEnabled">New value of the <see cref="IsEnabledInDm"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public SlashCommandBuilder SetEnabledInDm(bool isEnabled) | |||||
| { | |||||
| IsEnabledInDm = isEnabled; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets <see cref="DefaultMemberPermissions"/>. | |||||
| /// </summary> | |||||
| /// <param name="permissions">New value of the <see cref="DefaultMemberPermissions"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| internal override SlashCommandInfo Build (ModuleInfo module, InteractionService commandService) => | internal override SlashCommandInfo Build (ModuleInfo module, InteractionService commandService) => | ||||
| new SlashCommandInfo(this, module, commandService); | new SlashCommandInfo(this, module, commandService); | ||||
| } | } | ||||
| @@ -41,7 +41,7 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets <see cref="Style"/>. | /// Sets <see cref="Style"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="style">New value of the <see cref="SetValue(string)"/>.</param> | |||||
| /// <param name="style">New value of the <see cref="Style"/>.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -64,7 +64,7 @@ namespace Discord.Interactions.Builders | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Adds text components to <see cref="TextComponents"/>. | |||||
| /// Adds text components to <see cref="Components"/>. | |||||
| /// </summary> | /// </summary> | ||||
| /// <param name="configure">Text Component builder factory.</param> | /// <param name="configure">Text Component builder factory.</param> | ||||
| /// <returns> | /// <returns> | ||||
| @@ -51,8 +51,19 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets and sets the default permission of this module. | /// Gets and sets the default permission of this module. | ||||
| /// </summary> | /// </summary> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] | |||||
| public bool DefaultPermission { get; set; } = true; | public bool DefaultPermission { get; set; } = true; | ||||
| /// <summary> | |||||
| /// Gets whether this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabledInDm { get; set; } = true; | |||||
| /// <summary> | |||||
| /// Gets the default permissions needed for executing this command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; set; } = null; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets and sets whether this has a <see cref="DontAutoRegisterAttribute"/>. | /// Gets and sets whether this has a <see cref="DontAutoRegisterAttribute"/>. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -159,12 +170,39 @@ namespace Discord.Interactions.Builders | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] | |||||
| public ModuleBuilder WithDefaultPermission (bool permission) | public ModuleBuilder WithDefaultPermission (bool permission) | ||||
| { | { | ||||
| DefaultPermission = permission; | DefaultPermission = permission; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets <see cref="IsEnabledInDm"/>. | |||||
| /// </summary> | |||||
| /// <param name="isEnabled">New value of the <see cref="IsEnabledInDm"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public ModuleBuilder SetEnabledInDm(bool isEnabled) | |||||
| { | |||||
| IsEnabledInDm = isEnabled; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Sets <see cref="DefaultMemberPermissions"/>. | |||||
| /// </summary> | |||||
| /// <param name="permissions">New value of the <see cref="DefaultMemberPermissions"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| public ModuleBuilder WithDefaultMemberPermissions(GuildPermission permissions) | |||||
| { | |||||
| DefaultMemberPermissions = permissions; | |||||
| return this; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds attributes to <see cref="Attributes"/>. | /// Adds attributes to <see cref="Attributes"/>. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -319,7 +357,8 @@ namespace Discord.Interactions.Builders | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Adds a modal command builder to <see cref="ModalCommands"/>. | /// Adds a modal command builder to <see cref="ModalCommands"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="configure"><see cref="ModalCommands"/> factory.</param> | /// <param name="configure"><see cref="ModalCommands"/> factory.</param> | ||||
| @@ -85,6 +85,16 @@ namespace Discord.Interactions.Builders | |||||
| builder.DefaultPermission = defPermission.IsDefaultPermission; | builder.DefaultPermission = defPermission.IsDefaultPermission; | ||||
| } | } | ||||
| break; | break; | ||||
| case EnabledInDmAttribute enabledInDm: | |||||
| { | |||||
| builder.IsEnabledInDm = enabledInDm.IsEnabled; | |||||
| } | |||||
| break; | |||||
| case DefaultMemberPermissionsAttribute memberPermission: | |||||
| { | |||||
| builder.DefaultMemberPermissions = memberPermission.Permissions; | |||||
| } | |||||
| break; | |||||
| case PreconditionAttribute precondition: | case PreconditionAttribute precondition: | ||||
| builder.AddPreconditions(precondition); | builder.AddPreconditions(precondition); | ||||
| break; | break; | ||||
| @@ -169,6 +179,16 @@ namespace Discord.Interactions.Builders | |||||
| builder.DefaultPermission = defaultPermission.IsDefaultPermission; | builder.DefaultPermission = defaultPermission.IsDefaultPermission; | ||||
| } | } | ||||
| break; | break; | ||||
| case EnabledInDmAttribute enabledInDm: | |||||
| { | |||||
| builder.IsEnabledInDm = enabledInDm.IsEnabled; | |||||
| } | |||||
| break; | |||||
| case DefaultMemberPermissionsAttribute memberPermission: | |||||
| { | |||||
| builder.DefaultMemberPermissions = memberPermission.Permissions; | |||||
| } | |||||
| break; | |||||
| case PreconditionAttribute precondition: | case PreconditionAttribute precondition: | ||||
| builder.WithPreconditions(precondition); | builder.WithPreconditions(precondition); | ||||
| break; | break; | ||||
| @@ -211,6 +231,16 @@ namespace Discord.Interactions.Builders | |||||
| builder.DefaultPermission = defaultPermission.IsDefaultPermission; | builder.DefaultPermission = defaultPermission.IsDefaultPermission; | ||||
| } | } | ||||
| break; | break; | ||||
| case EnabledInDmAttribute enabledInDm: | |||||
| { | |||||
| builder.IsEnabledInDm = enabledInDm.IsEnabled; | |||||
| } | |||||
| break; | |||||
| case DefaultMemberPermissionsAttribute memberPermission: | |||||
| { | |||||
| builder.DefaultMemberPermissions = memberPermission.Permissions; | |||||
| } | |||||
| break; | |||||
| case PreconditionAttribute precondition: | case PreconditionAttribute precondition: | ||||
| builder.WithPreconditions(precondition); | builder.WithPreconditions(precondition); | ||||
| break; | break; | ||||
| @@ -122,7 +122,7 @@ namespace Discord.Interactions.Builders | |||||
| /// <summary> | /// <summary> | ||||
| /// Adds preconditions to <see cref="Preconditions"/> | /// Adds preconditions to <see cref="Preconditions"/> | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="preconditions">New attributes to be added to <see cref="Preconditions"/>.</param> | |||||
| /// <param name="attributes">New attributes to be added to <see cref="Preconditions"/>.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| @@ -7,8 +7,10 @@ | |||||
| <RootNamespace>Discord.Interactions</RootNamespace> | <RootNamespace>Discord.Interactions</RootNamespace> | ||||
| <AssemblyName>Discord.Net.Interactions</AssemblyName> | <AssemblyName>Discord.Net.Interactions</AssemblyName> | ||||
| <Description>A Discord.Net extension adding support for Application Commands.</Description> | <Description>A Discord.Net extension adding support for Application Commands.</Description> | ||||
| <WarningLevel>5</WarningLevel> | |||||
| <TreatWarningsAsErrors>True</TreatWarningsAsErrors> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| <ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | <ProjectReference Include="..\Discord.Net.Rest\Discord.Net.Rest.csproj" /> | ||||
| @@ -17,6 +17,12 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public bool DefaultPermission { get; } | public bool DefaultPermission { get; } | ||||
| /// <inheritdoc/> | |||||
| public bool IsEnabledInDm { get; } | |||||
| /// <inheritdoc/> | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | public override IReadOnlyCollection<CommandParameterInfo> Parameters { get; } | ||||
| @@ -31,6 +37,8 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| CommandType = builder.CommandType; | CommandType = builder.CommandType; | ||||
| DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
| IsEnabledInDm = builder.IsEnabledInDm; | |||||
| DefaultMemberPermissions = builder.DefaultMemberPermissions; | |||||
| Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | ||||
| } | } | ||||
| @@ -26,6 +26,12 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public bool DefaultPermission { get; } | public bool DefaultPermission { get; } | ||||
| /// <inheritdoc/> | |||||
| public bool IsEnabledInDm { get; } | |||||
| /// <inheritdoc/> | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; } | public override IReadOnlyCollection<SlashCommandParameterInfo> Parameters { get; } | ||||
| @@ -41,6 +47,8 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| Description = builder.Description; | Description = builder.Description; | ||||
| DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
| IsEnabledInDm = builder.IsEnabledInDm; | |||||
| DefaultMemberPermissions = builder.DefaultMemberPermissions; | |||||
| Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | ||||
| FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); | FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); | ||||
| @@ -1,3 +1,5 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| @@ -18,6 +20,17 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the DefaultPermission of this command. | /// Gets the DefaultPermission of this command. | ||||
| /// </summary> | /// </summary> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] | |||||
| bool DefaultPermission { get; } | bool DefaultPermission { get; } | ||||
| /// <summary> | |||||
| /// Gets whether this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabledInDm { get; } | |||||
| /// <summary> | |||||
| /// Gets the default permissions needed for executing this command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -41,8 +41,19 @@ namespace Discord.Interactions | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default Permission of this module. | /// Gets the default Permission of this module. | ||||
| /// </summary> | /// </summary> | ||||
| [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] | |||||
| public bool DefaultPermission { get; } | public bool DefaultPermission { get; } | ||||
| /// <summary> | |||||
| /// Gets whether this command can be used in DMs. | |||||
| /// </summary> | |||||
| public bool IsEnabledInDm { get; } | |||||
| /// <summary> | |||||
| /// Gets the default permissions needed for executing this command. | |||||
| /// </summary> | |||||
| public GuildPermission? DefaultMemberPermissions { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the collection of Sub Modules of this module. | /// Gets the collection of Sub Modules of this module. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -110,6 +121,8 @@ namespace Discord.Interactions | |||||
| Description = builder.Description; | Description = builder.Description; | ||||
| Parent = parent; | Parent = parent; | ||||
| DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
| IsEnabledInDm = builder.IsEnabledInDm; | |||||
| DefaultMemberPermissions = BuildDefaultMemberPermissions(builder); | |||||
| SlashCommands = BuildSlashCommands(builder).ToImmutableArray(); | SlashCommands = BuildSlashCommands(builder).ToImmutableArray(); | ||||
| ContextCommands = BuildContextCommands(builder).ToImmutableArray(); | ContextCommands = BuildContextCommands(builder).ToImmutableArray(); | ||||
| ComponentCommands = BuildComponentCommands(builder).ToImmutableArray(); | ComponentCommands = BuildComponentCommands(builder).ToImmutableArray(); | ||||
| @@ -226,5 +239,20 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| private static GuildPermission? BuildDefaultMemberPermissions(ModuleBuilder builder) | |||||
| { | |||||
| var permissions = builder.DefaultMemberPermissions; | |||||
| var parent = builder.Parent; | |||||
| while (parent != null) | |||||
| { | |||||
| permissions = (permissions ?? 0) | (parent.DefaultMemberPermissions ?? 0).SanitizeGuildPermissions(); | |||||
| parent = parent.Parent; | |||||
| } | |||||
| return permissions; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,10 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| /// <inheritdoc cref="IInteractionContext"/> | /// <inheritdoc cref="IInteractionContext"/> | ||||
| public class InteractionContext : IInteractionContext | |||||
| public class InteractionContext : IInteractionContext, IRouteMatchContainer | |||||
| { | { | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public IDiscordClient Client { get; } | public IDiscordClient Client { get; } | ||||
| @@ -13,14 +16,15 @@ namespace Discord.Interactions | |||||
| public IUser User { get; } | public IUser User { get; } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public IDiscordInteraction Interaction { get; } | public IDiscordInteraction Interaction { get; } | ||||
| /// <inheritdoc cref="IRouteMatchContainer.SegmentMatches"/> | |||||
| public IReadOnlyCollection<IRouteSegmentMatch> SegmentMatches { get; private set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Initializes a new <see cref="SocketInteractionContext{TInteraction}"/>. | /// Initializes a new <see cref="SocketInteractionContext{TInteraction}"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="client">The underlying client.</param> | /// <param name="client">The underlying client.</param> | ||||
| /// <param name="interaction">The underlying interaction.</param> | /// <param name="interaction">The underlying interaction.</param> | ||||
| /// <param name="user"><see cref="IUser"/> who executed the command.</param> | |||||
| /// <param name="channel"><see cref="ISocketMessageChannel"/> the command originated from.</param> | |||||
| /// <param name="channel"><see cref="IMessageChannel"/> the command originated from.</param> | |||||
| public InteractionContext(IDiscordClient client, IDiscordInteraction interaction, IMessageChannel channel = null) | public InteractionContext(IDiscordClient client, IDiscordInteraction interaction, IMessageChannel channel = null) | ||||
| { | { | ||||
| Client = client; | Client = client; | ||||
| @@ -30,5 +34,12 @@ namespace Discord.Interactions | |||||
| User = interaction.User; | User = interaction.User; | ||||
| Interaction = interaction; | Interaction = interaction; | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public void SetSegmentMatches(IEnumerable<IRouteSegmentMatch> segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray(); | |||||
| //IRouteMatchContainer | |||||
| /// <inheritdoc/> | |||||
| IEnumerable<IRouteSegmentMatch> IRouteMatchContainer.SegmentMatches => SegmentMatches; | |||||
| } | } | ||||
| } | } | ||||
| @@ -45,7 +45,7 @@ namespace Discord.Interactions | |||||
| protected virtual async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) => | protected virtual async Task DeferAsync(bool ephemeral = false, RequestOptions options = null) => | ||||
| await Context.Interaction.DeferAsync(ephemeral, options).ConfigureAwait(false); | await Context.Interaction.DeferAsync(ephemeral, options).ConfigureAwait(false); | ||||
| /// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | |||||
| /// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
| protected virtual async Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | protected virtual async Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | ||||
| AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | ||||
| await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | await Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
| @@ -70,7 +70,7 @@ namespace Discord.Interactions | |||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | ||||
| => Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | => Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | ||||
| /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, RequestOptions, MessageComponent, Embed)"/> | |||||
| /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions)"/> | |||||
| protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | protected virtual async Task<IUserMessage> FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, | ||||
| AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null) => | ||||
| await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | await Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options).ConfigureAwait(false); | ||||
| @@ -95,7 +95,7 @@ namespace Discord.Interactions | |||||
| AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null) | ||||
| => Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | => Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options); | ||||
| /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[])"/> | |||||
| /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags)"/> | |||||
| protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | protected virtual async Task<IUserMessage> ReplyAsync (string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, | ||||
| AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null) => | ||||
| await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | await Context.Channel.SendMessageAsync(text, false, embed, options, allowedMentions, messageReference, components).ConfigureAwait(false); | ||||
| @@ -118,9 +118,9 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc cref="IDiscordInteraction.RespondWithModalAsync(Modal, RequestOptions)"/> | /// <inheritdoc cref="IDiscordInteraction.RespondWithModalAsync(Modal, RequestOptions)"/> | ||||
| protected virtual async Task RespondWithModalAsync(Modal modal, RequestOptions options = null) => await Context.Interaction.RespondWithModalAsync(modal); | protected virtual async Task RespondWithModalAsync(Modal modal, RequestOptions options = null) => await Context.Interaction.RespondWithModalAsync(modal); | ||||
| /// <inheritdoc cref="IDiscordInteractionExtentions.RespondWithModalAsync(IDiscordInteraction, IModal, RequestOptions)"/> | |||||
| protected virtual async Task RespondWithModalAsync<T>(string customId, RequestOptions options = null) where T : class, IModal | |||||
| => await Context.Interaction.RespondWithModalAsync<T>(customId, options); | |||||
| /// <inheritdoc cref="IDiscordInteractionExtentions.RespondWithModalAsync{T}(IDiscordInteraction, string, RequestOptions, Action{ModalBuilder})"/> | |||||
| protected virtual async Task RespondWithModalAsync<TModal>(string customId, RequestOptions options = null) where TModal : class, IModal | |||||
| => await Context.Interaction.RespondWithModalAsync<TModal>(customId, options); | |||||
| //IInteractionModuleBase | //IInteractionModuleBase | ||||
| @@ -223,7 +223,8 @@ namespace Discord.Interactions | |||||
| new ConcurrentDictionary<Type, Type> | new ConcurrentDictionary<Type, Type> | ||||
| { | { | ||||
| [typeof(Array)] = typeof(DefaultArrayComponentConverter<>), | [typeof(Array)] = typeof(DefaultArrayComponentConverter<>), | ||||
| [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>) | |||||
| [typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>), | |||||
| [typeof(Nullable<>)] = typeof(NullableComponentConverter<>) | |||||
| }); | }); | ||||
| _typeReaderMap = new TypeMap<TypeReader, string>(this, new ConcurrentDictionary<Type, TypeReader>(), | _typeReaderMap = new TypeMap<TypeReader, string>(this, new ConcurrentDictionary<Type, TypeReader>(), | ||||
| @@ -234,7 +235,8 @@ namespace Discord.Interactions | |||||
| [typeof(IUser)] = typeof(DefaultUserReader<>), | [typeof(IUser)] = typeof(DefaultUserReader<>), | ||||
| [typeof(IMessage)] = typeof(DefaultMessageReader<>), | [typeof(IMessage)] = typeof(DefaultMessageReader<>), | ||||
| [typeof(IConvertible)] = typeof(DefaultValueReader<>), | [typeof(IConvertible)] = typeof(DefaultValueReader<>), | ||||
| [typeof(Enum)] = typeof(EnumReader<>) | |||||
| [typeof(Enum)] = typeof(EnumReader<>), | |||||
| [typeof(Nullable<>)] = typeof(NullableReader<>) | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -421,20 +423,39 @@ namespace Discord.Interactions | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Commands will be registered as standalone commands, if you want the <see cref="GroupAttribute"/> to take effect, | /// Commands will be registered as standalone commands, if you want the <see cref="GroupAttribute"/> to take effect, | ||||
| /// use <see cref="AddModulesToGuildAsync(IGuild, ModuleInfo[])"/>. Registering a commands without group names might cause the command traversal to fail. | |||||
| /// use <see cref="AddModulesToGuildAsync(IGuild, bool, ModuleInfo[])"/>. Registering a commands without group names might cause the command traversal to fail. | |||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="guild">The target guild.</param> | /// <param name="guild">The target guild.</param> | ||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="commands">Commands to be registered to Discord.</param> | /// <param name="commands">Commands to be registered to Discord.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | /// A task representing the command registration process. The task result contains the active application commands of the target guild. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<IReadOnlyCollection<RestGuildCommand>> AddCommandsToGuildAsync(IGuild guild, bool deleteMissing = false, params ICommandInfo[] commands) | public async Task<IReadOnlyCollection<RestGuildCommand>> AddCommandsToGuildAsync(IGuild guild, bool deleteMissing = false, params ICommandInfo[] commands) | ||||
| { | { | ||||
| EnsureClientReady(); | |||||
| if (guild is null) | if (guild is null) | ||||
| throw new ArgumentNullException(nameof(guild)); | throw new ArgumentNullException(nameof(guild)); | ||||
| return await AddCommandsToGuildAsync(guild.Id, deleteMissing, commands).ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> | |||||
| /// Register Application Commands from <paramref name="commands"/> to a guild. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// Commands will be registered as standalone commands, if you want the <see cref="GroupAttribute"/> to take effect, | |||||
| /// use <see cref="AddModulesToGuildAsync(ulong, bool, ModuleInfo[])"/>. Registering a commands without group names might cause the command traversal to fail. | |||||
| /// </remarks> | |||||
| /// <param name="guildId">The target guild ID.</param> | |||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="commands">Commands to be registered to Discord.</param> | |||||
| /// <returns> | |||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | |||||
| /// </returns> | |||||
| public async Task<IReadOnlyCollection<RestGuildCommand>> AddCommandsToGuildAsync(ulong guildId, bool deleteMissing = false, params ICommandInfo[] commands) | |||||
| { | |||||
| EnsureClientReady(); | |||||
| var props = new List<ApplicationCommandProperties>(); | var props = new List<ApplicationCommandProperties>(); | ||||
| foreach (var command in commands) | foreach (var command in commands) | ||||
| @@ -454,44 +475,60 @@ namespace Discord.Interactions | |||||
| if (!deleteMissing) | if (!deleteMissing) | ||||
| { | { | ||||
| var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); | |||||
| var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false); | |||||
| var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); | var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); | ||||
| props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); | props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); | ||||
| } | } | ||||
| return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false); | |||||
| return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Register Application Commands from modules provided in <paramref name="modules"/> to a guild. | /// Register Application Commands from modules provided in <paramref name="modules"/> to a guild. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="guild">The target guild.</param> | /// <param name="guild">The target guild.</param> | ||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="modules">Modules to be registered to Discord.</param> | /// <param name="modules">Modules to be registered to Discord.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | /// A task representing the command registration process. The task result contains the active application commands of the target guild. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<IReadOnlyCollection<RestGuildCommand>> AddModulesToGuildAsync(IGuild guild, bool deleteMissing = false, params ModuleInfo[] modules) | public async Task<IReadOnlyCollection<RestGuildCommand>> AddModulesToGuildAsync(IGuild guild, bool deleteMissing = false, params ModuleInfo[] modules) | ||||
| { | { | ||||
| EnsureClientReady(); | |||||
| if (guild is null) | if (guild is null) | ||||
| throw new ArgumentNullException(nameof(guild)); | throw new ArgumentNullException(nameof(guild)); | ||||
| return await AddModulesToGuildAsync(guild.Id, deleteMissing, modules).ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> | |||||
| /// Register Application Commands from modules provided in <paramref name="modules"/> to a guild. | |||||
| /// </summary> | |||||
| /// <param name="guildId">The target guild ID.</param> | |||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="modules">Modules to be registered to Discord.</param> | |||||
| /// <returns> | |||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | |||||
| /// </returns> | |||||
| public async Task<IReadOnlyCollection<RestGuildCommand>> AddModulesToGuildAsync(ulong guildId, bool deleteMissing = false, params ModuleInfo[] modules) | |||||
| { | |||||
| EnsureClientReady(); | |||||
| var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList(); | var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList(); | ||||
| if (!deleteMissing) | if (!deleteMissing) | ||||
| { | { | ||||
| var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); | |||||
| var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false); | |||||
| var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); | var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); | ||||
| props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); | props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); | ||||
| } | } | ||||
| return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false); | |||||
| return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| /// Register Application Commands from modules provided in <paramref name="modules"/> as global commands. | /// Register Application Commands from modules provided in <paramref name="modules"/> as global commands. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="modules">Modules to be registered to Discord.</param> | /// <param name="modules">Modules to be registered to Discord.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | /// A task representing the command registration process. The task result contains the active application commands of the target guild. | ||||
| @@ -517,8 +554,9 @@ namespace Discord.Interactions | |||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Commands will be registered as standalone commands, if you want the <see cref="GroupAttribute"/> to take effect, | /// Commands will be registered as standalone commands, if you want the <see cref="GroupAttribute"/> to take effect, | ||||
| /// use <see cref="AddModulesToGuildAsync(IGuild, ModuleInfo[])"/>. Registering a commands without group names might cause the command traversal to fail. | |||||
| /// use <see cref="AddModulesToGuildAsync(IGuild, bool, ModuleInfo[])"/>. Registering a commands without group names might cause the command traversal to fail. | |||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="deleteMissing">If <see langword="false"/>, this operation will not delete the commands that are missing from <see cref="InteractionService"/>.</param> | |||||
| /// <param name="commands">Commands to be registered to Discord.</param> | /// <param name="commands">Commands to be registered to Discord.</param> | ||||
| /// <returns> | /// <returns> | ||||
| /// A task representing the command registration process. The task result contains the active application commands of the target guild. | /// A task representing the command registration process. The task result contains the active application commands of the target guild. | ||||
| @@ -775,6 +813,9 @@ namespace Discord.Interactions | |||||
| await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); | await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); | ||||
| return result; | return result; | ||||
| } | } | ||||
| SetMatchesIfApplicable(context, result); | |||||
| return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -819,9 +860,30 @@ namespace Discord.Interactions | |||||
| await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); | await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); | ||||
| return result; | return result; | ||||
| } | } | ||||
| SetMatchesIfApplicable(context, result); | |||||
| return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); | ||||
| } | } | ||||
| private static void SetMatchesIfApplicable<T>(IInteractionContext context, SearchResult<T> searchResult) | |||||
| where T : class, ICommandInfo | |||||
| { | |||||
| if (!searchResult.Command.SupportsWildCards || context is not IRouteMatchContainer matchContainer) | |||||
| return; | |||||
| if (searchResult.RegexCaptureGroups?.Length > 0) | |||||
| { | |||||
| var matches = new RouteSegmentMatch[searchResult.RegexCaptureGroups.Length]; | |||||
| for (var i = 0; i < searchResult.RegexCaptureGroups.Length; i++) | |||||
| matches[i] = new RouteSegmentMatch(searchResult.RegexCaptureGroups[i]); | |||||
| matchContainer.SetSegmentMatches(matches); | |||||
| } | |||||
| else | |||||
| matchContainer.SetSegmentMatches(Array.Empty<RouteSegmentMatch>()); | |||||
| } | |||||
| internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null) | internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null) | ||||
| => _typeConverterMap.Get(type, services); | => _typeConverterMap.Get(type, services); | ||||
| @@ -941,7 +1003,7 @@ namespace Discord.Interactions | |||||
| /// Removes a type reader for the given type. | /// Removes a type reader for the given type. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="CommandService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="InteractionService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// You need to reload the modules for the changes to take effect. | /// You need to reload the modules for the changes to take effect. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="type">The type to remove the reader from.</param> | /// <param name="type">The type to remove the reader from.</param> | ||||
| @@ -954,7 +1016,7 @@ namespace Discord.Interactions | |||||
| /// Removes a generic type reader from the type <typeparamref name="T"/>. | /// Removes a generic type reader from the type <typeparamref name="T"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="CommandService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="InteractionService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// You need to reload the modules for the changes to take effect. | /// You need to reload the modules for the changes to take effect. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <typeparam name="T">The type to remove the readers from.</typeparam> | /// <typeparam name="T">The type to remove the readers from.</typeparam> | ||||
| @@ -967,7 +1029,7 @@ namespace Discord.Interactions | |||||
| /// Removes a generic type reader from the given type. | /// Removes a generic type reader from the given type. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="CommandService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="InteractionService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// You need to reload the modules for the changes to take effect. | /// You need to reload the modules for the changes to take effect. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <param name="type">The type to remove the reader from.</param> | /// <param name="type">The type to remove the reader from.</param> | ||||
| @@ -980,7 +1042,7 @@ namespace Discord.Interactions | |||||
| /// Serialize an object using a <see cref="TypeReader"/> into a <see cref="string"/> to be placed in a Component CustomId. | /// Serialize an object using a <see cref="TypeReader"/> into a <see cref="string"/> to be placed in a Component CustomId. | ||||
| /// </summary> | /// </summary> | ||||
| /// <remarks> | /// <remarks> | ||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="CommandService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// Removing a <see cref="TypeReader"/> from the <see cref="InteractionService"/> will not dereference the <see cref="TypeReader"/> from the loaded module/command instances. | |||||
| /// You need to reload the modules for the changes to take effect. | /// You need to reload the modules for the changes to take effect. | ||||
| /// </remarks> | /// </remarks> | ||||
| /// <typeparam name="T">Type of the object to be serialized.</typeparam> | /// <typeparam name="T">Type of the object to be serialized.</typeparam> | ||||
| @@ -1060,19 +1122,40 @@ namespace Discord.Interactions | |||||
| /// <returns> | /// <returns> | ||||
| /// The active command permissions after the modification. | /// The active command permissions after the modification. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync (ModuleInfo module, IGuild guild, | |||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync(ModuleInfo module, IGuild guild, | |||||
| params ApplicationCommandPermission[] permissions) | params ApplicationCommandPermission[] permissions) | ||||
| { | { | ||||
| if (module is null) | |||||
| throw new ArgumentNullException(nameof(module)); | |||||
| if (guild is null) | |||||
| throw new ArgumentNullException(nameof(guild)); | |||||
| return await ModifySlashCommandPermissionsAsync(module, guild.Id, permissions).ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> | |||||
| /// Modify the command permissions of the matching Discord Slash Command. | |||||
| /// </summary> | |||||
| /// <param name="module">Module representing the top level Slash Command.</param> | |||||
| /// <param name="guildId">Target guild ID.</param> | |||||
| /// <param name="permissions">New permission values.</param> | |||||
| /// <returns> | |||||
| /// The active command permissions after the modification. | |||||
| /// </returns> | |||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync(ModuleInfo module, ulong guildId, | |||||
| params ApplicationCommandPermission[] permissions) | |||||
| { | |||||
| if (module is null) | |||||
| throw new ArgumentNullException(nameof(module)); | |||||
| if (!module.IsSlashGroup) | if (!module.IsSlashGroup) | ||||
| throw new InvalidOperationException($"This module does not have a {nameof(GroupAttribute)} and does not represent an Application Command"); | throw new InvalidOperationException($"This module does not have a {nameof(GroupAttribute)} and does not represent an Application Command"); | ||||
| if (!module.IsTopLevelGroup) | if (!module.IsTopLevelGroup) | ||||
| throw new InvalidOperationException("This module is not a top level application command. You cannot change its permissions"); | throw new InvalidOperationException("This module is not a top level application command. You cannot change its permissions"); | ||||
| if (guild is null) | |||||
| throw new ArgumentNullException("guild"); | |||||
| var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); | |||||
| var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false); | |||||
| var appCommand = commands.First(x => x.Name == module.SlashGroupName); | var appCommand = commands.First(x => x.Name == module.SlashGroupName); | ||||
| return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); | return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); | ||||
| @@ -1087,9 +1170,29 @@ namespace Discord.Interactions | |||||
| /// <returns> | /// <returns> | ||||
| /// The active command permissions after the modification. | /// The active command permissions after the modification. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync (SlashCommandInfo command, IGuild guild, | |||||
| params ApplicationCommandPermission[] permissions) => | |||||
| await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false); | |||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync(SlashCommandInfo command, IGuild guild, | |||||
| params ApplicationCommandPermission[] permissions) | |||||
| { | |||||
| if (command is null) | |||||
| throw new ArgumentNullException(nameof(command)); | |||||
| if (guild is null) | |||||
| throw new ArgumentNullException(nameof(guild)); | |||||
| return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> | |||||
| /// Modify the command permissions of the matching Discord Slash Command. | |||||
| /// </summary> | |||||
| /// <param name="command">The Slash Command.</param> | |||||
| /// <param name="guildId">Target guild ID.</param> | |||||
| /// <param name="permissions">New permission values.</param> | |||||
| /// <returns> | |||||
| /// The active command permissions after the modification. | |||||
| /// </returns> | |||||
| public async Task<GuildApplicationCommandPermission> ModifySlashCommandPermissionsAsync(SlashCommandInfo command, ulong guildId, | |||||
| params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false); | |||||
| /// <summary> | /// <summary> | ||||
| /// Modify the command permissions of the matching Discord Slash Command. | /// Modify the command permissions of the matching Discord Slash Command. | ||||
| @@ -1100,20 +1203,40 @@ namespace Discord.Interactions | |||||
| /// <returns> | /// <returns> | ||||
| /// The active command permissions after the modification. | /// The active command permissions after the modification. | ||||
| /// </returns> | /// </returns> | ||||
| public async Task<GuildApplicationCommandPermission> ModifyContextCommandPermissionsAsync (ContextCommandInfo command, IGuild guild, | |||||
| params ApplicationCommandPermission[] permissions) => | |||||
| await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false); | |||||
| public async Task<GuildApplicationCommandPermission> ModifyContextCommandPermissionsAsync(ContextCommandInfo command, IGuild guild, | |||||
| params ApplicationCommandPermission[] permissions) | |||||
| { | |||||
| if (command is null) | |||||
| throw new ArgumentNullException(nameof(command)); | |||||
| private async Task<GuildApplicationCommandPermission> ModifyApplicationCommandPermissionsAsync<T> (T command, IGuild guild, | |||||
| if (guild is null) | |||||
| throw new ArgumentNullException(nameof(guild)); | |||||
| return await ModifyApplicationCommandPermissionsAsync(command, guild.Id, permissions).ConfigureAwait(false); | |||||
| } | |||||
| /// <summary> | |||||
| /// Modify the command permissions of the matching Discord Slash Command. | |||||
| /// </summary> | |||||
| /// <param name="command">The Context Command.</param> | |||||
| /// <param name="guildId">Target guild ID.</param> | |||||
| /// <param name="permissions">New permission values.</param> | |||||
| /// <returns> | |||||
| /// The active command permissions after the modification. | |||||
| /// </returns> | |||||
| public async Task<GuildApplicationCommandPermission> ModifyContextCommandPermissionsAsync(ContextCommandInfo command, ulong guildId, | |||||
| params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guildId, permissions).ConfigureAwait(false); | |||||
| private async Task<GuildApplicationCommandPermission> ModifyApplicationCommandPermissionsAsync<T> (T command, ulong guildId, | |||||
| params ApplicationCommandPermission[] permissions) where T : class, IApplicationCommandInfo, ICommandInfo | params ApplicationCommandPermission[] permissions) where T : class, IApplicationCommandInfo, ICommandInfo | ||||
| { | { | ||||
| if (command is null) | |||||
| throw new ArgumentNullException(nameof(command)); | |||||
| if (!command.IsTopLevelCommand) | if (!command.IsTopLevelCommand) | ||||
| throw new InvalidOperationException("This command is not a top level application command. You cannot change its permissions"); | throw new InvalidOperationException("This command is not a top level application command. You cannot change its permissions"); | ||||
| if (guild is null) | |||||
| throw new ArgumentNullException("guild"); | |||||
| var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); | |||||
| var commands = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false); | |||||
| var appCommand = commands.First(x => x.Name == ( command as IApplicationCommandInfo ).Name); | var appCommand = commands.First(x => x.Name == ( command as IApplicationCommandInfo ).Name); | ||||
| return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); | return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); | ||||
| @@ -87,12 +87,12 @@ namespace Discord.Interactions | |||||
| await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false); | await InteractionService._restResponseCallback(Context, payload).ConfigureAwait(false); | ||||
| } | } | ||||
| protected override async Task RespondWithModalAsync<T>(string customId, RequestOptions options = null) | |||||
| protected override async Task RespondWithModalAsync<TModal>(string customId, RequestOptions options = null) | |||||
| { | { | ||||
| if (Context.Interaction is not RestInteraction restInteraction) | if (Context.Interaction is not RestInteraction restInteraction) | ||||
| throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method"); | throw new InvalidOperationException($"Invalid interaction type. Interaction must be a type of {nameof(RestInteraction)} in order to execute this method"); | ||||
| var payload = restInteraction.RespondWithModal<T>(customId, options); | |||||
| var payload = restInteraction.RespondWithModal<TModal>(customId, options); | |||||
| if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null) | if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null) | ||||
| await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false); | await restContext.InteractionResponseCallback.Invoke(payload).ConfigureAwait(false); | ||||
| @@ -3,7 +3,7 @@ using System; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Represents a result type for <see cref="TypeConverter.ReadAsync(IInteractionContext, WebSocket.SocketSlashCommandDataOption, IServiceProvider)"/>. | |||||
| /// Represents a result type for <see cref="TypeConverter.ReadAsync(IInteractionContext, IApplicationCommandInteractionDataOption, IServiceProvider)"/>. | |||||
| /// </summary> | /// </summary> | ||||
| public struct TypeConverterResult : IResult | public struct TypeConverterResult : IResult | ||||
| { | { | ||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal class NullableComponentConverter<T> : ComponentTypeConverter<T> | |||||
| { | |||||
| private readonly ComponentTypeConverter _typeConverter; | |||||
| public NullableComponentConverter(InteractionService interactionService, IServiceProvider services) | |||||
| { | |||||
| var type = Nullable.GetUnderlyingType(typeof(T)); | |||||
| if (type is null) | |||||
| throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type"); | |||||
| _typeConverter = interactionService.GetComponentTypeConverter(type, services); | |||||
| } | |||||
| public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) | |||||
| => string.IsNullOrEmpty(option.Value) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeConverter.ReadAsync(context, option, services); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal class NullableReader<T> : TypeReader<T> | |||||
| { | |||||
| private readonly TypeReader _typeReader; | |||||
| public NullableReader(InteractionService interactionService, IServiceProvider services) | |||||
| { | |||||
| var type = Nullable.GetUnderlyingType(typeof(T)); | |||||
| if (type is null) | |||||
| throw new ArgumentException($"No type {nameof(TypeConverter)} is defined for this {type.FullName}", "type"); | |||||
| _typeReader = interactionService.GetTypeReader(type, services); | |||||
| } | |||||
| public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services) | |||||
| => string.IsNullOrEmpty(option) ? Task.FromResult(TypeConverterResult.FromSuccess(null)) : _typeReader.ReadAsync(context, option, services); | |||||
| } | |||||
| } | |||||
| @@ -40,7 +40,8 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| Name = commandInfo.Name, | Name = commandInfo.Name, | ||||
| Description = commandInfo.Description, | Description = commandInfo.Description, | ||||
| IsDefaultPermission = commandInfo.DefaultPermission, | |||||
| IsDMEnabled = commandInfo.IsEnabledInDm, | |||||
| DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), | |||||
| }.Build(); | }.Build(); | ||||
| if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | ||||
| @@ -64,8 +65,20 @@ namespace Discord.Interactions | |||||
| public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | ||||
| => commandInfo.CommandType switch | => commandInfo.CommandType switch | ||||
| { | { | ||||
| ApplicationCommandType.Message => new MessageCommandBuilder { Name = commandInfo.Name, IsDefaultPermission = commandInfo.DefaultPermission}.Build(), | |||||
| ApplicationCommandType.User => new UserCommandBuilder { Name = commandInfo.Name, IsDefaultPermission=commandInfo.DefaultPermission}.Build(), | |||||
| ApplicationCommandType.Message => new MessageCommandBuilder | |||||
| { | |||||
| Name = commandInfo.Name, | |||||
| IsDefaultPermission = commandInfo.DefaultPermission, | |||||
| DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), | |||||
| IsDMEnabled = commandInfo.IsEnabledInDm | |||||
| }.Build(), | |||||
| ApplicationCommandType.User => new UserCommandBuilder | |||||
| { | |||||
| Name = commandInfo.Name, | |||||
| IsDefaultPermission = commandInfo.DefaultPermission, | |||||
| DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), | |||||
| IsDMEnabled = commandInfo.IsEnabledInDm | |||||
| }.Build(), | |||||
| _ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.") | _ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.") | ||||
| }; | }; | ||||
| #endregion | #endregion | ||||
| @@ -113,6 +126,8 @@ namespace Discord.Interactions | |||||
| Name = moduleInfo.SlashGroupName, | Name = moduleInfo.SlashGroupName, | ||||
| Description = moduleInfo.Description, | Description = moduleInfo.Description, | ||||
| IsDefaultPermission = moduleInfo.DefaultPermission, | IsDefaultPermission = moduleInfo.DefaultPermission, | ||||
| IsDMEnabled = moduleInfo.IsEnabledInDm, | |||||
| DefaultMemberPermissions = moduleInfo.DefaultMemberPermissions | |||||
| }.Build(); | }.Build(); | ||||
| if (options.Count > SlashCommandBuilder.MaxOptionsCount) | if (options.Count > SlashCommandBuilder.MaxOptionsCount) | ||||
| @@ -217,5 +232,8 @@ namespace Discord.Interactions | |||||
| return builder.Build(); | return builder.Build(); | ||||
| } | } | ||||
| public static GuildPermission? SanitizeGuildPermissions(this GuildPermission permissions) => | |||||
| permissions == 0 ? null : permissions; | |||||
| } | } | ||||
| } | } | ||||
| @@ -24,5 +24,12 @@ namespace Discord.API | |||||
| [JsonProperty("default_permission")] | [JsonProperty("default_permission")] | ||||
| public Optional<bool> DefaultPermissions { get; set; } | public Optional<bool> DefaultPermissions { get; set; } | ||||
| // V2 Permissions | |||||
| [JsonProperty("dm_permission")] | |||||
| public Optional<bool?> DmPermission { get; set; } | |||||
| [JsonProperty("default_member_permissions")] | |||||
| public Optional<GuildPermission?> DefaultMemberPermission { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -66,5 +66,12 @@ namespace Discord.API | |||||
| [JsonProperty("member_count")] | [JsonProperty("member_count")] | ||||
| public Optional<int> MemberCount { get; set; } | public Optional<int> MemberCount { get; set; } | ||||
| //ForumChannel | |||||
| [JsonProperty("available_tags")] | |||||
| public Optional<ForumTags[]> ForumTags { get; set; } | |||||
| [JsonProperty("default_auto_archive_duration")] | |||||
| public Optional<ThreadArchiveDuration> AutoArchiveDuration { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,8 +9,5 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("members")] | [JsonProperty("members")] | ||||
| public ThreadMember[] Members { get; set; } | public ThreadMember[] Members { get; set; } | ||||
| [JsonProperty("has_more")] | |||||
| public bool HasMore { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,21 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ForumTags | |||||
| { | |||||
| [JsonProperty("id")] | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; set; } | |||||
| [JsonProperty("emoji_id")] | |||||
| public Optional<ulong?> EmojiId { get; set; } | |||||
| [JsonProperty("emoji_name")] | |||||
| public Optional<string> EmojiName { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API | |||||
| { | |||||
| internal class ForumThreadMessage | |||||
| { | |||||
| [JsonProperty("content")] | |||||
| public Optional<string> Content { get; set; } | |||||
| [JsonProperty("nonce")] | |||||
| public Optional<string> Nonce { get; set; } | |||||
| [JsonProperty("embeds")] | |||||
| public Optional<Embed[]> Embeds { get; set; } | |||||
| [JsonProperty("allowed_mentions")] | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| [JsonProperty("components")] | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| [JsonProperty("sticker_ids")] | |||||
| public Optional<ulong[]> Stickers { get; set; } | |||||
| [JsonProperty("flags")] | |||||
| public Optional<MessageFlags> Flags { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -12,5 +12,8 @@ namespace Discord.API | |||||
| [JsonProperty("guild_id")] | [JsonProperty("guild_id")] | ||||
| public Optional<ulong> GuildId { get; set; } | public Optional<ulong> GuildId { get; set; } | ||||
| [JsonProperty("fail_if_not_exists")] | |||||
| public Optional<bool> FailIfNotExists { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -19,6 +19,12 @@ namespace Discord.API.Rest | |||||
| [JsonProperty("default_permission")] | [JsonProperty("default_permission")] | ||||
| public Optional<bool> DefaultPermission { get; set; } | public Optional<bool> DefaultPermission { get; set; } | ||||
| [JsonProperty("dm_permission")] | |||||
| public Optional<bool?> DmPermission { get; set; } | |||||
| [JsonProperty("default_member_permissions")] | |||||
| public Optional<GuildPermission?> DefaultMemberPermission { get; set; } | |||||
| public CreateApplicationCommandParams() { } | public CreateApplicationCommandParams() { } | ||||
| public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) | public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) | ||||
| { | { | ||||
| @@ -0,0 +1,96 @@ | |||||
| using Discord.Net.Converters; | |||||
| using Discord.Net.Rest; | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class CreateMultipartPostAsync | |||||
| { | |||||
| private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||||
| public FileAttachment[] Files { get; } | |||||
| public string Title { get; set; } | |||||
| public ThreadArchiveDuration ArchiveDuration { get; set; } | |||||
| public Optional<int?> Slowmode { get; set; } | |||||
| public Optional<string> Content { get; set; } | |||||
| public Optional<Embed[]> Embeds { get; set; } | |||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||||
| public Optional<ActionRowComponent[]> MessageComponent { get; set; } | |||||
| public Optional<MessageFlags?> Flags { get; set; } | |||||
| public Optional<ulong[]> Stickers { get; set; } | |||||
| public CreateMultipartPostAsync(params FileAttachment[] attachments) | |||||
| { | |||||
| Files = attachments; | |||||
| } | |||||
| public IReadOnlyDictionary<string, object> ToDictionary() | |||||
| { | |||||
| var d = new Dictionary<string, object>(); | |||||
| var payload = new Dictionary<string, object>(); | |||||
| var message = new Dictionary<string, object>(); | |||||
| payload["name"] = Title; | |||||
| payload["auto_archive_duration"] = ArchiveDuration; | |||||
| if (Slowmode.IsSpecified) | |||||
| payload["rate_limit_per_user"] = Slowmode.Value; | |||||
| // message | |||||
| if (Content.IsSpecified) | |||||
| message["content"] = Content.Value; | |||||
| if (Embeds.IsSpecified) | |||||
| message["embeds"] = Embeds.Value; | |||||
| if (AllowedMentions.IsSpecified) | |||||
| message["allowed_mentions"] = AllowedMentions.Value; | |||||
| if (MessageComponent.IsSpecified) | |||||
| message["components"] = MessageComponent.Value; | |||||
| if (Stickers.IsSpecified) | |||||
| message["sticker_ids"] = Stickers.Value; | |||||
| if (Flags.IsSpecified) | |||||
| message["flags"] = Flags.Value; | |||||
| List<object> attachments = new(); | |||||
| for (int n = 0; n != Files.Length; n++) | |||||
| { | |||||
| var attachment = Files[n]; | |||||
| var filename = attachment.FileName ?? "unknown.dat"; | |||||
| if (attachment.IsSpoiler && !filename.StartsWith(AttachmentExtensions.SpoilerPrefix)) | |||||
| filename = filename.Insert(0, AttachmentExtensions.SpoilerPrefix); | |||||
| d[$"files[{n}]"] = new MultipartFile(attachment.Stream, filename); | |||||
| attachments.Add(new | |||||
| { | |||||
| id = (ulong)n, | |||||
| filename = filename, | |||||
| description = attachment.Description ?? Optional<string>.Unspecified | |||||
| }); | |||||
| } | |||||
| message["attachments"] = attachments; | |||||
| payload["message"] = message; | |||||
| var json = new StringBuilder(); | |||||
| using (var text = new StringWriter(json)) | |||||
| using (var writer = new JsonTextWriter(text)) | |||||
| _serializer.Serialize(writer, payload); | |||||
| d["payload_json"] = json.ToString(); | |||||
| return d; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| internal class CreatePostParams | |||||
| { | |||||
| // thread | |||||
| [JsonProperty("name")] | |||||
| public string Title { get; set; } | |||||
| [JsonProperty("auto_archive_duration")] | |||||
| public ThreadArchiveDuration ArchiveDuration { get; set; } | |||||
| [JsonProperty("rate_limit_per_user")] | |||||
| public Optional<int?> Slowmode { get; set; } | |||||
| [JsonProperty("message")] | |||||
| public ForumThreadMessage Message { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -37,7 +37,7 @@ namespace Discord.API.Rest | |||||
| if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
| payload["content"] = Content.Value; | payload["content"] = Content.Value; | ||||
| if (IsTTS.IsSpecified) | if (IsTTS.IsSpecified) | ||||
| payload["tts"] = IsTTS.Value.ToString(); | |||||
| payload["tts"] = IsTTS.Value; | |||||
| if (Nonce.IsSpecified) | if (Nonce.IsSpecified) | ||||
| payload["nonce"] = Nonce.Value; | payload["nonce"] = Nonce.Value; | ||||
| if (Embeds.IsSpecified) | if (Embeds.IsSpecified) | ||||
| @@ -50,7 +50,7 @@ namespace Discord.API.Rest | |||||
| if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
| data["content"] = Content.Value; | data["content"] = Content.Value; | ||||
| if (IsTTS.IsSpecified) | if (IsTTS.IsSpecified) | ||||
| data["tts"] = IsTTS.Value.ToString(); | |||||
| data["tts"] = IsTTS.Value; | |||||
| if (MessageComponents.IsSpecified) | if (MessageComponents.IsSpecified) | ||||
| data["components"] = MessageComponents.Value; | data["components"] = MessageComponents.Value; | ||||
| if (Embeds.IsSpecified) | if (Embeds.IsSpecified) | ||||
| @@ -36,7 +36,7 @@ namespace Discord.API.Rest | |||||
| if (Content.IsSpecified) | if (Content.IsSpecified) | ||||
| payload["content"] = Content.Value; | payload["content"] = Content.Value; | ||||
| if (IsTTS.IsSpecified) | if (IsTTS.IsSpecified) | ||||
| payload["tts"] = IsTTS.Value.ToString(); | |||||
| payload["tts"] = IsTTS.Value; | |||||
| if (Nonce.IsSpecified) | if (Nonce.IsSpecified) | ||||
| payload["nonce"] = Nonce.Value; | payload["nonce"] = Nonce.Value; | ||||
| if (Username.IsSpecified) | if (Username.IsSpecified) | ||||
| @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Commands")] | [assembly: InternalsVisibleTo("Discord.Net.Commands")] | ||||
| [assembly: InternalsVisibleTo("Discord.Net.Tests")] | [assembly: InternalsVisibleTo("Discord.Net.Tests")] | ||||
| [assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")] | [assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")] | ||||
| [assembly: InternalsVisibleTo("Discord.Net.Tests.Integration")] | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Interactions")] | [assembly: InternalsVisibleTo("Discord.Net.Interactions")] | ||||
| [assembly: TypeForwardedTo(typeof(Discord.Embed))] | [assembly: TypeForwardedTo(typeof(Discord.Embed))] | ||||
| @@ -7,6 +7,8 @@ | |||||
| <Description>A core Discord.Net library containing the REST client and models.</Description> | <Description>A core Discord.Net library containing the REST client and models.</Description> | ||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net6.0;net5.0;net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">net6.0;net5.0;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <WarningLevel>5</WarningLevel> | |||||
| <TreatWarningsAsErrors>True</TreatWarningsAsErrors> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||