diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 84ee6e5a1..807381d31 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,3 @@ +github: quinchs open_collective: discordnet +custom: https://paypal.me/quinchs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4de065c..023400c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [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 ### Added diff --git a/Discord.Net.targets b/Discord.Net.targets index e50e6eceb..adb0a338c 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -1,6 +1,6 @@ - 3.5.0 + 3.6.1 latest Discord.Net Contributors discord;discordapp diff --git a/README.md b/README.md index 541948f4b..bb8437432 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -# Discord.Net

Logo diff --git a/docs/docfx.json b/docs/docfx.json index 2a4ee2867..105aa0493 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -60,7 +60,7 @@ "overwrite": "_overwrites/**/**.md", "globalMetadata": { "_appTitle": "Discord.Net Documentation", - "_appFooter": "Discord.Net (c) 2015-2022 3.5.0", + "_appFooter": "Discord.Net (c) 2015-2022 3.6.1", "_enableSearch": true, "_appLogoPath": "marketing/logo/SVG/Logomark Purple.svg", "_appFaviconPath": "favicon.ico" diff --git a/docs/guides/getting_started/first-bot.md b/docs/guides/getting_started/first-bot.md index e1af20d30..a5b0dbbd4 100644 --- a/docs/guides/getting_started/first-bot.md +++ b/docs/guides/getting_started/first-bot.md @@ -202,7 +202,7 @@ online in Discord. To create commands for your bot, you may choose from a variety of 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 [CommandService]. diff --git a/docs/guides/int_basics/application-commands/slash-commands/parameters.md b/docs/guides/int_basics/application-commands/slash-commands/parameters.md index 6afd83729..4f3cd2e8c 100644 --- a/docs/guides/int_basics/application-commands/slash-commands/parameters.md +++ b/docs/guides/int_basics/application-commands/slash-commands/parameters.md @@ -15,9 +15,10 @@ Slash commands can have a bunch of parameters, each their own type. Let's first | Integer | A number. | | Boolean | True or False. | | User | A user | -| Channel | A channel, this includes voice text and categories | | Role | A role. | +| Channel | A channel, this includes voice text and categories | | 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: | Name | C# Type | @@ -31,6 +32,7 @@ Each one of the parameter types has its own DNET type in the `SocketSlashCommand | Role | `SocketRole` | | Channel | `SocketChannel` | | Mentionable | `SocketUser`, `SocketGuildUser`, or `SocketRole` | +| File | `IAttachment` | Let's start by making a command that takes in a user and lists their roles. diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md index abea2a735..c019b1424 100644 --- a/docs/guides/int_framework/intro.md +++ b/docs/guides/int_framework/intro.md @@ -158,6 +158,14 @@ Interaction service complex parameter constructors are prioritized in the follow 2. Constuctor tagged with `[ComplexParameterCtor]`. 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 A valid User Command must have the following structure: @@ -282,6 +290,8 @@ By nesting commands inside a module that is tagged with [GroupAttribute] you can > Although creating nested module stuctures are allowed, > 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 Any of the following socket events can be used to execute commands: diff --git a/docs/guides/int_framework/permissions.md b/docs/guides/int_framework/permissions.md new file mode 100644 index 000000000..e35bb162d --- /dev/null +++ b/docs/guides/int_framework/permissions.md @@ -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 + diff --git a/docs/guides/int_framework/samples/intro/groupmodule.cs b/docs/guides/int_framework/samples/intro/groupmodule.cs new file mode 100644 index 000000000..f0d992aff --- /dev/null +++ b/docs/guides/int_framework/samples/intro/groupmodule.cs @@ -0,0 +1,21 @@ +// You can put commands in groups +[Group("group-name", "Group description")] +public class CommandGroupModule : InteractionModuleBase +{ + // 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 + { + // 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); + } +} \ No newline at end of file diff --git a/docs/guides/int_framework/samples/permissions/guild-only.cs b/docs/guides/int_framework/samples/permissions/guild-only.cs new file mode 100644 index 000000000..2e907e2d3 --- /dev/null +++ b/docs/guides/int_framework/samples/permissions/guild-only.cs @@ -0,0 +1,6 @@ +[EnabledInDm(false)] +[SlashCommand("ban", "Bans a user in this guild")] +public async Task BanAsync(...) +{ + ... +} diff --git a/docs/guides/int_framework/samples/permissions/guild-perms.cs b/docs/guides/int_framework/samples/permissions/guild-perms.cs new file mode 100644 index 000000000..2853f23e7 --- /dev/null +++ b/docs/guides/int_framework/samples/permissions/guild-perms.cs @@ -0,0 +1,7 @@ +[EnabledInDm(false)] +[DefaultMemberPermissions(GuildPermission.BanMembers)] +[SlashCommand("ban", "Bans a user in this guild")] +public async Task BanAsync(...) +{ + ... +} diff --git a/docs/guides/int_framework/samples/permissions/perm-nesting.cs b/docs/guides/int_framework/samples/permissions/perm-nesting.cs new file mode 100644 index 000000000..8913b1ac1 --- /dev/null +++ b/docs/guides/int_framework/samples/permissions/perm-nesting.cs @@ -0,0 +1,16 @@ +[EnabledInDm(true)] +[DefaultMemberPermissions(GuildPermission.ViewChannels)] +public class Module : InteractionModuleBase +{ + [DefaultMemberPermissions(GuildPermission.SendMessages)] + public class NestedModule : InteractionModuleBase + { + // 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"); + } +} diff --git a/docs/guides/int_framework/samples/permissions/perm-stacking.cs b/docs/guides/int_framework/samples/permissions/perm-stacking.cs new file mode 100644 index 000000000..92cc51477 --- /dev/null +++ b/docs/guides/int_framework/samples/permissions/perm-stacking.cs @@ -0,0 +1,4 @@ +[DefaultMemberPermissions(GuildPermission.SendMessages | GuildPermission.ViewChannels)] +[SlashCommand("ping", "Pong!")] +public async Task Ping() + => await RespondAsync("pong"); diff --git a/docs/guides/other_libs/samples/ModifyLogMethod.cs b/docs/guides/other_libs/samples/ModifyLogMethod.cs index b4870cfd1..0f7c11daf 100644 --- a/docs/guides/other_libs/samples/ModifyLogMethod.cs +++ b/docs/guides/other_libs/samples/ModifyLogMethod.cs @@ -6,8 +6,8 @@ private static async Task LogAsync(LogMessage message) LogSeverity.Error => LogEventLevel.Error, LogSeverity.Warning => LogEventLevel.Warning, LogSeverity.Info => LogEventLevel.Information, - LogSeverity.Verbose => LogEventLevel.Debug, - LogSeverity.Debug => LogEventLevel.Verbose, + LogSeverity.Verbose => LogEventLevel.Verbose, + LogSeverity.Debug => LogEventLevel.Debug, _ => LogEventLevel.Information }; Log.Write(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message); diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml index af0a8e2b4..f122ea6ba 100644 --- a/docs/guides/toc.yml +++ b/docs/guides/toc.yml @@ -57,6 +57,8 @@ topicUid: Guides.IntFw.DI - name: Post-execution Handling topicUid: Guides.IntFw.PostExecution + - name: Permissions + topicUid: Guides.IntFw.Perms - name: Slash Command Basics items: - name: Introduction diff --git a/samples/InteractionFramework/Modules/ExampleModule.cs b/samples/InteractionFramework/Modules/ExampleModule.cs index 1c0a6c8a2..21064bbe3 100644 --- a/samples/InteractionFramework/Modules/ExampleModule.cs +++ b/samples/InteractionFramework/Modules/ExampleModule.cs @@ -14,7 +14,7 @@ namespace InteractionFramework.Modules private InteractionHandler _handler; - // Constructor injection is also a valid way to access the dependecies + // Constructor injection is also a valid way to access the dependencies public ExampleModule(InteractionHandler handler) { _handler = handler; diff --git a/samples/MediatRSample/MediatRSample.sln b/samples/MediatRSample/MediatRSample.sln deleted file mode 100644 index d0599ae26..000000000 --- a/samples/MediatRSample/MediatRSample.sln +++ /dev/null @@ -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 diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs index e9ed63e58..a6861c10c 100644 --- a/src/Discord.Net.Core/DiscordErrorCode.cs +++ b/src/Discord.Net.Core/DiscordErrorCode.cs @@ -58,6 +58,7 @@ namespace Discord #endregion #region General Actions (20XXX) + UnknownTag = 10087, BotsCannotUse = 20001, OnlyBotsCanUse = 20002, CannotSendExplicitContent = 20009, @@ -98,6 +99,8 @@ namespace Discord #region General Request Errors (40XXX) MaximumNumberOfEditsReached = 30046, + MaximumNumberOfPinnedThreadsInAForumChannelReached = 30047, + MaximumNumberOfTagsInAForumChannelReached = 30048, TokenUnauthorized = 40001, InvalidVerification = 40002, OpeningDMTooFast = 40003, @@ -112,6 +115,7 @@ namespace Discord #region Action Preconditions/Checks (50XXX) InteractionHasAlreadyBeenAcknowledged = 40060, + TagNamesMustBeUnique = 40061, MissingPermissions = 50001, InvalidAccountType = 50002, CannotExecuteForDM = 50003, @@ -148,6 +152,7 @@ namespace Discord InvalidMessageType = 50068, PaymentSourceRequiredForGift = 50070, CannotDeleteRequiredCommunityChannel = 50074, + CannotEditStickersWithinAMessage = 50080, InvalidSticker = 50081, CannotExecuteOnArchivedThread = 50083, InvalidThreadNotificationSettings = 50084, diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index 501a0e905..9b3ac8453 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -17,6 +17,16 @@ namespace Discord /// public Optional IsDefaultPermission { get; set; } + ///

+ /// Gets or sets whether or not this command can be used in DMs. + /// + public Optional IsDMEnabled { get; set; } + + /// + /// Gets or sets the default permissions required by a user to execute this application command. + /// + public Optional DefaultMemberPermissions { get; set; } + internal ApplicationCommandProperties() { } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs index c7a7cf741..59040dd4e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs @@ -31,6 +31,16 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; + /// + /// Gets or sets whether or not this command can be used in DMs. + /// + public bool IsDMEnabled { get; set; } = true; + + /// + /// Gets or sets the default permission required to use this slash command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } + private string _name; /// @@ -44,7 +54,9 @@ namespace Discord var props = new MessageCommandProperties { Name = Name, - IsDefaultPermission = IsDefaultPermission + IsDefaultPermission = IsDefaultPermission, + IsDMEnabled = IsDMEnabled, + DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified }; return props; @@ -73,5 +85,27 @@ namespace Discord IsDefaultPermission = isDefaultPermission; return this; } + + /// + /// Sets whether or not this command can be used in dms + /// + /// if the command is available in dms, otherwise . + /// The current builder. + public MessageCommandBuilder WithDMPermission(bool permission) + { + IsDMEnabled = permission; + return this; + } + + /// + /// Sets the default member permissions required to use this application command. + /// + /// The permissions required to use this command. + /// The current builder. + public MessageCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) + { + DefaultMemberPermissions = permissions; + return this; + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs index bd1078be3..7c82dce55 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs @@ -31,6 +31,16 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; + /// + /// Gets or sets whether or not this command can be used in DMs. + /// + public bool IsDMEnabled { get; set; } = true; + + /// + /// Gets or sets the default permission required to use this slash command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } + private string _name; /// @@ -42,7 +52,9 @@ namespace Discord var props = new UserCommandProperties { Name = Name, - IsDefaultPermission = IsDefaultPermission + IsDefaultPermission = IsDefaultPermission, + IsDMEnabled = IsDMEnabled, + DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified }; return props; @@ -71,5 +83,27 @@ namespace Discord IsDefaultPermission = isDefaultPermission; return this; } + + /// + /// Sets whether or not this command can be used in dms + /// + /// if the command is available in dms, otherwise . + /// The current builder. + public UserCommandBuilder WithDMPermission(bool permission) + { + IsDMEnabled = permission; + return this; + } + + /// + /// Sets the default member permissions required to use this application command. + /// + /// The permissions required to use this command. + /// The current builder. + public UserCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) + { + DefaultMemberPermissions = permissions; + return this; + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index 72045a52a..58a002649 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -34,6 +34,19 @@ namespace Discord /// bool IsDefaultPermission { get; } + /// + /// Indicates whether the command is available in DMs with the app. + /// + /// + /// Only for globally-scoped commands. + /// + bool IsEnabledInDm { get; } + + /// + /// Set of default required to invoke the command. + /// + GuildPermissions DefaultMemberPermissions { get; } + /// /// Gets a collection of options for this application command. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs index ccfb2da0a..bf74a160c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs @@ -81,6 +81,16 @@ namespace Discord /// public bool IsDefaultPermission { get; set; } = true; + /// + /// Gets or sets whether or not this command can be used in DMs. + /// + public bool IsDMEnabled { get; set; } = true; + + /// + /// Gets or sets the default permission required to use this slash command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } + private string _name; private string _description; private List _options; @@ -96,6 +106,8 @@ namespace Discord Name = Name, Description = Description, IsDefaultPermission = IsDefaultPermission, + IsDMEnabled = IsDMEnabled, + DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified }; if (Options != null && Options.Any()) @@ -145,6 +157,28 @@ namespace Discord return this; } + /// + /// Sets whether or not this command can be used in dms + /// + /// if the command is available in dms, otherwise . + /// The current builder. + public SlashCommandBuilder WithDMPermission(bool permission) + { + IsDMEnabled = permission; + return this; + } + + /// + /// Sets the default member permissions required to use this application command. + /// + /// The permissions required to use this command. + /// The current builder. + public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissions) + { + DefaultMemberPermissions = permissions; + return this; + } + /// /// Adds an option to the current slash command. /// @@ -164,21 +198,13 @@ namespace Discord string description, bool? isRequired = null, bool? isDefault = null, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, List options = null, List 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, // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) 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 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)); @@ -214,6 +240,7 @@ namespace Discord throw new InvalidOperationException($"Cannot have more than {MaxOptionsCount} options!"); 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); return this; @@ -236,6 +263,9 @@ namespace Discord if (Options.Count + options.Length > MaxOptionsCount) 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); return this; } @@ -379,7 +409,7 @@ namespace Discord MinValue = MinValue, MaxValue = MaxValue }; - } + } /// /// Adds an option to the current slash command. @@ -400,21 +430,13 @@ namespace Discord string description, bool? isRequired = null, bool isDefault = false, bool isAutocomplete = false, double? minValue = null, double? maxValue = null, List options = null, List 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, // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand if (!Regex.IsMatch(name, @"^[\w-]{1,32}$")) 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 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)); @@ -449,6 +471,7 @@ namespace Discord throw new InvalidOperationException($"There can only be {SlashCommandBuilder.MaxOptionsCount} options per sub command group!"); Preconditions.NotNull(option, nameof(option)); + Preconditions.Options(option.Name, option.Description); // double check again Options.Add(option); return this; diff --git a/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs b/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs new file mode 100644 index 000000000..f9a3a3183 --- /dev/null +++ b/src/Discord.Net.Core/Interactions/IRouteMatchContainer.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Discord +{ + /// + /// Represents a container for temporarily storing CustomId wild card matches of a component. + /// + public interface IRouteMatchContainer + { + /// + /// Gets the collection of captured route segments in this container. + /// + /// + /// A collection of captured route segments. + /// + IEnumerable SegmentMatches { get; } + + /// + /// Sets the property of this container. + /// + /// The collection of captured route segments. + void SetSegmentMatches(IEnumerable segmentMatches); + } +} diff --git a/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs b/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs new file mode 100644 index 000000000..675bd6754 --- /dev/null +++ b/src/Discord.Net.Core/Interactions/IRouteSegmentMatch.cs @@ -0,0 +1,16 @@ +namespace Discord +{ + /// + /// Represents an object for storing a CustomId wild card match. + /// + public interface IRouteSegmentMatch + { + /// + /// Gets the captured value of this wild card match. + /// + /// + /// The value of this wild card. + /// + string Value { get; } + } +} diff --git a/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs b/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs new file mode 100644 index 000000000..f1d80cfea --- /dev/null +++ b/src/Discord.Net.Core/Interactions/RouteSegmentMatch.cs @@ -0,0 +1,16 @@ +namespace Discord +{ + /// + /// Represents an object for storing a CustomId wild card match. + /// + internal record RouteSegmentMatch : IRouteSegmentMatch + { + /// + public string Value { get; } + + public RouteSegmentMatch(string value) + { + Value = value; + } + } +} diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index ff8eb7c0d..2f24e660d 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -297,5 +297,22 @@ namespace Discord } } #endregion + + #region SlashCommandOptions + + /// or is null. + /// or are either empty or their length exceed limits. + 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 } } diff --git a/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs b/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs new file mode 100644 index 000000000..ec79da1e3 --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/DefaultMemberPermissionAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Interactions +{ + /// + /// Sets the of an application command or module. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class DefaultMemberPermissionsAttribute : Attribute + { + /// + /// Gets the default permission required to use this command. + /// + public GuildPermission Permissions { get; } + + /// + /// Sets the of an application command or module. + /// + /// The default permission required to use this command. + public DefaultMemberPermissionsAttribute(GuildPermission permissions) + { + Permissions = permissions; + } + } +} diff --git a/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs b/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs index ed0a532be..2e03dfac6 100644 --- a/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs +++ b/src/Discord.Net.Interactions/Attributes/DefaultPermissionAttribute.cs @@ -6,6 +6,7 @@ namespace Discord.Interactions /// Set the "Default Permission" property of an Application Command. /// [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 { /// diff --git a/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs new file mode 100644 index 000000000..a97f85a25 --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Interactions +{ + /// + /// Sets the property of an application command or module. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class EnabledInDmAttribute : Attribute + { + /// + /// Gets whether or not this command can be used in DMs. + /// + public bool IsEnabled { get; } + + /// + /// Sets the property of an application command or module. + /// + /// Whether or not this command can be used in DMs. + public EnabledInDmAttribute(bool isEnabled) + { + IsEnabled = isEnabled; + } + } +} diff --git a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs index d40547b3c..be0e5eb70 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs @@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders /// /// Gets the default permission of this command. /// + [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] public bool DefaultPermission { get; set; } = true; + /// + /// Gets whether this command can be used in DMs. + /// + public bool IsEnabledInDm { get; set; } = true; + + /// + /// Gets the default permissions needed for executing this command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } = null; + internal ContextCommandBuilder (ModuleBuilder module) : base(module) { } /// @@ -49,6 +60,7 @@ namespace Discord.Interactions.Builders /// /// The builder instance. /// + [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] public ContextCommandBuilder SetDefaultPermission (bool defaultPermision) { DefaultPermission = defaultPermision; @@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders return this; } + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public ContextCommandBuilder SetEnabledInDm(bool isEnabled) + { + IsEnabledInDm = isEnabled; + return this; + } + + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public ContextCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions) + { + DefaultMemberPermissions = permissions; + return this; + } + internal override ContextCommandInfo Build (ModuleInfo module, InteractionService commandService) => ContextCommandInfo.Create(this, module, commandService); } diff --git a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs index d8e9b0658..cd9bdfc24 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs @@ -17,8 +17,19 @@ namespace Discord.Interactions.Builders /// /// Gets and sets the default permission of this command. /// + [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] public bool DefaultPermission { get; set; } = true; + /// + /// Gets whether this command can be used in DMs. + /// + public bool IsEnabledInDm { get; set; } = true; + + /// + /// Gets the default permissions needed for executing this command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } = null; + internal SlashCommandBuilder (ModuleBuilder module) : base(module) { } /// @@ -49,6 +60,7 @@ namespace Discord.Interactions.Builders /// /// The builder instance. /// + [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] public SlashCommandBuilder WithDefaultPermission (bool permission) { DefaultPermission = permission; @@ -70,6 +82,32 @@ namespace Discord.Interactions.Builders return this; } + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public SlashCommandBuilder SetEnabledInDm(bool isEnabled) + { + IsEnabledInDm = isEnabled; + return this; + } + + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission permissions) + { + DefaultMemberPermissions = permissions; + return this; + } + internal override SlashCommandInfo Build (ModuleInfo module, InteractionService commandService) => new SlashCommandInfo(this, module, commandService); } diff --git a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs index 40c263643..b7f00025f 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs @@ -51,8 +51,19 @@ namespace Discord.Interactions.Builders /// /// Gets and sets the default permission of this module. /// + [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] public bool DefaultPermission { get; set; } = true; + /// + /// Gets whether this command can be used in DMs. + /// + public bool IsEnabledInDm { get; set; } = true; + + /// + /// Gets the default permissions needed for executing this command. + /// + public GuildPermission? DefaultMemberPermissions { get; set; } = null; + /// /// Gets and sets whether this has a . /// @@ -159,12 +170,39 @@ namespace Discord.Interactions.Builders /// /// The builder instance. /// + [Obsolete($"To be deprecated soon, use {nameof(SetEnabledInDm)} and {nameof(WithDefaultMemberPermissions)} instead.")] public ModuleBuilder WithDefaultPermission (bool permission) { DefaultPermission = permission; return this; } + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public ModuleBuilder SetEnabledInDm(bool isEnabled) + { + IsEnabledInDm = isEnabled; + return this; + } + + /// + /// Sets . + /// + /// New value of the . + /// + /// The builder instance. + /// + public ModuleBuilder WithDefaultMemberPermissions(GuildPermission permissions) + { + DefaultMemberPermissions = permissions; + return this; + } + /// /// Adds attributes to . /// diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index b2317d1f3..1bbdfcc4a 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -85,6 +85,16 @@ namespace Discord.Interactions.Builders builder.DefaultPermission = defPermission.IsDefaultPermission; } break; + case EnabledInDmAttribute enabledInDm: + { + builder.IsEnabledInDm = enabledInDm.IsEnabled; + } + break; + case DefaultMemberPermissionsAttribute memberPermission: + { + builder.DefaultMemberPermissions = memberPermission.Permissions; + } + break; case PreconditionAttribute precondition: builder.AddPreconditions(precondition); break; @@ -169,6 +179,16 @@ namespace Discord.Interactions.Builders builder.DefaultPermission = defaultPermission.IsDefaultPermission; } break; + case EnabledInDmAttribute enabledInDm: + { + builder.IsEnabledInDm = enabledInDm.IsEnabled; + } + break; + case DefaultMemberPermissionsAttribute memberPermission: + { + builder.DefaultMemberPermissions = memberPermission.Permissions; + } + break; case PreconditionAttribute precondition: builder.WithPreconditions(precondition); break; @@ -211,6 +231,16 @@ namespace Discord.Interactions.Builders builder.DefaultPermission = defaultPermission.IsDefaultPermission; } break; + case EnabledInDmAttribute enabledInDm: + { + builder.IsEnabledInDm = enabledInDm.IsEnabled; + } + break; + case DefaultMemberPermissionsAttribute memberPermission: + { + builder.DefaultMemberPermissions = memberPermission.Permissions; + } + break; case PreconditionAttribute precondition: builder.WithPreconditions(precondition); break; diff --git a/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs index 4c2e7af7d..2d6d748d4 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ContextCommands/ContextCommandInfo.cs @@ -17,6 +17,12 @@ namespace Discord.Interactions /// public bool DefaultPermission { get; } + /// + public bool IsEnabledInDm { get; } + + /// + public GuildPermission? DefaultMemberPermissions { get; } + /// public override IReadOnlyCollection Parameters { get; } @@ -31,6 +37,8 @@ namespace Discord.Interactions { CommandType = builder.CommandType; DefaultPermission = builder.DefaultPermission; + IsEnabledInDm = builder.IsEnabledInDm; + DefaultMemberPermissions = builder.DefaultMemberPermissions; Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); } diff --git a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs index a123ac183..e428144c7 100644 --- a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs @@ -26,6 +26,12 @@ namespace Discord.Interactions /// public bool DefaultPermission { get; } + /// + public bool IsEnabledInDm { get; } + + /// + public GuildPermission? DefaultMemberPermissions { get; } + /// public override IReadOnlyCollection Parameters { get; } @@ -41,6 +47,8 @@ namespace Discord.Interactions { Description = builder.Description; DefaultPermission = builder.DefaultPermission; + IsEnabledInDm = builder.IsEnabledInDm; + DefaultMemberPermissions = builder.DefaultMemberPermissions; Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); diff --git a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs index 1e0d532b0..dd1b97899 100644 --- a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs @@ -1,3 +1,5 @@ +using System; + namespace Discord.Interactions { /// @@ -18,6 +20,17 @@ namespace Discord.Interactions /// /// Gets the DefaultPermission of this command. /// + [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] bool DefaultPermission { get; } + + /// + /// Gets whether this command can be used in DMs. + /// + public bool IsEnabledInDm { get; } + + /// + /// Gets the default permissions needed for executing this command. + /// + public GuildPermission? DefaultMemberPermissions { get; } } } diff --git a/src/Discord.Net.Interactions/Info/ModuleInfo.cs b/src/Discord.Net.Interactions/Info/ModuleInfo.cs index 321e0bfa9..904d67410 100644 --- a/src/Discord.Net.Interactions/Info/ModuleInfo.cs +++ b/src/Discord.Net.Interactions/Info/ModuleInfo.cs @@ -41,8 +41,19 @@ namespace Discord.Interactions /// /// Gets the default Permission of this module. /// + [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] public bool DefaultPermission { get; } + /// + /// Gets whether this command can be used in DMs. + /// + public bool IsEnabledInDm { get; } + + /// + /// Gets the default permissions needed for executing this command. + /// + public GuildPermission? DefaultMemberPermissions { get; } + /// /// Gets the collection of Sub Modules of this module. /// @@ -110,6 +121,8 @@ namespace Discord.Interactions Description = builder.Description; Parent = parent; DefaultPermission = builder.DefaultPermission; + IsEnabledInDm = builder.IsEnabledInDm; + DefaultMemberPermissions = BuildDefaultMemberPermissions(builder); SlashCommands = BuildSlashCommands(builder).ToImmutableArray(); ContextCommands = BuildContextCommands(builder).ToImmutableArray(); ComponentCommands = BuildComponentCommands(builder).ToImmutableArray(); @@ -226,5 +239,20 @@ namespace Discord.Interactions } 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); + parent = parent.Parent; + } + + return permissions; + } } } diff --git a/src/Discord.Net.Interactions/InteractionContext.cs b/src/Discord.Net.Interactions/InteractionContext.cs index 99a8d8736..024ab5ef8 100644 --- a/src/Discord.Net.Interactions/InteractionContext.cs +++ b/src/Discord.Net.Interactions/InteractionContext.cs @@ -1,7 +1,10 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + namespace Discord.Interactions { /// - public class InteractionContext : IInteractionContext + public class InteractionContext : IInteractionContext, IRouteMatchContainer { /// public IDiscordClient Client { get; } @@ -13,6 +16,8 @@ namespace Discord.Interactions public IUser User { get; } /// public IDiscordInteraction Interaction { get; } + /// + public IReadOnlyCollection SegmentMatches { get; private set; } /// /// Initializes a new . @@ -30,5 +35,12 @@ namespace Discord.Interactions User = interaction.User; Interaction = interaction; } + + /// + public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray(); + + //IRouteMatchContainer + /// + IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches; } } diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index 01fb8cc9d..24302dfc7 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -775,6 +775,9 @@ namespace Discord.Interactions await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); return result; } + + SetMatchesIfApplicable(context, result); + return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); } @@ -819,9 +822,30 @@ namespace Discord.Interactions await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); return result; } + + SetMatchesIfApplicable(context, result); + return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); } + private static void SetMatchesIfApplicable(IInteractionContext context, SearchResult 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()); + } + internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null) => _typeConverterMap.Get(type, services); diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs index c2052b7c7..60980c065 100644 --- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs +++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs @@ -40,7 +40,8 @@ namespace Discord.Interactions { Name = commandInfo.Name, Description = commandInfo.Description, - IsDefaultPermission = commandInfo.DefaultPermission, + IsDMEnabled = commandInfo.IsEnabledInDm, + DefaultMemberPermissions = (commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0) }.Build(); if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) @@ -64,8 +65,20 @@ namespace Discord.Interactions public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) => 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), + IsDMEnabled = commandInfo.IsEnabledInDm + }.Build(), + ApplicationCommandType.User => new UserCommandBuilder + { + Name = commandInfo.Name, + IsDefaultPermission = commandInfo.DefaultPermission, + DefaultMemberPermissions = (commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0), + IsDMEnabled = commandInfo.IsEnabledInDm + }.Build(), _ => throw new InvalidOperationException($"{commandInfo.CommandType} isn't a supported command type.") }; #endregion @@ -113,6 +126,8 @@ namespace Discord.Interactions Name = moduleInfo.SlashGroupName, Description = moduleInfo.Description, IsDefaultPermission = moduleInfo.DefaultPermission, + IsDMEnabled = moduleInfo.IsEnabledInDm, + DefaultMemberPermissions = moduleInfo.DefaultMemberPermissions }.Build(); if (options.Count > SlashCommandBuilder.MaxOptionsCount) diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs index 81598b96e..8b84149dd 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs @@ -24,5 +24,12 @@ namespace Discord.API [JsonProperty("default_permission")] public Optional DefaultPermissions { get; set; } + + // V2 Permissions + [JsonProperty("dm_permission")] + public Optional DmPermission { get; set; } + + [JsonProperty("default_member_permissions")] + public Optional DefaultMemberPermission { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index 82f0befcd..7ae8718b6 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -19,6 +19,12 @@ namespace Discord.API.Rest [JsonProperty("default_permission")] public Optional DefaultPermission { get; set; } + [JsonProperty("dm_permission")] + public Optional DmPermission { get; set; } + + [JsonProperty("default_member_permissions")] + public Optional DefaultMemberPermission { get; set; } + public CreateApplicationCommandParams() { } public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) { diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index e345bfa94..74d7953ad 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -100,7 +100,12 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + + // TODO: better conversion to nullable optionals + DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), + DmPermission = arg.IsDMEnabled.ToNullable() + }; if (arg is SlashCommandProperties slashProps) @@ -134,7 +139,11 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + + // TODO: better conversion to nullable optionals + DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), + DmPermission = arg.IsDMEnabled.ToNullable() }; if (arg is SlashCommandProperties slashProps) @@ -171,7 +180,11 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + + // TODO: better conversion to nullable optionals + DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), + DmPermission = arg.IsDMEnabled.ToNullable() }; if (arg is SlashCommandProperties slashProps) @@ -285,7 +298,11 @@ namespace Discord.Rest Type = arg.Type, DefaultPermission = arg.IsDefaultPermission.IsSpecified ? arg.IsDefaultPermission.Value - : Optional.Unspecified + : Optional.Unspecified, + + // TODO: better conversion to nullable optionals + DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), + DmPermission = arg.IsDMEnabled.ToNullable() }; if (arg is SlashCommandProperties slashProps) diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index ea8d5bc42..667609ef4 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -27,6 +27,12 @@ namespace Discord.Rest /// public bool IsDefaultPermission { get; private set; } + /// + public bool IsEnabledInDm { get; private set; } + + /// + public GuildPermissions DefaultMemberPermissions { get; private set; } + /// /// Gets a collection of options for this command. /// @@ -57,6 +63,9 @@ namespace Discord.Rest Options = model.Options.IsSpecified ? model.Options.Value.Select(RestApplicationCommandOption.Create).ToImmutableArray() : ImmutableArray.Create(); + + IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true); + DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0)); } /// diff --git a/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs b/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs index 196c6133b..d407f5103 100644 --- a/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs +++ b/src/Discord.Net.Rest/Interactions/RestInteractionContext.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; namespace Discord.Rest @@ -6,7 +8,7 @@ namespace Discord.Rest /// /// Represents a Rest based context of an . /// - public class RestInteractionContext : IRestInteractionContext + public class RestInteractionContext : IRestInteractionContext, IRouteMatchContainer where TInteraction : RestInteraction { /// @@ -45,6 +47,9 @@ namespace Discord.Rest /// public Func InteractionResponseCallback { get; set; } + /// + public IReadOnlyCollection SegmentMatches { get; private set; } + /// /// Initializes a new . /// @@ -71,6 +76,13 @@ namespace Discord.Rest InteractionResponseCallback = interactionResponseCallback; } + /// + public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray(); + + //IRouteMatchContainer + /// + IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches; + // IInterationContext /// IDiscordClient IInteractionContext.Client => Client; diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 21594fed7..cca2de203 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -274,7 +274,7 @@ namespace Discord.API { ["$device"] = "Discord.Net", ["$os"] = Environment.OSVersion.Platform.ToString(), - [$"browser"] = "Discord.Net" + ["$browser"] = "Discord.Net" }; var msg = new IdentifyParams() { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 13da7cce7..61a5ff619 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1732,7 +1732,7 @@ namespace Discord.WebSocket /// ulong? IGuild.AFKChannelId => AFKChannelId; /// - IAudioClient IGuild.AudioClient => null; + IAudioClient IGuild.AudioClient => AudioClient; /// bool IGuild.Available => true; /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index 36eba0cd1..8f27b65f4 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -36,6 +36,12 @@ namespace Discord.WebSocket /// public bool IsDefaultPermission { get; private set; } + /// + public bool IsEnabledInDm { get; private set; } + + /// + public GuildPermissions DefaultMemberPermissions { get; private set; } + /// /// Gets a collection of s for this command. /// @@ -86,6 +92,9 @@ namespace Discord.WebSocket Options = model.Options.IsSpecified ? model.Options.Value.Select(SocketApplicationCommandOption.Create).ToImmutableArray() : ImmutableArray.Create(); + + IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true); + DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0)); } /// diff --git a/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs b/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs index d61068e3e..557c47a69 100644 --- a/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs +++ b/src/Discord.Net.WebSocket/Interactions/SocketInteractionContext.cs @@ -1,11 +1,13 @@ using Discord.WebSocket; +using System.Collections.Generic; +using System.Collections.Immutable; namespace Discord.Interactions { /// /// Represents a Web-Socket based context of an . /// - public class SocketInteractionContext : IInteractionContext + public class SocketInteractionContext : IInteractionContext, IRouteMatchContainer where TInteraction : SocketInteraction { /// @@ -36,6 +38,9 @@ namespace Discord.Interactions /// public TInteraction Interaction { get; } + /// + public IReadOnlyCollection SegmentMatches { get; private set; } + /// /// Initializes a new . /// @@ -50,6 +55,13 @@ namespace Discord.Interactions Interaction = interaction; } + /// + public void SetSegmentMatches(IEnumerable segmentMatches) => SegmentMatches = segmentMatches.ToImmutableArray(); + + //IRouteMatchContainer + /// + IEnumerable IRouteMatchContainer.SegmentMatches => SegmentMatches; + // IInteractionContext /// IDiscordClient IInteractionContext.Client => Client; diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index d79e9a24a..269657771 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net - 3.5.0$suffix$ + 3.6.1$suffix$ Discord.Net Discord.Net Contributors foxbot @@ -14,44 +14,44 @@ https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + +