From afa480a8e69a1b3626b1c2257759c9c9c7b7989b Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Wed, 11 Aug 2021 20:04:23 -0400 Subject: [PATCH 01/14] Implemented Context Menus Added ApplicationCommand types: Slash : 1 User: 2 Message: 3 And the appropriate CRUD methods. --- Discord.Net.sln | 20 +- src/Discord.Net.Core/Discord.Net.Core.csproj | 2 +- src/Discord.Net.Core/Discord.Net.Core.xml | 149 ++++++++ .../ApplicationCommandProperties.cs | 6 +- .../Interactions/ApplicationCommandTypes.cs | 15 + .../Interactions/IApplicationCommand.cs | 5 + .../Interactions/MessageCommandBuilder.cs | 109 ++++++ .../MessageCommandCreationProperties.cs | 30 ++ .../Interactions/SlashCommandBuilder.cs | 3 +- .../SlashCommandCreationProperties.cs | 4 + .../Interactions/UserCommandBuilder.cs | 109 ++++++ .../UserCommandCreationProperties.cs | 30 ++ .../Rest/CreateApplicationCommandParams.cs | 6 +- .../Rest/ModifyApplicationCommandParams.cs | 3 + src/Discord.Net.Rest/Discord.Net.Rest.xml | 97 +++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 134 +++++++ src/Discord.Net.Rest/DiscordRestClient.cs | 17 + .../Interactions/InteractionHelper.cs | 346 ++++++++++++++++++ .../Interactions/RestApplicationCommand.cs | 34 +- .../RestApplicationCommandType.cs | 6 +- .../Interactions/RestGlobalMessageCommand.cs | 41 +++ .../Interactions/RestGlobalUserCommand.cs | 41 +++ .../Interactions/RestGuildMessageCommand.cs | 61 +++ .../Interactions/RestGuildUserCommand.cs | 61 +++ .../Discord.Net.WebSocket.xml | 3 + .../SocketApplicationCommand.cs | 3 + 26 files changed, 1320 insertions(+), 15 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs create mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs diff --git a/Discord.Net.sln b/Discord.Net.sln index 1a32f1270..c11739aef 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28407.52 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31521.260 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" EndProject @@ -40,7 +40,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeatureTesting", "..\FeatureTesting\FeatureTesting\FeatureTesting.csproj", "{0CC57A32-3AC7-489D-8DF5-C431925E4675}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -232,6 +234,18 @@ Global {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x64.Build.0 = Release|Any CPU {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.ActiveCfg = Release|Any CPU {4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.Build.0 = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x64.Build.0 = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Debug|x86.Build.0 = Debug|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|Any CPU.Build.0 = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x64.ActiveCfg = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x64.Build.0 = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x86.ActiveCfg = Release|Any CPU + {0CC57A32-3AC7-489D-8DF5-C431925E4675}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index f0be10059..babda86b7 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -8,7 +8,7 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 Discord.Net.Labs.Core - 3.0.0-pre + 3.3.1.0 Discord.Net.Labs.Core https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index 059d9d16b..d1e4770e3 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -4452,6 +4452,11 @@ Gets or sets the discription of this command. + + + Gets or sets the type for this command. + + Gets or sets the options for this command. @@ -4472,6 +4477,11 @@ Gets the unique id of the parent application. + + + The type of the command + + The name of the command. @@ -5444,6 +5454,73 @@ Will render this option as selected by default. + + + A class used to build slash commands. + + + + + Returns the maximun length a commands name allowed by Discord + + + + + Returns the maximum length of a commands description allowed by Discord. + + + + + The name of this slash command. + + + + + A 1-100 length description of this slash command + + + + + Build the current builder into a class. + + A that can be used to create user commands over rest. + + + + Sets the field name. + + The value to set the field name to. + + The current builder. + + + + + Sets the description of the current command. + + The description of this command. + The current builder. + + + + A class used to create Message commands. + + + + + The name of this command. + + + + + The discription of this command. + + + + + Gets or sets the type for this command. + + A class used to build slash commands. @@ -5691,6 +5768,11 @@ The discription of this command. + + + Gets or sets the type for this command. + + Gets or sets the options for this command. @@ -5701,6 +5783,73 @@ Whether the command is enabled by default when the app is added to a guild. Default is + + + A class used to build slash commands. + + + + + Returns the maximun length a commands name allowed by Discord + + + + + Returns the maximum length of a commands description allowed by Discord. + + + + + The name of this slash command. + + + + + A 1-100 length description of this slash command + + + + + Build the current builder into a class. + + A that can be used to create user commands over rest. + + + + Sets the field name. + + The value to set the field name to. + + The current builder. + + + + + Sets the description of the current command. + + The description of this command. + The current builder. + + + + A class used to create User commands. + + + + + The name of this command. + + + + + The discription of this command. + + + + + Gets or sets the type for this command. + + Represents a generic invite object. diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index e0d10af87..70b430bfc 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -20,7 +20,11 @@ namespace Discord /// Gets or sets the discription of this command. /// public Optional Description { get; set; } - + + /// + /// Gets or sets the type for this command. + /// + public Optional Type { get; set; } /// /// Gets or sets the options for this command. diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs new file mode 100644 index 000000000..23b00f2a2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + public enum ApplicationCommandType : byte + { + Slash = 1, + User = 2, + Message = 3 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index eb61c539f..ab4b3eac0 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -16,6 +16,11 @@ namespace Discord /// ulong ApplicationId { get; } + /// + /// The type of the command + /// + ApplicationCommandType Type { get; } + /// /// The name of the command. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs new file mode 100644 index 000000000..792d7d19f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to build slash commands. + /// + public class MessageCommandBuilder + { + /// + /// Returns the maximun length a commands name allowed by Discord + /// + public const int MaxNameLength = 32; + /// + /// Returns the maximum length of a commands description allowed by Discord. + /// + public const int MaxDescriptionLength = 0; + + /// + /// The name of this slash command. + /// + public string Name + { + get + { + return _name; + } + set + { + Preconditions.NotNullOrEmpty(value, nameof(Name)); + Preconditions.AtLeast(value.Length, 3, nameof(Name)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); + + // Discord updated the docs, this regex prevents special characters like @!$%(... etc, + // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand + if (!Regex.IsMatch(value, @"^[\w -]{3,32}$")) + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!"); + + _name = value; + } + } + + /// + /// A 1-100 length description of this slash command + /// + public string Description + { + get + { + return _description; + } + set + { + Preconditions.Equals(value, ""); + + _description = value; + } + } + + private string _name { get; set; } + private string _description { get; set; } + + /// + /// Build the current builder into a class. + /// + /// A that can be used to create user commands over rest. + public MessageCommandCreationProperties Build() + { + MessageCommandCreationProperties props = new MessageCommandCreationProperties() + { + Name = this.Name, + Description = this.Description, + Type=ApplicationCommandType.Message + }; + + return props; + + } + + /// + /// Sets the field name. + /// + /// The value to set the field name to. + /// + /// The current builder. + /// + public MessageCommandBuilder WithName(string name) + { + this.Name = name; + return this; + } + + /// + /// Sets the description of the current command. + /// + /// The description of this command. + /// The current builder. + public MessageCommandBuilder WithDescription(string description) + { + this.Description = description; + return this; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs new file mode 100644 index 000000000..7c7dac593 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to create Message commands. + /// + public class MessageCommandCreationProperties + { + /// + /// The name of this command. + /// + public string Name { get; set; } + + /// + /// The discription of this command. + /// + public string Description { get; set; } + + + /// + /// Gets or sets the type for this command. + /// + public ApplicationCommandType Type { get; set; } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs index e0afde50c..933f511e1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs @@ -102,7 +102,8 @@ namespace Discord { Name = this.Name, Description = this.Description, - DefaultPermission = this.DefaultPermission + DefaultPermission = this.DefaultPermission, + Type = ApplicationCommandType.Slash }; if (this.Options != null && this.Options.Any()) diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs index 7f4a3a62d..3021d7a2c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs @@ -21,6 +21,10 @@ namespace Discord /// public string Description { get; set; } + /// + /// Gets or sets the type for this command. + /// + public ApplicationCommandType Type { get; set; } /// /// Gets or sets the options for this command. diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs new file mode 100644 index 000000000..0dc6526ba --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to build slash commands. + /// + public class UserCommandBuilder + { + /// + /// Returns the maximun length a commands name allowed by Discord + /// + public const int MaxNameLength = 32; + /// + /// Returns the maximum length of a commands description allowed by Discord. + /// + public const int MaxDescriptionLength = 0; + + /// + /// The name of this slash command. + /// + public string Name + { + get + { + return _name; + } + set + { + Preconditions.NotNullOrEmpty(value, nameof(Name)); + Preconditions.AtLeast(value.Length, 3, nameof(Name)); + Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); + + // Discord updated the docs, this regex prevents special characters like @!$%(... etc, + // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand + if (!Regex.IsMatch(value, @"^[\w -]{3,32}$")) + throw new ArgumentException("Command name cannot contain any special characters or whitespaces!"); + + _name = value; + } + } + + /// + /// A 1-100 length description of this slash command + /// + public string Description + { + get + { + return _description; + } + set + { + Preconditions.Equals(value, ""); + + _description = value; + } + } + + private string _name { get; set; } + private string _description { get; set; } + + /// + /// Build the current builder into a class. + /// + /// A that can be used to create user commands over rest. + public UserCommandCreationProperties Build() + { + UserCommandCreationProperties props = new UserCommandCreationProperties() + { + Name = this.Name, + Description = this.Description, + Type=ApplicationCommandType.User + }; + + return props; + + } + + /// + /// Sets the field name. + /// + /// The value to set the field name to. + /// + /// The current builder. + /// + public UserCommandBuilder WithName(string name) + { + this.Name = name; + return this; + } + + /// + /// Sets the description of the current command. + /// + /// The description of this command. + /// The current builder. + public UserCommandBuilder WithDescription(string description) + { + this.Description = description; + return this; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs new file mode 100644 index 000000000..323b2ce1d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to create User commands. + /// + public class UserCommandCreationProperties + { + /// + /// The name of this command. + /// + public string Name { get; set; } + + /// + /// The discription of this command. + /// + public string Description { get; set; } + + + /// + /// Gets or sets the type for this command. + /// + public ApplicationCommandType Type { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index 2e66245d7..ff72429a3 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -13,6 +13,9 @@ namespace Discord.API.Rest [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } + [JsonProperty("description")] public string Description { get; set; } @@ -23,11 +26,12 @@ namespace Discord.API.Rest public Optional DefaultPermission { get; set; } public CreateApplicationCommandParams() { } - public CreateApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null) + public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null) { this.Name = name; this.Description = description; this.Options = Optional.Create(options); + this.Type = type; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs index 2ed9466c0..29a6ff796 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -15,6 +15,9 @@ namespace Discord.API.Rest [JsonProperty("description")] public Optional Description { get; set; } + [JsonProperty("type")] + public Optional Type { get; set; } + [JsonProperty("options")] public Optional Options { get; set; } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index 920e0bf60..c6c81ee75 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -3732,6 +3732,9 @@ + + + @@ -3831,6 +3834,32 @@ The modified command. + + + + + + Modifies this . + + The delegate containing the properties to modify the command with. + The options to be used when sending the request. + + The modified command. + + + + + + + + Modifies this . + + The delegate containing the properties to modify the command with. + The options to be used when sending the request. + + The modified command. + + Represents a Rest-based guild command. @@ -3886,6 +3915,74 @@ . + + + Represents a Rest-based guild command. + + + + + The guild Id where this command originates. + + + + + + + + Modifies this . + + The delegate containing the properties to modify the command with. + The options to be used when sending the request. + + The modified command + + + + + Gets the guild that this slash command resides in. + + if you want the approximate member and presence counts for the guild, otherwise . + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a + . + + + + + Represents a Rest-based guild command. + + + + + The guild Id where this command originates. + + + + + + + + Modifies this . + + The delegate containing the properties to modify the command with. + The options to be used when sending the request. + + The modified command + + + + + Gets the guild that this slash command resides in. + + if you want the approximate member and presence counts for the guild, otherwise . + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a + . + + diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 3e9ed4393..350a156c7 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -235,6 +235,7 @@ namespace Discord.API options.BucketId = bucketId; string json = payload != null ? SerializeJson(payload) : null; + Console.WriteLine($"Sending JSON....\n{json}"); var request = new JsonRestRequest(RestClient, method, endpoint, json, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } @@ -1082,6 +1083,18 @@ namespace Discord.API return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); } + public async Task ModifyGlobalApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task ModifyGlobalApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options)).ConfigureAwait(false); + } public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); @@ -1095,6 +1108,46 @@ namespace Discord.API return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); } + public async Task CreateGlobalApplicationUserCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); + Preconditions.Equals(command.Description, ""); + + options = RequestOptions.CreateOrClone(options); + + + + return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + public async Task CreateGlobalApplicationMessageCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); + Preconditions.Equals(command.Description, ""); + + options = RequestOptions.CreateOrClone(options); + + + + return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); + } + + public async Task BulkOverwriteGlobalApplicationUserCommands(CreateApplicationCommandParams[] commands, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); + } + + public async Task BulkOverwriteGlobalApplicationMessageCommands(CreateApplicationCommandParams[] commands, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/commands", commands, new BucketIds(), options: options)).ConfigureAwait(false); + } public async Task GetGuildApplicationCommandsAsync(ulong guildId, RequestOptions options = null) { @@ -1163,6 +1216,87 @@ namespace Discord.API return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); } + public async Task CreateGuildApplicationUserCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); + + } + public async Task ModifyGuildApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + try + { + return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); + } + catch (HttpException x) + { + if (x.HttpCode == HttpStatusCode.BadRequest) + { + var json = (x.Request as JsonRestRequest).Json; + throw new ApplicationCommandException(json, x); + } + + // Re-throw the http exception + throw; + } + } + public async Task BulkOverwriteGuildApplicationUserCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); + } + + public async Task CreateGuildApplicationMessageCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); + + } + public async Task ModifyGuildApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + try + { + return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); + } + catch (HttpException x) + { + if (x.HttpCode == HttpStatusCode.BadRequest) + { + var json = (x.Request as JsonRestRequest).Json; + throw new ApplicationCommandException(json, x); + } + + // Re-throw the http exception + throw; + } + } + + public async Task BulkOverwriteGuildApplicationMessageCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); + } + //Interaction Responses public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index a8849525e..cb28d19b6 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,3 +1,4 @@ +//using Discord.Rest.Entities.Interactions; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -111,10 +112,26 @@ namespace Discord.Rest => InteractionHelper.CreateGlobalCommand(this, properties, options); public Task CreateGlobalCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, func, options); + public Task CreateGlobalUserCommand(UserCommandCreationProperties properties, RequestOptions options = null) + => InteractionHelper.CreateGlobalUserCommand(this, properties, options); + public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) + => InteractionHelper.CreateGlobalUserCommand(this, func, options); + public Task CreateGlobalMessageCommand(MessageCommandCreationProperties properties, RequestOptions options = null) + => InteractionHelper.CreateGlobalMessageCommand(this, properties, options); + public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) + => InteractionHelper.CreateGlobalMessageCommand(this, func, options); public Task CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, func, options); + public Task CreateGuildUserCommand(UserCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + => InteractionHelper.CreateGuildUserCommand(this, guildId, properties, options); + public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) + => InteractionHelper.CreateGuildUserCommand(this, guildId, func, options); + public Task CreateGuildMessageCommand(MessageCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + => InteractionHelper.CreateGuildMessageCommand(this, guildId, properties, options); + public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) + => InteractionHelper.CreateGuildMessageCommand(this, guildId, func, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); public Task> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index e4df0d75e..d5006a6a9 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -1,6 +1,7 @@ using Discord.API; using Discord.API.Rest; using Discord.Net; +//using Discord.Rest.Entities.Interactions; using System; using System.Collections.Generic; using System.Linq; @@ -63,6 +64,7 @@ namespace Discord.Rest { Name = arg.Name, Description = arg.Description, + Type= arg.Type, Options = arg.Options.IsSpecified ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -94,6 +96,7 @@ namespace Discord.Rest { Name = arg.Name, Description = arg.Description, + Type = arg.Type, Options = arg.Options.IsSpecified ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -129,6 +132,7 @@ namespace Discord.Rest { Name = arg.Name, Description = arg.Description, + Type = arg.Type, Options = arg.Options.IsSpecified ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -173,6 +177,7 @@ namespace Discord.Rest { Name = args.Name, Description = args.Description, + Type = args.Type, Options = args.Options.IsSpecified ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -195,6 +200,174 @@ namespace Discord.Rest await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); } + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + { + var args = new UserCommandCreationProperties(); + func(args); + return await CreateGlobalUserCommand(client, args, options).ConfigureAwait(false); + } + + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, UserCommandCreationProperties arg, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.Equals(arg.Description, ""); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + var cmd = await client.ApiClient.CreateGlobalApplicationUserCommandAsync(model, options).ConfigureAwait(false); + return RestGlobalUserCommand.Create(client, cmd); + } + + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + { + var args = new MessageCommandCreationProperties(); + func(args); + return await CreateGlobalMessageCommand(client, args, options).ConfigureAwait(false); + } + + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, MessageCommandCreationProperties arg, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.Equals(arg.Description, ""); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + var cmd = await client.ApiClient.CreateGlobalApplicationMessageCommandAsync(model, options).ConfigureAwait(false); + return RestGlobalMessageCommand.Create(client, cmd); + } + + public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, UserCommandCreationProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + List models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); + Preconditions.Equals(arg.Type, ApplicationCommandType.User); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + models.Add(model); + } + + var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationUserCommands(models.ToArray(), options); + + return apiModels.Select(x => RestGlobalUserCommand.Create(client, x)).ToArray(); + } + public static async Task ModifyGlobalUserCommand(BaseDiscordClient client, RestGlobalUserCommand command, + Action func, RequestOptions options = null) + { + ApplicationCommandProperties args = new ApplicationCommandProperties(); + func(args); + + if (args.Name.IsSpecified) + { + Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + } + if (args.Description.IsSpecified) + { + Preconditions.Equals(args.Description.Value, ""); + } + + var model = new Discord.API.Rest.ModifyApplicationCommandParams() + { + Name = args.Name, + Description = args.Description + }; + + var msg = await client.ApiClient.ModifyGlobalApplicationUserCommandAsync(model, command.Id, options).ConfigureAwait(false); + command.Update(msg); + return command; + } + + public static async Task DeleteGlobalUserCommand(BaseDiscordClient client, RestGlobalUserCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); + } + + public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, MessageCommandCreationProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + List models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); + Preconditions.Equals(arg.Type, ApplicationCommandType.Message); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + models.Add(model); + } + + var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationMessageCommands(models.ToArray(), options); + + return apiModels.Select(x => RestGlobalMessageCommand.Create(client, x)).ToArray(); + } + public static async Task ModifyGlobalMessageCommand(BaseDiscordClient client, RestGlobalMessageCommand command, + Action func, RequestOptions options = null) + { + ApplicationCommandProperties args = new ApplicationCommandProperties(); + func(args); + + if (args.Name.IsSpecified) + { + Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + } + if (args.Description.IsSpecified) + { + Preconditions.Equals(args.Description.Value, ""); + } + + var model = new Discord.API.Rest.ModifyApplicationCommandParams() + { + Name = args.Name, + Description = args.Description + }; + + var msg = await client.ApiClient.ModifyGlobalApplicationMessageCommandAsync(model, command.Id, options).ConfigureAwait(false); + command.Update(msg); + return command; + } + + public static async Task DeleteGlobalMessageCommand(BaseDiscordClient client, RestGlobalMessageCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); + } + // Guild Commands public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) @@ -231,6 +404,7 @@ namespace Discord.Rest { Name = args.Name, Description = args.Description, + Type = args.Type, Options = args.Options.IsSpecified ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -269,6 +443,7 @@ namespace Discord.Rest { Name = args.Name, Description = args.Description, + Type = args.Type, Options = args.Options.IsSpecified ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() : Optional.Unspecified, @@ -290,6 +465,177 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + { + var args = new UserCommandCreationProperties(); + func(args); + return await CreateGuildUserCommand(client, guildId, args, options).ConfigureAwait(false); + } + + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties arg, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.Equals(arg.Description, ""); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + var cmd = await client.ApiClient.CreateGuildApplicationUserCommandAsync(model, guildId, options).ConfigureAwait(false); + return RestGuildUserCommand.Create(client, cmd, guildId); + } + + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + { + var args = new MessageCommandCreationProperties(); + func(args); + return await CreateGuildMessageCommand(client, guildId, args, options).ConfigureAwait(false); + } + + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties arg, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.Equals(arg.Description, ""); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + var cmd = await client.ApiClient.CreateGuildApplicationMessageCommandAsync(model, guildId, options).ConfigureAwait(false); + return RestGuildMessageCommand.Create(client, cmd, guildId); + } + + public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + List models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); + Preconditions.Equals(arg.Type, ApplicationCommandType.User); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + models.Add(model); + } + + var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationUserCommands(guildId, models.ToArray(), options); + + return apiModels.Select(x => RestGuildUserCommand.Create(client, x, guildId)).ToArray(); + } + public static async Task ModifyGuildUserCommand(BaseDiscordClient client, RestGuildUserCommand command, + Action func, RequestOptions options = null) + { + ApplicationCommandProperties args = new ApplicationCommandProperties(); + func(args); + + if (args.Name.IsSpecified) + { + Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + } + if (args.Description.IsSpecified) + { + Preconditions.Equals(args.Description.Value, ""); + } + + var model = new Discord.API.Rest.ModifyApplicationCommandParams() + { + Name = args.Name, + Description = args.Description, + Type=args.Type + }; + + var msg = await client.ApiClient.ModifyGuildApplicationUserCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); + command.Update(msg); + return command; + } + + public static async Task DeleteGuildUserCommand(BaseDiscordClient client, ulong guildId, RestGuildUserCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); + } + + public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties[] args, RequestOptions options = null) + { + Preconditions.NotNull(args, nameof(args)); + + List models = new List(); + + foreach (var arg in args) + { + Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); + Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); + Preconditions.Equals(arg.Type, ApplicationCommandType.Message); + + var model = new CreateApplicationCommandParams() + { + Name = arg.Name, + Description = arg.Description, + Type = arg.Type + }; + + models.Add(model); + } + + var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationMessageCommands(guildId, models.ToArray(), options); + + return apiModels.Select(x => RestGuildMessageCommand.Create(client, x, guildId)).ToArray(); + } + public static async Task ModifyGuildMessageCommand(BaseDiscordClient client, RestGuildMessageCommand command, + Action func, RequestOptions options = null) + { + ApplicationCommandProperties args = new ApplicationCommandProperties(); + func(args); + + if (args.Name.IsSpecified) + { + Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); + Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + } + if (args.Description.IsSpecified) + { + Preconditions.Equals(args.Description.Value, ""); + } + + var model = new Discord.API.Rest.ModifyApplicationCommandParams() + { + Name = args.Name, + Description = args.Description, + Type = args.Type + }; + + var msg = await client.ApiClient.ModifyGuildApplicationMessageCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); + command.Update(msg); + return command; + } + + public static async Task DeleteGuildMessageCommand(BaseDiscordClient client, ulong guildId, RestGuildMessageCommand command, RequestOptions options = null) + { + Preconditions.NotNull(command, nameof(command)); + Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); + + await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); + } + + public static async Task ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action func, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index e1a854187..2744c7967 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -16,6 +16,9 @@ namespace Discord.Rest /// public ulong ApplicationId { get; private set; } + /// + public ApplicationCommandType Type { get; private set; } + /// public string Name { get; private set; } @@ -47,13 +50,30 @@ namespace Discord.Rest internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, RestApplicationCommandType type, ulong guildId = 0) { - if (type == RestApplicationCommandType.GlobalCommand) - return RestGlobalCommand.Create(client, model); - - if (type == RestApplicationCommandType.GuildCommand) - return RestGuildCommand.Create(client, model, guildId); - - return null; + switch (type) + { + case RestApplicationCommandType.GlobalCommand: + return RestGlobalCommand.Create(client, model); + break; + case RestApplicationCommandType.GlobalUserCommand: + return RestGlobalUserCommand.Create(client, model); + break; + case RestApplicationCommandType.GlobalMessageCommand: + return RestGlobalMessageCommand.Create(client, model); + break; + case RestApplicationCommandType.GuildCommand: + return RestGuildCommand.Create(client, model, guildId); + break; + case RestApplicationCommandType.GuildUserCommand: + return RestGuildUserCommand.Create(client, model, guildId); + break; + case RestApplicationCommandType.GuildMessageCommand: + return RestGuildMessageCommand.Create(client, model, guildId); + break; + default: + return null; + break; + } } internal virtual void Update(Model model) diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs index 96ba07053..cf2fb1110 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs @@ -15,10 +15,14 @@ namespace Discord.Rest /// Specifies that this command is a Global command. /// GlobalCommand, + GlobalUserCommand, + GlobalMessageCommand, /// /// Specifies that this command is a Guild specific command. /// - GuildCommand + GuildCommand, + GuildUserCommand, + GuildMessageCommand } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs new file mode 100644 index 000000000..2c0ed222c --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + public class RestGlobalMessageCommand : RestApplicationCommand + { + internal RestGlobalMessageCommand(BaseDiscordClient client, ulong id) + : base(client, id) + { + this.CommandType = RestApplicationCommandType.GlobalMessageCommand; +} + + internal static RestGlobalMessageCommand Create(BaseDiscordClient client, Model model) + { + var entity = new RestGlobalMessageCommand(client, model.Id); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGlobalMessageCommand(Discord, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command. + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + => await InteractionHelper.ModifyGlobalMessageCommand(Discord, this, func, options).ConfigureAwait(false); + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs new file mode 100644 index 000000000..981686379 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + public class RestGlobalUserCommand : RestApplicationCommand + { + internal RestGlobalUserCommand(BaseDiscordClient client, ulong id) + : base(client, id) + { + this.CommandType = RestApplicationCommandType.GlobalUserCommand; + } + + internal static RestGlobalUserCommand Create(BaseDiscordClient client, Model model) + { + var entity = new RestGlobalUserCommand(client, model.Id); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGlobalUserCommand(Discord, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command. + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + => await InteractionHelper.ModifyGlobalUserCommand(Discord, this, func, options).ConfigureAwait(false); + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs new file mode 100644 index 000000000..e057b2fe3 --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based guild command. + /// + public class RestGuildMessageCommand : RestApplicationCommand + { + /// + /// The guild Id where this command originates. + /// + public ulong GuildId { get; private set; } + + internal RestGuildMessageCommand(BaseDiscordClient client, ulong id, ulong guildId) + : base(client, id) + { + this.CommandType = RestApplicationCommandType.GuildMessageCommand; + this.GuildId = guildId; + } + + internal static RestGuildMessageCommand Create(BaseDiscordClient client, Model model, ulong guildId) + { + var entity = new RestGuildMessageCommand(client, model.Id, guildId); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGuildMessageCommand(Discord, GuildId, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + => await InteractionHelper.ModifyGuildMessageCommand(Discord, this, func, options).ConfigureAwait(false); + + /// + /// Gets the guild that this slash command resides in. + /// + /// if you want the approximate member and presence counts for the guild, otherwise . + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a + /// . + /// + public Task GetGuild(bool withCounts = false, RequestOptions options = null) + => ClientHelper.GetGuildAsync(this.Discord, this.GuildId, withCounts, options); + } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs new file mode 100644 index 000000000..02dc173db --- /dev/null +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommand; + +namespace Discord.Rest +{ + /// + /// Represents a Rest-based guild command. + /// + public class RestGuildUserCommand : RestApplicationCommand + { + /// + /// The guild Id where this command originates. + /// + public ulong GuildId { get; private set; } + + internal RestGuildUserCommand(BaseDiscordClient client, ulong id, ulong guildId) + : base(client, id) + { + this.CommandType = RestApplicationCommandType.GuildUserCommand; + this.GuildId = guildId; + } + + internal static RestGuildUserCommand Create(BaseDiscordClient client, Model model, ulong guildId) + { + var entity = new RestGuildUserCommand(client, model.Id, guildId); + entity.Update(model); + return entity; + } + + /// + public override async Task DeleteAsync(RequestOptions options = null) + => await InteractionHelper.DeleteGuildUserCommand(Discord, GuildId, this).ConfigureAwait(false); + + /// + /// Modifies this . + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// The modified command + /// + public async Task ModifyAsync(Action func, RequestOptions options = null) + => await InteractionHelper.ModifyGuildUserCommand(Discord, this, func, options).ConfigureAwait(false); + + /// + /// Gets the guild that this slash command resides in. + /// + /// if you want the approximate member and presence counts for the guild, otherwise . + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a + /// . + /// + public Task GetGuild(bool withCounts = false, RequestOptions options = null) + => ClientHelper.GetGuildAsync(this.Discord, this.GuildId, withCounts, options); + } +} diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index d22c00429..bec25d285 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3668,6 +3668,9 @@ + + + diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs index 81decb4be..9f53af562 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs @@ -20,6 +20,9 @@ namespace Discord.WebSocket /// public string Name { get; private set; } + /// + public ApplicationCommandType Type { get; private set; } + /// public string Description { get; private set; } From 9fb64e6eb5abc9e6c1f9b3a4cd6016ebbd06c615 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Fri, 13 Aug 2021 14:56:37 -0400 Subject: [PATCH 02/14] oops --- src/Discord.Net.Core/Discord.Net.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index babda86b7..f0be10059 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -8,7 +8,7 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 Discord.Net.Labs.Core - 3.3.1.0 + 3.0.0-pre Discord.Net.Labs.Core https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png From d48874437852b7ad65897a0fa80ee0efac655de1 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sat, 14 Aug 2021 07:57:04 -0400 Subject: [PATCH 03/14] handling context menu usage events --- .../ApplicationCommandInteractionData.cs | 3 + ...plicationCommandInteractionDataResolved.cs | 2 + .../Discord.Net.WebSocket.xml | 64 +++++++ .../SocketApplicationMessageCommand.cs | 165 ++++++++++++++++++ .../SocketApplicationMessageCommandData.cs | 140 +++++++++++++++ .../SocketApplicationUserCommand.cs | 165 ++++++++++++++++++ .../SocketApplicationUserCommandData.cs | 113 ++++++++++++ .../Entities/Interaction/SocketInteraction.cs | 14 ++ 8 files changed, 666 insertions(+) create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs index c0ced154a..ed86b0b4a 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs @@ -17,5 +17,8 @@ namespace Discord.API [JsonProperty("resolved")] public Optional Resolved { get; set; } + [JsonProperty("type")] + public Optional Type { get; set; } + } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs index 46eca6b71..5b4b83e23 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionDataResolved.cs @@ -16,5 +16,7 @@ namespace Discord.API [JsonProperty("roles")] public Optional> Roles { get; set; } + [JsonProperty("messages")] + public Optional> Messages { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index bec25d285..c2af7b358 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3599,6 +3599,70 @@ + + + Represents a Websocket-based slash command received over the gateway. + + + + + The data associated with this interaction. + + + + + + + + + + + Acknowledges this interaction with the . + + + A task that represents the asynchronous operation of acknowledging the interaction. + + + + + Represents the data tied with the interaction. + + + + + + + + Represents a Websocket-based slash command received over the gateway. + + + + + The data associated with this interaction. + + + + + + + + + + + Acknowledges this interaction with the . + + + A task that represents the asynchronous operation of acknowledging the interaction. + + + + + Represents the data tied with the interaction. + + + + + Represents a Websocket-based interaction type for Message Components. diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs new file mode 100644 index 000000000..0a483b2bf --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs @@ -0,0 +1,165 @@ +using Discord.Rest; +using System; +using System.Linq; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based slash command received over the gateway. + /// + public class SocketApplicationMessageCommand : SocketSlashCommand + { + /// + /// The data associated with this interaction. + /// + new public SocketApplicationMessageCommandData Data { get; } + + internal SocketApplicationMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model, channel) + { + var dataModel = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (this.Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketApplicationMessageCommandData.Create(client, dataModel, model.Id, guildId); + } + + new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketApplicationMessageCommand(client, model, channel); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + var data = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + this.Data.Update(data); + + base.Update(model); + } + + /// + public override async Task RespondAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + + if (Discord.AlwaysAcknowledgeInteractions) + { + await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); + return; + } + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + TTS = isTTS ? true : Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + } + }; + + if (ephemeral) + response.Data.Value.Flags = 64; + + await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); + } + + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = 64; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + /// Acknowledges this interaction with the . + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public override Task DeferAsync(RequestOptions options = null) + { + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + }; + + return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs new file mode 100644 index 000000000..e548e4cf7 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data tied with the interaction. + /// + public class SocketApplicationMessageCommandData : SocketEntity, IApplicationCommandInteractionData + { + /// + public string Name { get; private set; } + public SocketMessage Message { get; private set; } + + internal Dictionary guildMembers { get; private set; } + = new Dictionary(); + internal Dictionary users { get; private set; } + = new Dictionary(); + internal Dictionary channels { get; private set; } + = new Dictionary(); + internal Dictionary roles { get; private set; } + = new Dictionary(); + + IReadOnlyCollection IApplicationCommandInteractionData.Options => throw new System.NotImplementedException(); + + private ulong? guildId; + + private ApplicationCommandType Type; + + internal SocketApplicationMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model.Id) + { + this.guildId = guildId; + + this.Type = (ApplicationCommandType)model.Type; + + if (model.Resolved.IsSpecified) + { + var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; + + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); + + this.users.Add(ulong.Parse(user.Key), socketUser); + } + } + + if (resolved.Channels.IsSpecified) + { + foreach (var channel in resolved.Channels.Value) + { + SocketChannel socketChannel = guild != null + ? guild.GetChannel(channel.Value.Id) + : Discord.GetChannel(channel.Value.Id); + + if (socketChannel == null) + { + var channelModel = guild != null + ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() + : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, Discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); + } + + Discord.State.AddChannel(socketChannel); + this.channels.Add(ulong.Parse(channel.Key), socketChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + member.Value.User = resolved.Users.Value[member.Key]; + var user = guild.AddOrUpdateUser(member.Value); + this.guildMembers.Add(ulong.Parse(member.Key), user); + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var socketRole = guild.AddOrUpdateRole(role.Value); + this.roles.Add(ulong.Parse(role.Key), socketRole); + } + } + + if (resolved.Messages.IsSpecified) + { + foreach (var msg in resolved.Messages.Value) + { + var channel = client.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; + + SocketUser author; + if (guild != null) + { + if (msg.Value.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, client.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); + else + author = guild.GetUser(msg.Value.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); + + if (channel == null) + { + if (!msg.Value.GuildId.IsSpecified) // assume it is a DM + { + channel = client.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, client.State); + } + } + + this.Message = SocketMessage.Create(client, client.State, author, channel, msg.Value); + } + } + } + } + + internal static SocketApplicationMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketApplicationMessageCommandData(client, model, guildId); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + this.Name = model.Name; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs new file mode 100644 index 000000000..37bdd40bd --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs @@ -0,0 +1,165 @@ +using Discord.Rest; +using System; +using System.Linq; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + /// + /// Represents a Websocket-based slash command received over the gateway. + /// + public class SocketApplicationUserCommand : SocketSlashCommand + { + /// + /// The data associated with this interaction. + /// + new public SocketApplicationUserCommandData Data { get; } + + internal SocketApplicationUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model, channel) + { + var dataModel = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (this.Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketApplicationUserCommandData.Create(client, dataModel, model.Id, guildId); + } + + new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketApplicationUserCommand(client, model, channel); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + var data = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + this.Data.Update(data); + + base.Update(model); + } + + /// + public override async Task RespondAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + + if (Discord.AlwaysAcknowledgeInteractions) + { + await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); + return; + } + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + TTS = isTTS ? true : Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + } + }; + + if (ephemeral) + response.Data.Value.Flags = 64; + + await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); + } + + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = 64; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + /// Acknowledges this interaction with the . + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public override Task DeferAsync(RequestOptions options = null) + { + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + }; + + return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs new file mode 100644 index 000000000..0c5a540c7 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + /// + /// Represents the data tied with the interaction. + /// + public class SocketApplicationUserCommandData : SocketEntity, IApplicationCommandInteractionData + { + /// + public string Name { get; private set; } + + public SocketUser Member { get; private set; } + + internal Dictionary guildMembers { get; private set; } + = new Dictionary(); + internal Dictionary users { get; private set; } + = new Dictionary(); + internal Dictionary channels { get; private set; } + = new Dictionary(); + internal Dictionary roles { get; private set; } + = new Dictionary(); + + IReadOnlyCollection IApplicationCommandInteractionData.Options => throw new System.NotImplementedException(); + + private ulong? guildId; + + private ApplicationCommandType Type; + + internal SocketApplicationUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model.Id) + { + this.guildId = guildId; + + this.Type = (ApplicationCommandType)model.Type; + + if (model.Resolved.IsSpecified) + { + var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; + + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); + + this.users.Add(ulong.Parse(user.Key), socketUser); + } + } + + if (resolved.Channels.IsSpecified) + { + foreach (var channel in resolved.Channels.Value) + { + SocketChannel socketChannel = guild != null + ? guild.GetChannel(channel.Value.Id) + : Discord.GetChannel(channel.Value.Id); + + if (socketChannel == null) + { + var channelModel = guild != null + ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() + : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, Discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); + } + + Discord.State.AddChannel(socketChannel); + this.channels.Add(ulong.Parse(channel.Key), socketChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + member.Value.User = resolved.Users.Value[member.Key]; + var user = guild.AddOrUpdateUser(member.Value); + this.guildMembers.Add(ulong.Parse(member.Key), user); + this.Member = user; + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var socketRole = guild.AddOrUpdateRole(role.Value); + this.roles.Add(ulong.Parse(role.Key), socketRole); + } + } + } + } + + internal static SocketApplicationUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketApplicationUserCommandData(client, model, guildId); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + this.Name = model.Name; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 4b2e3baec..01b3874f7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -2,6 +2,7 @@ using Discord.Rest; using System; using System.Threading.Tasks; using Model = Discord.API.Interaction; +using DataModel = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { @@ -61,6 +62,19 @@ namespace Discord.WebSocket internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { if (model.Type == InteractionType.ApplicationCommand) + if(model.ApplicationId != null) + { + var dataModel = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + if(dataModel != null) + { + if (dataModel.Type.Equals(ApplicationCommandType.User)) + return SocketApplicationUserCommand.Create(client, model, channel); + if (dataModel.Type.Equals(ApplicationCommandType.Message)) + return SocketApplicationMessageCommand.Create(client, model, channel); + } + } return SocketSlashCommand.Create(client, model, channel); if (model.Type == InteractionType.MessageComponent) return SocketMessageComponent.Create(client, model, channel); From 66a8f9a684d14f29551c18e8af8e4f29f0e2c4b1 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sat, 14 Aug 2021 08:24:02 -0400 Subject: [PATCH 04/14] debugging line removed. --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 350a156c7..42cde4cfc 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -235,7 +235,7 @@ namespace Discord.API options.BucketId = bucketId; string json = payload != null ? SerializeJson(payload) : null; - Console.WriteLine($"Sending JSON....\n{json}"); + var request = new JsonRestRequest(RestClient, method, endpoint, json, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } From 8c1a26f6978414b1a4aff3fa9ccff5c4f75f7321 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sat, 14 Aug 2021 11:10:22 -0400 Subject: [PATCH 05/14] Moved SocketCommandBase in front of slash/user/message commands --- .../Discord.Net.WebSocket.xml | 114 ++++++------ .../SocketApplicationMessageCommand.cs | 126 +------------- .../SocketApplicationMessageCommandData.cs | 2 +- .../SocketApplicationUserCommand.cs | 128 +------------- .../SocketApplicationUserCommandData.cs | 2 +- .../Slash Commands/SocketSlashCommand.cs | 130 +------------- .../SocketApplicationCommand.cs | 0 .../SocketApplicationCommandChoice.cs | 0 .../SocketApplicationCommandOption.cs | 0 .../SocketBaseCommand/SocketCommandBase.cs | 164 ++++++++++++++++++ .../SocketCommandBaseData.cs | 145 ++++++++++++++++ .../SocketCommandBaseDataOption.cs | 130 ++++++++++++++ .../Entities/Interaction/SocketInteraction.cs | 6 +- 13 files changed, 502 insertions(+), 445 deletions(-) rename src/Discord.Net.WebSocket/Entities/Interaction/{Slash Commands => SocketBaseCommand}/SocketApplicationCommand.cs (100%) rename src/Discord.Net.WebSocket/Entities/Interaction/{Slash Commands => SocketBaseCommand}/SocketApplicationCommandChoice.cs (100%) rename src/Discord.Net.WebSocket/Entities/Interaction/{Slash Commands => SocketBaseCommand}/SocketApplicationCommandOption.cs (100%) create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index c2af7b358..742bebac0 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3609,23 +3609,9 @@ The data associated with this interaction. - - - - - - - - - Acknowledges this interaction with the . - - - A task that represents the asynchronous operation of acknowledging the interaction. - - - Represents the data tied with the interaction. + Represents the data tied with the interaction. @@ -3641,23 +3627,9 @@ The data associated with this interaction. - - - - - - - - - Acknowledges this interaction with the . - - - A task that represents the asynchronous operation of acknowledging the interaction. - - - Represents the data tied with the interaction. + Represents the data tied with the interaction. @@ -3721,6 +3693,48 @@ The value(s) of a interaction response. + + + Represents a Websocket-based slash command received over the gateway. + + + + + The data associated with this interaction. + + + + + Represents the data tied with the interaction. + + + + + + + + The 's received with this interaction. + + + + + Represents a Websocket-based recieved by the gateway + + + + + + + + + + + + + + The sub command options received for this sub command group. + + Represends a Websocket-based recieved over the gateway. @@ -3798,23 +3812,18 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. - - - Represents a Websocket-based slash command received over the gateway. - - - + The data associated with this interaction. - + - + - + Acknowledges this interaction with the . @@ -3822,34 +3831,13 @@ A task that represents the asynchronous operation of acknowledging the interaction. - - - Represents the data tied with the interaction. - - - - - - - - The 's received with this interaction. - - - - - Represents a Websocket-based recieved by the gateway - - - - - - + - + - + The sub command options received for this sub command group. diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs index 0a483b2bf..828ed14bb 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs @@ -10,7 +10,7 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationMessageCommand : SocketSlashCommand + public class SocketApplicationMessageCommand : SocketCommandBase { /// /// The data associated with this interaction. @@ -37,129 +37,5 @@ namespace Discord.WebSocket entity.Update(model); return entity; } - - internal override void Update(Model model) - { - var data = model.Data.IsSpecified ? - (DataModel)model.Data.Value - : null; - - this.Data.Update(data); - - base.Update(model); - } - - /// - public override async Task RespondAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - - if (Discord.AlwaysAcknowledgeInteractions) - { - await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); - return; - } - - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) - { - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) - { - throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); - } - - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) - { - throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); - } - } - - var response = new API.InteractionResponse - { - Type = InteractionResponseType.ChannelMessageWithSource, - Data = new API.InteractionCallbackData - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - TTS = isTTS ? true : Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - } - }; - - if (ephemeral) - response.Data.Value.Flags = 64; - - await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); - } - - /// - public override async Task FollowupAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - }; - - if (ephemeral) - args.Flags = 64; - - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); - } - - /// - /// Acknowledges this interaction with the . - /// - /// - /// A task that represents the asynchronous operation of acknowledging the interaction. - /// - public override Task DeferAsync(RequestOptions options = null) - { - var response = new API.InteractionResponse - { - Type = InteractionResponseType.DeferredChannelMessageWithSource, - }; - - return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); - } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs index e548e4cf7..ead330ef6 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs @@ -6,7 +6,7 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// public class SocketApplicationMessageCommandData : SocketEntity, IApplicationCommandInteractionData { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs index 37bdd40bd..603a11397 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs @@ -10,7 +10,7 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationUserCommand : SocketSlashCommand + public class SocketApplicationUserCommand : SocketCommandBase { /// /// The data associated with this interaction. @@ -36,130 +36,6 @@ namespace Discord.WebSocket var entity = new SocketApplicationUserCommand(client, model, channel); entity.Update(model); return entity; - } - - internal override void Update(Model model) - { - var data = model.Data.IsSpecified ? - (DataModel)model.Data.Value - : null; - - this.Data.Update(data); - - base.Update(model); - } - - /// - public override async Task RespondAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - - if (Discord.AlwaysAcknowledgeInteractions) - { - await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); - return; - } - - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) - { - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) - { - throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); - } - - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) - { - throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); - } - } - - var response = new API.InteractionResponse - { - Type = InteractionResponseType.ChannelMessageWithSource, - Data = new API.InteractionCallbackData - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - TTS = isTTS ? true : Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - } - }; - - if (ephemeral) - response.Data.Value.Flags = 64; - - await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); - } - - /// - public override async Task FollowupAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - }; - - if (ephemeral) - args.Flags = 64; - - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); - } - - /// - /// Acknowledges this interaction with the . - /// - /// - /// A task that represents the asynchronous operation of acknowledging the interaction. - /// - public override Task DeferAsync(RequestOptions options = null) - { - var response = new API.InteractionResponse - { - Type = InteractionResponseType.DeferredChannelMessageWithSource, - }; - - return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); - } + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs index 0c5a540c7..a6eb24ca5 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs @@ -6,7 +6,7 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// public class SocketApplicationUserCommandData : SocketEntity, IApplicationCommandInteractionData { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs index 245274613..3013099c7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -10,7 +10,7 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketSlashCommand : SocketInteraction + public class SocketSlashCommand : SocketCommandBase { /// /// The data associated with this interaction. @@ -18,7 +18,7 @@ namespace Discord.WebSocket new public SocketSlashCommandData Data { get; } internal SocketSlashCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) - : base(client, model.Id, channel) + : base(client, model, channel) { var dataModel = model.Data.IsSpecified ? (DataModel)model.Data.Value @@ -36,130 +36,6 @@ namespace Discord.WebSocket var entity = new SocketSlashCommand(client, model, channel); entity.Update(model); return entity; - } - - internal override void Update(Model model) - { - var data = model.Data.IsSpecified ? - (DataModel)model.Data.Value - : null; - - this.Data.Update(data); - - base.Update(model); - } - - /// - public override async Task RespondAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - - if (Discord.AlwaysAcknowledgeInteractions) - { - await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); - return; - } - - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) - { - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) - { - throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); - } - - if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) - { - throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); - } - } - - var response = new API.InteractionResponse - { - Type = InteractionResponseType.ChannelMessageWithSource, - Data = new API.InteractionCallbackData - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - TTS = isTTS ? true : Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - } - }; - - if (ephemeral) - response.Data.Value.Flags = 64; - - await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); - } - - /// - public override async Task FollowupAsync( - string text = null, - Embed[] embeds = null, - bool isTTS = false, - bool ephemeral = false, - AllowedMentions allowedMentions = null, - RequestOptions options = null, - MessageComponent component = null, - Embed embed = null) - { - if (!IsValidToken) - throw new InvalidOperationException("Interaction token is no longer valid"); - - if (embeds == null && embed != null) - embeds = new[] { embed }; - Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); - Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); - Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); - - var args = new API.Rest.CreateWebhookMessageParams - { - Content = text, - AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, - IsTTS = isTTS, - Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified - }; - - if (ephemeral) - args.Flags = 64; - - return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); - } - - /// - /// Acknowledges this interaction with the . - /// - /// - /// A task that represents the asynchronous operation of acknowledging the interaction. - /// - public override Task DeferAsync(RequestOptions options = null) - { - var response = new API.InteractionResponse - { - Type = InteractionResponseType.DeferredChannelMessageWithSource, - }; - - return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); - } + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs similarity index 100% rename from src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommand.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandChoice.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs similarity index 100% rename from src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandChoice.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandChoice.cs diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs similarity index 100% rename from src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketApplicationCommandOption.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommandOption.cs diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs new file mode 100644 index 000000000..9fd25ac5d --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -0,0 +1,164 @@ +using Discord.Rest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DataModel = Discord.API.ApplicationCommandInteractionData; +using Model = Discord.API.Interaction; + +namespace Discord.WebSocket +{ + public class SocketCommandBase : SocketInteraction + { + /// + /// The data associated with this interaction. + /// + new internal SocketCommandBaseData Data { get; } + + internal SocketCommandBase(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + : base(client, model.Id, channel) + { + var dataModel = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + ulong? guildId = null; + if (this.Channel is SocketGuildChannel guildChannel) + guildId = guildChannel.Guild.Id; + + Data = SocketCommandBaseData.Create(client, dataModel, model.Id, guildId); + } + + new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + { + var entity = new SocketSlashCommand(client, model, channel); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + var data = model.Data.IsSpecified ? + (DataModel)model.Data.Value + : null; + + this.Data.Update(data); + + base.Update(model); + } + + /// + public override async Task RespondAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + + if (Discord.AlwaysAcknowledgeInteractions) + { + await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component); + return; + } + + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + // check that user flag and user Id list are exclusive, same with role flag and role Id list + if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + { + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && + allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + { + throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); + } + + if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && + allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + { + throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); + } + } + + var response = new API.InteractionResponse + { + Type = InteractionResponseType.ChannelMessageWithSource, + Data = new API.InteractionCallbackData + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + TTS = isTTS ? true : Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + } + }; + + if (ephemeral) + response.Data.Value.Flags = 64; + + await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); + } + + /// + public override async Task FollowupAsync( + string text = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + }; + + if (ephemeral) + args.Flags = 64; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + /// Acknowledges this interaction with the . + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + public override Task DeferAsync(RequestOptions options = null) + { + var response = new API.InteractionResponse + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + }; + + return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs new file mode 100644 index 000000000..7d7dbbce7 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -0,0 +1,145 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket +{ + public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData + { + public string Name { get; private set; } + + public IReadOnlyCollection Options { get; private set; } + // id + // type + internal Dictionary guildMembers { get; private set; } + = new Dictionary(); + internal Dictionary users { get; private set; } + = new Dictionary(); + internal Dictionary channels { get; private set; } + = new Dictionary(); + internal Dictionary roles { get; private set; } + = new Dictionary(); + + private ulong? guildId; + + internal SocketMessage Message { get; private set; } + + private ApplicationCommandType Type { get; set; } + + internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model.Id) + { + this.guildId = guildId; + + this.Type = (ApplicationCommandType)model.Type; + + if (model.Resolved.IsSpecified) + { + var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; + + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); + + this.users.Add(ulong.Parse(user.Key), socketUser); + } + } + + if (resolved.Channels.IsSpecified) + { + foreach (var channel in resolved.Channels.Value) + { + SocketChannel socketChannel = guild != null + ? guild.GetChannel(channel.Value.Id) + : Discord.GetChannel(channel.Value.Id); + + if (socketChannel == null) + { + var channelModel = guild != null + ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() + : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, Discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); + } + + Discord.State.AddChannel(socketChannel); + this.channels.Add(ulong.Parse(channel.Key), socketChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + member.Value.User = resolved.Users.Value[member.Key]; + var user = guild.AddOrUpdateUser(member.Value); + this.guildMembers.Add(ulong.Parse(member.Key), user); + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var socketRole = guild.AddOrUpdateRole(role.Value); + this.roles.Add(ulong.Parse(role.Key), socketRole); + } + } + + if (resolved.Messages.IsSpecified) + { + foreach (var msg in resolved.Messages.Value) + { + var channel = client.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; + + SocketUser author; + if (guild != null) + { + if (msg.Value.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, client.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); + else + author = guild.GetUser(msg.Value.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); + + if (channel == null) + { + if (!msg.Value.GuildId.IsSpecified) // assume it is a DM + { + channel = client.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, client.State); + } + } + + this.Message = SocketMessage.Create(client, client.State, author, channel, msg.Value); + } + } + } + } + + internal static SocketCommandBaseData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + { + var entity = new SocketCommandBaseData(client, model, guildId); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + this.Name = model.Name; + + this.Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new SocketCommandBaseDataOption(this, x)).ToImmutableArray() + : null; + } + + IReadOnlyCollection IApplicationCommandInteractionData.Options => Options; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs new file mode 100644 index 000000000..369f1f868 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionDataOption; + +namespace Discord.WebSocket +{ + public class SocketCommandBaseDataOption : IApplicationCommandInteractionDataOption + { + public string Name { get; private set; } + + /// + public object Value { get; private set; } + + /// + public ApplicationCommandOptionType Type { get; private set; } + + /// + /// The sub command options received for this sub command group. + /// + public IReadOnlyCollection Options { get; private set; } + + internal SocketCommandBaseDataOption() { } + internal SocketCommandBaseDataOption(SocketCommandBaseData data, Model model) + { + this.Name = model.Name; + this.Type = model.Type; + + if (model.Value.IsSpecified) + { + switch (Type) + { + case ApplicationCommandOptionType.User: + case ApplicationCommandOptionType.Role: + case ApplicationCommandOptionType.Channel: + case ApplicationCommandOptionType.Mentionable: + if (ulong.TryParse($"{model.Value.Value}", out var valueId)) + { + switch (this.Type) + { + case ApplicationCommandOptionType.User: + { + var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + this.Value = guildUser; + else + this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; + } + break; + case ApplicationCommandOptionType.Channel: + this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Role: + this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; + break; + case ApplicationCommandOptionType.Mentionable: + { + if (data.guildMembers.Any(x => x.Key == valueId) || data.users.Any(x => x.Key == valueId)) + { + var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; + + if (guildUser != null) + this.Value = guildUser; + else + this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; + } + else if (data.roles.Any(x => x.Key == valueId)) + { + this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; + } + } + break; + default: + this.Value = model.Value.Value; + break; + } + } + break; + case ApplicationCommandOptionType.String: + this.Value = model.Value.ToString(); + break; + case ApplicationCommandOptionType.Integer: + { + if (model.Value.Value is int val) + this.Value = val; + else if (int.TryParse(model.Value.Value.ToString(), out int res)) + this.Value = res; + } + break; + case ApplicationCommandOptionType.Boolean: + { + if (model.Value.Value is bool val) + this.Value = val; + else if (bool.TryParse(model.Value.Value.ToString(), out bool res)) + this.Value = res; + } + break; + case ApplicationCommandOptionType.Number: + { + if (model.Value.Value is int val) + this.Value = val; + else if (double.TryParse(model.Value.Value.ToString(), out double res)) + this.Value = res; + } + break; + } + + } + + this.Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new SocketCommandBaseDataOption(data, x)).ToImmutableArray() + : null; + } + + // Converters + public static explicit operator bool(SocketCommandBaseDataOption option) + => (bool)option.Value; + public static explicit operator int(SocketCommandBaseDataOption option) + => (int)option.Value; + public static explicit operator string(SocketCommandBaseDataOption option) + => option.Value.ToString(); + + IReadOnlyCollection IApplicationCommandInteractionDataOption.Options => this.Options; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 01b3874f7..59bae6f08 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -62,12 +62,13 @@ namespace Discord.WebSocket internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { if (model.Type == InteractionType.ApplicationCommand) - if(model.ApplicationId != null) + { + if (model.ApplicationId != null) { var dataModel = model.Data.IsSpecified ? (DataModel)model.Data.Value : null; - if(dataModel != null) + if (dataModel != null) { if (dataModel.Type.Equals(ApplicationCommandType.User)) return SocketApplicationUserCommand.Create(client, model, channel); @@ -76,6 +77,7 @@ namespace Discord.WebSocket } } return SocketSlashCommand.Create(client, model, channel); + } if (model.Type == InteractionType.MessageComponent) return SocketMessageComponent.Create(client, model, channel); else From 555fed862e8bd89bef0180d27c60dcfaf2eb92d2 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sat, 14 Aug 2021 11:20:29 -0400 Subject: [PATCH 06/14] Oops --- .../Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 9fd25ac5d..4dbadc552 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -32,7 +32,7 @@ namespace Discord.WebSocket new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketSlashCommand(client, model, channel); + var entity = new SocketCommandBase(client, model, channel); entity.Update(model); return entity; } From 50f8fbfa62b805d55f7171e711b6f9d5b4cfb057 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sun, 15 Aug 2021 17:00:35 -0400 Subject: [PATCH 07/14] guides revamped for slash/user/message commands --- .../01-getting-started.md | 25 ++++++ .../creating-context-menu-commands.md | 82 ++++++++++++++++++ .../02-creating-slash-commands.md | 0 .../03-responding-to-slash-commands.md | 0 .../slash-commands/04-parameters.md | 0 .../05-responding-ephemerally.md | 0 .../slash-commands/06-subcommands.md | 0 .../slash-commands/07-choice-slash-command.md | 0 .../slash-commands/README.md | 22 ++--- .../slash-commands/images/ephemeral1.png | Bin .../slash-commands/images/feedback1.png | Bin .../slash-commands/images/feedback2.png | Bin .../slash-commands/images/listroles1.png | Bin .../slash-commands/images/listroles2.png | Bin .../slash-commands/images/oauth.png | Bin .../slash-commands/images/settings1.png | Bin .../slash-commands/images/settings2.png | Bin .../slash-commands/images/settings3.png | Bin .../slash-commands/images/slashcommand1.png | Bin .../slash-commands/images/slashcommand2.png | Bin .../slash-commands/01-getting-started.md | 23 ----- 21 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 docs/guides/application-commands/01-getting-started.md create mode 100644 docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md rename docs/guides/{ => application-commands}/slash-commands/02-creating-slash-commands.md (100%) rename docs/guides/{ => application-commands}/slash-commands/03-responding-to-slash-commands.md (100%) rename docs/guides/{ => application-commands}/slash-commands/04-parameters.md (100%) rename docs/guides/{ => application-commands}/slash-commands/05-responding-ephemerally.md (100%) rename docs/guides/{ => application-commands}/slash-commands/06-subcommands.md (100%) rename docs/guides/{ => application-commands}/slash-commands/07-choice-slash-command.md (100%) rename docs/guides/{ => application-commands}/slash-commands/README.md (99%) rename docs/guides/{ => application-commands}/slash-commands/images/ephemeral1.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/feedback1.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/feedback2.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/listroles1.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/listroles2.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/oauth.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/settings1.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/settings2.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/settings3.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/slashcommand1.png (100%) rename docs/guides/{ => application-commands}/slash-commands/images/slashcommand2.png (100%) delete mode 100644 docs/guides/slash-commands/01-getting-started.md diff --git a/docs/guides/application-commands/01-getting-started.md b/docs/guides/application-commands/01-getting-started.md new file mode 100644 index 000000000..28d5fc817 --- /dev/null +++ b/docs/guides/application-commands/01-getting-started.md @@ -0,0 +1,25 @@ +# Getting started with application commands. + +Welcome! This guide will show you how to use application commands. If you have extra questions that aren't covered here you can come to our [Discord](https://discord.com/invite/dvSfUTet3K) server and ask around there. + +## What is an application command? + +Application commands consist of three different types. Slash commands, context menu User commands and context menu Message commands. +Slash commands are made up of a name, description, and a block of options, which you can think of like arguments to a function. The name and description help users find your command among many others, and the options validate user input as they fill out your command. +Message and User commands are only a name, to the user. So try to make the name descriptive. They're accessed by right clicking (or long press, on mobile) a user or a message, respectively. + +All three varieties of application commands have both Global and Guild variants. Your global commands are available in every guild that adds your application. You can also make commands for a specific guild; they're only available in that guild. The User and Message commands are more limited in quantity than the slash commands. For specifics, check out their respective guide pages. + +An Interaction is the message that your application receives when a user uses a command. It includes the values that the user submitted, as well as some metadata about this particular instance of the command being used: the guild_id, channel_id, member and other fields. You can find all the values in our data models. + +## Authorizing your bot for application commands + +There is a new special OAuth2 scope for applications called `applications.commands`. In order to make Application Commands work within a guild, the guild must authorize your application with the `applications.commands` scope. The bot scope is not enough. + +Head over to your discord applications OAuth2 screen and make sure to select the `application.commands` scope. + +![OAuth2 scoping](images/oauth.png) + +From there you can then use the link to add your bot to a server. + +**Note**: In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild. diff --git a/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md new file mode 100644 index 000000000..4e1a42987 --- /dev/null +++ b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md @@ -0,0 +1,82 @@ +# Creating context menu commands. + +There are two kinds of Context Menu Commands: User Commands and Message Commands. +Each of these have a Global and Guild variant. +Global menu commands are available for every guild that adds your app. An individual app's global commands are also available in DMs if that app has a bot that shares a mutual guild with the user. + +Guild commands are specific to the guild you specify when making them. Guild commands are not available in DMs. Command names are unique per application within each scope (global and guild). That means: + +- Your app cannot have two global commands with the same name +- Your app cannot have two guild commands within the same name on the same guild +- Your app can have a global and guild command with the same name +- Multiple apps can have commands with the same names + +**Note**: Apps can have a maximum of 5 global context menu commands, and an additional 5 guild-specific context menu commands per guild. + +If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html). + +## SlashCommandBuilder + +The slash command builder will help you create slash commands. The builder has these available fields and methods: + +| Name | Type | Description | +| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | +| Name | string | The name of this context menu command. | +| Description | string | A 0 length string. Left in place for possible future use. | +| WithName | Function | Sets the field name. | +| Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands | + +**Note**: Context Menu command names can be upper and lowercase, and use spaces. + +Let's use the user command builder to make a global and guild command. + +```cs +// Let's hook the ready event for creating our commands in. +client.Ready += Client_Ready; + +... + +public async Task Client_Ready() +{ + // Let's build a guild command! We're going to need a guild id so lets just put that in a variable. + ulong guildId = 848176216011046962; + + // Next, lets create our user and message command builder. This is like the embed builder but for context menu commands. + var guildUserCommand = new UserCommandBuilder(); + var guildMessageCommand = new MessageCommandBuilder(); + + // Note: Names have to be all lowercase and match the regular expression ^[\w -]{3,32}$ + guildUserCommand.WithName("Guild User Command"); + guildMessageCommand.WithName("Guild Message Command"); + + // Descriptions are not used with User and Message commands + //guildCommand.WithDescription(""); + + // Let's do our global commands + var globalCommand = new UserCommandBuilder(); + globalCommand.WithName("Global User Command"); + var globalMessageCommand = new MessageCommandBuilder(); + globalMessageCommand.WithName("Global Message Command"); + + try + { + // Now that we have our builder, we can call the rest API to make our slash command. + await client.Rest.CreateGuildUserCommand(guildUserCommand.Build(), guildId); + await client.Rest.CreateGuildMessageCommand(guildMessageCommand.Build(), guildId); + + // With global commands we dont need the guild id. + await client.Rest.CreateGlobalUserCommand(globalUserCommand.Build()); + await client.Rest.CreateGlobalMessageCommand(globalMessageCommand.Build()); + } + catch(ApplicationCommandException exception) + { + // If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is. + var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented); + + // You can send this error somewhere or just print it to the console, for this example we're just going to print it. + Console.WriteLine(json); + } +} + +``` +**Note**: Application commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register application commands. diff --git a/docs/guides/slash-commands/02-creating-slash-commands.md b/docs/guides/application-commands/slash-commands/02-creating-slash-commands.md similarity index 100% rename from docs/guides/slash-commands/02-creating-slash-commands.md rename to docs/guides/application-commands/slash-commands/02-creating-slash-commands.md diff --git a/docs/guides/slash-commands/03-responding-to-slash-commands.md b/docs/guides/application-commands/slash-commands/03-responding-to-slash-commands.md similarity index 100% rename from docs/guides/slash-commands/03-responding-to-slash-commands.md rename to docs/guides/application-commands/slash-commands/03-responding-to-slash-commands.md diff --git a/docs/guides/slash-commands/04-parameters.md b/docs/guides/application-commands/slash-commands/04-parameters.md similarity index 100% rename from docs/guides/slash-commands/04-parameters.md rename to docs/guides/application-commands/slash-commands/04-parameters.md diff --git a/docs/guides/slash-commands/05-responding-ephemerally.md b/docs/guides/application-commands/slash-commands/05-responding-ephemerally.md similarity index 100% rename from docs/guides/slash-commands/05-responding-ephemerally.md rename to docs/guides/application-commands/slash-commands/05-responding-ephemerally.md diff --git a/docs/guides/slash-commands/06-subcommands.md b/docs/guides/application-commands/slash-commands/06-subcommands.md similarity index 100% rename from docs/guides/slash-commands/06-subcommands.md rename to docs/guides/application-commands/slash-commands/06-subcommands.md diff --git a/docs/guides/slash-commands/07-choice-slash-command.md b/docs/guides/application-commands/slash-commands/07-choice-slash-command.md similarity index 100% rename from docs/guides/slash-commands/07-choice-slash-command.md rename to docs/guides/application-commands/slash-commands/07-choice-slash-command.md diff --git a/docs/guides/slash-commands/README.md b/docs/guides/application-commands/slash-commands/README.md similarity index 99% rename from docs/guides/slash-commands/README.md rename to docs/guides/application-commands/slash-commands/README.md index 702aec6de..3b672792c 100644 --- a/docs/guides/slash-commands/README.md +++ b/docs/guides/application-commands/slash-commands/README.md @@ -1,11 +1,11 @@ -## Slash command guides - -Here you can find some guides on how to use slash commands. - -1. [Getting started](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/01-getting-started.md) -2. [Creating a slash command](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/02-creating-slash-commands.md) -3. [Responding to slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/03-responding-to-slash-commands.md) -4. [Parameters in slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/04-parameters.md) -5. [Responding ephemerally](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/05-responding-ephemerally.md) -6. [Subcommands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/06-subcommands.md) -7. [Choices](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/07-choice-slash-command.md) +## Slash command guides + +Here you can find some guides on how to use slash commands. + +1. [Getting started](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/01-getting-started.md) +2. [Creating a slash command](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/02-creating-slash-commands.md) +3. [Responding to slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/03-responding-to-slash-commands.md) +4. [Parameters in slash commands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/04-parameters.md) +5. [Responding ephemerally](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/05-responding-ephemerally.md) +6. [Subcommands](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/06-subcommands.md) +7. [Choices](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/Interactions/docs/guides/slash-commands/07-choice-slash-command.md) diff --git a/docs/guides/slash-commands/images/ephemeral1.png b/docs/guides/application-commands/slash-commands/images/ephemeral1.png similarity index 100% rename from docs/guides/slash-commands/images/ephemeral1.png rename to docs/guides/application-commands/slash-commands/images/ephemeral1.png diff --git a/docs/guides/slash-commands/images/feedback1.png b/docs/guides/application-commands/slash-commands/images/feedback1.png similarity index 100% rename from docs/guides/slash-commands/images/feedback1.png rename to docs/guides/application-commands/slash-commands/images/feedback1.png diff --git a/docs/guides/slash-commands/images/feedback2.png b/docs/guides/application-commands/slash-commands/images/feedback2.png similarity index 100% rename from docs/guides/slash-commands/images/feedback2.png rename to docs/guides/application-commands/slash-commands/images/feedback2.png diff --git a/docs/guides/slash-commands/images/listroles1.png b/docs/guides/application-commands/slash-commands/images/listroles1.png similarity index 100% rename from docs/guides/slash-commands/images/listroles1.png rename to docs/guides/application-commands/slash-commands/images/listroles1.png diff --git a/docs/guides/slash-commands/images/listroles2.png b/docs/guides/application-commands/slash-commands/images/listroles2.png similarity index 100% rename from docs/guides/slash-commands/images/listroles2.png rename to docs/guides/application-commands/slash-commands/images/listroles2.png diff --git a/docs/guides/slash-commands/images/oauth.png b/docs/guides/application-commands/slash-commands/images/oauth.png similarity index 100% rename from docs/guides/slash-commands/images/oauth.png rename to docs/guides/application-commands/slash-commands/images/oauth.png diff --git a/docs/guides/slash-commands/images/settings1.png b/docs/guides/application-commands/slash-commands/images/settings1.png similarity index 100% rename from docs/guides/slash-commands/images/settings1.png rename to docs/guides/application-commands/slash-commands/images/settings1.png diff --git a/docs/guides/slash-commands/images/settings2.png b/docs/guides/application-commands/slash-commands/images/settings2.png similarity index 100% rename from docs/guides/slash-commands/images/settings2.png rename to docs/guides/application-commands/slash-commands/images/settings2.png diff --git a/docs/guides/slash-commands/images/settings3.png b/docs/guides/application-commands/slash-commands/images/settings3.png similarity index 100% rename from docs/guides/slash-commands/images/settings3.png rename to docs/guides/application-commands/slash-commands/images/settings3.png diff --git a/docs/guides/slash-commands/images/slashcommand1.png b/docs/guides/application-commands/slash-commands/images/slashcommand1.png similarity index 100% rename from docs/guides/slash-commands/images/slashcommand1.png rename to docs/guides/application-commands/slash-commands/images/slashcommand1.png diff --git a/docs/guides/slash-commands/images/slashcommand2.png b/docs/guides/application-commands/slash-commands/images/slashcommand2.png similarity index 100% rename from docs/guides/slash-commands/images/slashcommand2.png rename to docs/guides/application-commands/slash-commands/images/slashcommand2.png diff --git a/docs/guides/slash-commands/01-getting-started.md b/docs/guides/slash-commands/01-getting-started.md deleted file mode 100644 index 093178878..000000000 --- a/docs/guides/slash-commands/01-getting-started.md +++ /dev/null @@ -1,23 +0,0 @@ -# Getting started with slash commands. - -Welcome! This guide will show you how to use slash commands. If you have extra questions that aren't covered here you can come to our [Discord](https://discord.com/invite/dvSfUTet3K) server and ask around there. - -## What is a slash command? - -Slash Commands _(synonymous with application commands)_ are made up of a name, description, and a block of options, which you can think of like arguments to a function. The name and description help users find your command among many others, and the options validate user input as they fill out your command. - -Your global commands are available in every guild that adds your application. You can also make commands for a specific guild; they're only available in that guild. - -An Interaction is the message that your application receives when a user uses a command. It includes the values that the user submitted, as well as some metadata about this particular instance of the command being used: the guild_id, channel_id, member and other fields. You can find all the values in our data models. - -## Authorizing your bot for slash commands - -There is a new special OAuth2 scope for applications called `applications.commands`. In order to make Slash Commands work within a guild, the guild must authorize your application with the `applications.commands` scope. The bot scope is not enough. - -Head over to your discord applications OAuth2 screen and make sure to select the `application.commands` scope. - -![OAuth2 scoping](images/oauth.png) - -From there you can then use the link to add your bot to a server. - -**Note**: In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild. From a0173dcd9c6521df5663158112599d3e97fd7265 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sun, 15 Aug 2021 17:22:37 -0400 Subject: [PATCH 08/14] Added Receive event guides --- .../receiving-context-menu-command-events.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md diff --git a/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md new file mode 100644 index 000000000..a1492fc50 --- /dev/null +++ b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md @@ -0,0 +1,43 @@ +# Receiving Context Menu events + +User commands and Message commands have their own unique objects returned. Different from Slash commands. To get the appropriate object returned, you can use a similar method to the slash commands. + +```cs +client.InteractionCreated += InteractionCreatedHandler; + +... + +public async Task InteractionCreatedHandler(SocketInteraction arg) +{ + if ( arg.Type == InteractionType.ApplicationCommand) + Task.Run(() => ApplicationCommandHandler(arg)); +} + +public async Task ApplicationCommandHandler(SocketInteraction arg) +{ + var slashCommand = arg as SocketSlashCommand; + if(slashCommand != null) + Console.Writeline("Slash command received!") + + var userCommand = arg as SocketApplicationUserCommand; + if(userCommand != null) + { + Console.Writeline("User command received!") + // userCommand.User = User who ran command. + // userCommand.Data.Member = User who was clicked. + } + + var messageCommand = arg as SocketApplicationMessageCommand; + if(messageCommand != null) + { + Console.Writeline("Message command received!") + // messageCommand.User = User who ran command. + // messageCommand.Data.Message = Message that was clicked. + } +} +``` + +User commands return a SocketUser object, showing the user that was clicked to run the command. +Message commands return a SocketMessage object, showing the message that was clicked to run the command. + +Both return the user who ran the command, the guild (if any), channel, etc. \ No newline at end of file From 8993911d68a241a96d756f427a3a096f2528bf82 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Fri, 20 Aug 2021 23:03:27 -0400 Subject: [PATCH 09/14] Implemented Context Menus (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update README.md * Update README.md * Fix SocketSlashCommandDataOption to use long for Number instead of int (#89) * Application webhooks (#86) * Added webhook components for hooks having an application ID. * resolved #88 * resolved #85 * Update device for gateway * Fix MessageProperties.Embed being ignored in some methods that modifies a message (#92) * Update label/description lengths for selects (ref: https://github.com/discord/discord-api-docs/pull/3598/files) (#91) https://github.com/discord/discord-api-docs/pull/3598/files * Fix tests (#90) * Fix gateway serialization to include nulls (#96) * Add missing guild permissions (#93) * Update GuildPermissions.cs * Update GuildPermissionsTests.cs * Add banner and accent color to user and some fixes/improvements (#81) * Add banner and accent color to user and some fixes * Fix * Fix! * increase size of user banners to 256 * Some changes and mini refactor of color class * add constant maxDecimalValue to color and checks with exceptions * add `NotSupportedException` for `BannerId` and `AccentColor` in `SocketWebhookUser` * Update ComponentBuilder.cs - `MaxLabelLength` from `ComponentBuilder` moved to `ButtonBuilder` - Added `MaxLabelLength` for `SelectMenuOptionBuilder` - Changed `MaxDescriptionLength` to 100 * Interface Method Declarations for Interaction Methods (#99) * added interface method declarations * inline docs * Fix serialization error * meta: bump versions * Fix debug pragma * meta: bump version * Remove rich presence button * Assign CurrentUserId in Sharded Client (#100) * added interface method declarations * inline docs * current user id assignment in sharded client * Allow EmbedBuilder.ImageUrl to use attachment scheme syntax (#104) * Make Webhook ApplicationId nullable instead of optional + fix IDiscordInteraction DeferAsync method (#110) * Make Webhook ApplicationId nullable instead of optional * Fix IDiscordInteraction DeferAsync to account for ephemeral defer * Fix application command and thread starter messages being created as SocketSystemMessage * Added description of ApplicationCommandType Enums * Requested Fixes renamed SocketApplicationUserCommand to SocketUserCommand renamed SocketApplicationMessageCommand to SocketMessageCommand using ContextMenuCreationProperties for both User and Message commands * Added Summary to public members removed whitespace from DiscordRestApiClient.cs * Fixing guide to use switch statement * implemented TrySendApplicationCommandAsync * implemented ephemeral in SocketCommandBase Defer, and RespondAsync. assigning int 64 was error. changed to "MessageFlags.Ephemeral", built and tested to work. * removed ApplicationCommandType from SocketUser and SocketMessageCommandData Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> Co-authored-by: František Boháček Co-authored-by: quin lynch Co-authored-by: d4n3436 Co-authored-by: MrCakeSlayer <13650699+MrCakeSlayer@users.noreply.github.com> Co-authored-by: Nikon <47792796+INikonI@users.noreply.github.com> Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> --- Discord.Net.sln | 2 - .../creating-context-menu-commands.md | 15 +- .../receiving-context-menu-command-events.md | 31 +- src/Discord.Net.Core/CDN.cs | 18 ++ src/Discord.Net.Core/Discord.Net.Core.csproj | 8 +- src/Discord.Net.Core/Discord.Net.Core.xml | 286 ++++++++++++------ .../Entities/Guilds/IGuild.cs | 10 + .../Interactions/ApplicationCommandTypes.cs | 12 + ...> ContextMenuCommandCreationProperties.cs} | 8 +- .../Interactions/IApplicationCommand.cs | 9 +- .../Interactions/IDiscordInteraction.cs | 54 ++++ .../Message Components/ComponentBuilder.cs | 35 ++- .../Interactions/MessageCommandBuilder.cs | 48 +-- .../Interactions/UserCommandBuilder.cs | 48 +-- .../Entities/Messages/EmbedBuilder.cs | 2 +- .../Entities/Messages/MessageProperties.cs | 2 +- .../Entities/Permissions/GuildPermissions.cs | 55 +++- src/Discord.Net.Core/Entities/Roles/Color.cs | 101 ++++--- src/Discord.Net.Core/Entities/Users/IUser.cs | 32 +- .../Entities/Webhooks/IWebhook.cs | 7 +- src/Discord.Net.Rest/API/Common/Game.cs | 4 +- .../API/Common/InteractionCallbackData.cs | 2 +- .../API/Common/RichPresenceButton.cs | 18 -- src/Discord.Net.Rest/API/Common/User.cs | 4 + src/Discord.Net.Rest/API/Common/Webhook.cs | 2 + .../API/Rest/CreateWebhookMessageParams.cs | 2 +- .../API/Rest/ModifyWebhookMessageParams.cs | 2 + src/Discord.Net.Rest/Discord.Net.Rest.csproj | 6 +- src/Discord.Net.Rest/Discord.Net.Rest.xml | 29 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 62 +--- src/Discord.Net.Rest/DiscordRestClient.cs | 16 +- .../Entities/Guilds/RestGuild.cs | 16 +- .../Interactions/InteractionHelper.cs | 97 +++--- .../Entities/Messages/MessageHelper.cs | 45 ++- .../Entities/Users/RestThreadUser.cs | 4 +- .../Entities/Users/RestUser.cs | 12 + .../Entities/Webhooks/RestWebhook.cs | 4 + .../Extensions/EntityExtensions.cs | 1 + .../Discord.Net.WebSocket.csproj | 4 +- .../Discord.Net.WebSocket.xml | 121 +++++++- .../DiscordShardedClient.cs | 11 +- .../DiscordSocketApiClient.cs | 17 +- .../Entities/Guilds/SocketGuild.cs | 3 + ...sageCommand.cs => SocketMessageCommand.cs} | 10 +- ...andData.cs => SocketMessageCommandData.cs} | 17 +- ...ionUserCommand.cs => SocketUserCommand.cs} | 10 +- ...ommandData.cs => SocketUserCommandData.cs} | 18 +- .../SocketMessageComponent.cs | 61 +++- .../Slash Commands/SocketSlashCommand.cs | 2 +- .../SocketSlashCommandDataOption.cs | 6 +- .../SocketBaseCommand/SocketCommandBase.cs | 9 +- .../SocketCommandBaseData.cs | 10 +- .../SocketCommandBaseDataOption.cs | 4 + .../Entities/Interaction/SocketInteraction.cs | 25 +- .../Entities/Messages/SocketMessage.cs | 5 +- .../Entities/Users/SocketGlobalUser.cs | 4 +- .../Entities/Users/SocketGroupUser.cs | 4 + .../Entities/Users/SocketGuildUser.cs | 7 +- .../Entities/Users/SocketSelfUser.cs | 4 + .../Entities/Users/SocketThreadUser.cs | 16 +- .../Entities/Users/SocketUnknownUser.cs | 9 +- .../Entities/Users/SocketUser.cs | 18 ++ .../Entities/Users/SocketWebhookUser.cs | 17 ++ .../Discord.Net.Webhook.csproj | 2 +- .../Discord.Net.Webhook.xml | 7 +- .../DiscordWebhookClient.cs | 4 +- .../Messages/WebhookMessageProperties.cs | 4 + .../Entities/Webhooks/RestInternalWebhook.cs | 3 + .../WebhookClientHelper.cs | 7 +- src/Discord.Net/Discord.Net.nuspec | 20 +- .../GuildPermissionsTests.cs | 16 +- 71 files changed, 1067 insertions(+), 517 deletions(-) rename src/Discord.Net.Core/Entities/Interactions/{MessageCommandCreationProperties.cs => ContextMenuCommandCreationProperties.cs} (73%) delete mode 100644 src/Discord.Net.Rest/API/Common/RichPresenceButton.cs rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/{SocketApplicationMessageCommand.cs => SocketMessageCommand.cs} (69%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/{SocketApplicationMessageCommandData.cs => SocketMessageCommandData.cs} (89%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/{SocketApplicationUserCommand.cs => SocketUserCommand.cs} (70%) rename src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/{SocketApplicationUserCommandData.cs => SocketUserCommandData.cs} (86%) diff --git a/Discord.Net.sln b/Discord.Net.sln index c11739aef..9f888cd12 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -42,8 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeatureTesting", "..\FeatureTesting\FeatureTesting\FeatureTesting.csproj", "{0CC57A32-3AC7-489D-8DF5-C431925E4675}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md index 4e1a42987..9100c3b42 100644 --- a/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md +++ b/docs/guides/application-commands/context-menu-commands/creating-context-menu-commands.md @@ -15,14 +15,23 @@ Guild commands are specific to the guild you specify when making them. Guild com If you don't have the code for a bot ready yet please follow [this guide](https://docs.stillu.cc/guides/getting_started/first-bot.html). -## SlashCommandBuilder +## UserCommandBuilder -The slash command builder will help you create slash commands. The builder has these available fields and methods: +The context menu user command builder will help you create user commands. The builder has these available fields and methods: + +| Name | Type | Description | +| --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | +| Name | string | The name of this context menu command. | +| WithName | Function | Sets the field name. | +| Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands | + +## MessageCommandBuilder + +The context menu message command builder will help you create message commands. The builder has these available fields and methods: | Name | Type | Description | | --------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | | Name | string | The name of this context menu command. | -| Description | string | A 0 length string. Left in place for possible future use. | | WithName | Function | Sets the field name. | | Build | Function | Builds the builder into the appropriate `CommandCreationProperties` class used to make Menu commands | diff --git a/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md index a1492fc50..52bc303e7 100644 --- a/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md +++ b/docs/guides/application-commands/context-menu-commands/receiving-context-menu-command-events.md @@ -15,24 +15,21 @@ public async Task InteractionCreatedHandler(SocketInteraction arg) public async Task ApplicationCommandHandler(SocketInteraction arg) { - var slashCommand = arg as SocketSlashCommand; - if(slashCommand != null) - Console.Writeline("Slash command received!") - - var userCommand = arg as SocketApplicationUserCommand; - if(userCommand != null) + switch (arg) { - Console.Writeline("User command received!") - // userCommand.User = User who ran command. - // userCommand.Data.Member = User who was clicked. - } - - var messageCommand = arg as SocketApplicationMessageCommand; - if(messageCommand != null) - { - Console.Writeline("Message command received!") - // messageCommand.User = User who ran command. - // messageCommand.Data.Message = Message that was clicked. + case SocketSlashCommand slashCommand: + Console.Writeline("Slash command received!"); + break; + case SocketUserCommand userCommand: + Console.Writeline("User command received!") + // userCommand.User = User who ran command. + // userCommand.Data.Member = User who was clicked. + break; + case SocketMessageCommand messageCommand: + Console.Writeline("Message command received!") + // messageCommand.User = User who ran command. + // messageCommand.Data.Message = Message that was clicked. + break; } } ``` diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index e1e8e5e1a..b1879eebc 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -46,6 +46,24 @@ namespace Discord string extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } + + /// + /// Returns a user banner URL. + /// + /// The user snowflake identifier. + /// The banner identifier. + /// The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048. + /// The format to return. + /// + /// A URL pointing to the user's banner in the specified size. + /// + public static string GetUserBannerUrl(ulong userId, string bannerId, ushort size, ImageFormat format) + { + if (bannerId == null) + return null; + string extension = FormatToExtension(format, bannerId); + return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}"; + } /// /// Returns the default user avatar URL. /// diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index f0be10059..ac54b259c 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -1,4 +1,4 @@ - + @@ -8,12 +8,12 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 Discord.Net.Labs.Core - 3.0.0-pre + 3.0.1-pre Discord.Net.Labs.Core https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png - 2.3.8 - 2.3.8 + 3.3.1 + 3.0.1 false diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index d1e4770e3..8be13e6bd 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -100,6 +100,18 @@ A URL pointing to the user's avatar in the specified size. + + + Returns a user banner URL. + + The user snowflake identifier. + The banner identifier. + The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048. + The format to return. + + A URL pointing to the user's banner in the specified size. + + Returns the default user avatar URL. @@ -3902,6 +3914,16 @@ A task that represents the asynchronous removal operation. + + + Gets this guilds slash commands commands + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection + of application commands found within the guild. + + Holds information for a guild integration feature. @@ -4467,6 +4489,41 @@ Whether the command is enabled by default when the app is added to a guild. Default is + + + ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + + + + + ApplicationCommandType.Slash is Slash command type + + + + + ApplicationCommandType.User is Context Menu User command type + + + + + ApplicationCommandType.Message is Context Menu Message command type + + + + + A class used to create Message commands. + + + + + The name of this command. + + + + + Gets or sets the type for this command. + + The base command model that belongs to an application. see @@ -4502,13 +4559,6 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. - - - Deletes this command - - The options to be used when sending the request. - A task that represents the asynchronous delete operation. - Represents data of an Interaction Command, see . @@ -4642,6 +4692,58 @@ read-only property, always 1. + + + Responds to an Interaction with type . + + The text of the message to be sent. + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + + + Sends a followup message for this interaction. + + The text of the message to be sent + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + The sent message. + + + + + Gets the original response for this interaction. + + The request options for this async request. + A that represents the initial response. + + + + Edits original response for this interaction. + + A delegate containing the properties to modify the message with. + The request options for this async request. + A that represents the initial response. + + + + Acknowledges this interaction. + + + A task that represents the asynchronous operation of acknowledging the interaction. + + Represents an interface used to specify classes that they are a vaild dataype of a class. @@ -4792,7 +4894,7 @@ Represents a builder for creating a . - + The max length of a . @@ -4915,11 +5017,16 @@ Represents a class used to build 's. + + + The max length of a . + + Gets or sets the label of the current button. - length exceeds . + length exceeds . @@ -5239,16 +5346,26 @@ Represents a class used to build 's. + + + The maximum length of a . + + The maximum length of a . + + + The maximum length of a . + + Gets or sets the label of the current select menu. - length exceeds + length exceeds @@ -5456,7 +5573,7 @@ - A class used to build slash commands. + A class used to build Message commands. @@ -5464,26 +5581,16 @@ Returns the maximun length a commands name allowed by Discord - - - Returns the maximum length of a commands description allowed by Discord. - - - The name of this slash command. - - - - - A 1-100 length description of this slash command + The name of this Message command. - Build the current builder into a class. + Build the current builder into a class. - A that can be used to create user commands over rest. + A that can be used to create message commands over rest. @@ -5494,33 +5601,6 @@ The current builder. - - - Sets the description of the current command. - - The description of this command. - The current builder. - - - - A class used to create Message commands. - - - - - The name of this command. - - - - - The discription of this command. - - - - - Gets or sets the type for this command. - - A class used to build slash commands. @@ -5785,7 +5865,7 @@ - A class used to build slash commands. + A class used to build user commands. @@ -5793,26 +5873,16 @@ Returns the maximun length a commands name allowed by Discord - - - Returns the maximum length of a commands description allowed by Discord. - - - The name of this slash command. - - - - - A 1-100 length description of this slash command + The name of this User command. - Build the current builder into a class. + Build the current builder into a class. - A that can be used to create user commands over rest. + A that can be used to create user commands over rest. @@ -5823,13 +5893,6 @@ The current builder. - - - Sets the description of the current command. - - The description of this command. - The current builder. - A class used to create User commands. @@ -7751,7 +7814,7 @@ Gets or sets a single embed for this message. - This property will be added to the array, in the future please use the array rather then this property. + This property will be added to the array, in the future please use the array rather than this property. @@ -8794,7 +8857,25 @@ If true, a user may edit the webhooks for this guild. - If true, a user may edit the emojis for this guild. + If true, a user may edit the emojis and stickers for this guild. + + + If true, a user may use slash commands in this guild. + + + If true, a user may request to speak in stage channels. + + + If true, a user may manage threads in this guild. + + + If true, a user may create public threads in this guild. + + + If true, a user may create private threads in this guild. + + + If true, a user may use external stickers in this guild. Creates a new with the provided packed value. @@ -8802,10 +8883,10 @@ Creates a new with the provided packed value after converting to ulong. - + Creates a new structure with the provided permissions. - + Creates a new from this one, changing the provided non-null permissions. @@ -8992,6 +9073,9 @@ Represents a color used in Discord. + + Gets the max decimal value of color. + Gets the default user color value. @@ -9096,20 +9180,21 @@ Initializes a struct with the given raw value. - The following will create a color that has a hex value of + The following will create a color that has a hex value of #607D8B. Color darkGrey = new Color(0x607D8B); The raw value of the color (e.g. 0x607D8B). + Value exceeds . Initializes a struct with the given RGB bytes. - The following will create a color that has a value of + The following will create a color that has a value of #607D8B. Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); @@ -9118,13 +9203,14 @@ The byte that represents the red color. The byte that represents the green color. The byte that represents the blue color. + Value exceeds . Initializes a struct with the given RGB value. - The following will create a color that has a value of + The following will create a color that has a value of #607D8B. Color darkGrey = new Color(96, 125, 139); @@ -9140,7 +9226,7 @@ Initializes a struct with the given RGB float value. - The following will create a color that has a value of + The following will create a color that has a value of #607c8c. Color darkGrey = new Color(0.38f, 0.49f, 0.55f); @@ -9908,19 +9994,33 @@ Gets the identifier of this user's avatar. + + + Gets the identifier of this user's banner. + + + + + Gets the user's banner color. + + + A struct representing the accent color of this user's banner. + + Gets the avatar URL for this user. This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar - (i.e. their avatar identifier is not set), this property will return null. If you wish to + (i.e. their avatar identifier is not set), this method will return null. If you wish to retrieve the default avatar for this user, consider using (see example). - The following example attempts to retrieve the user's current avatar and send it to a channel; if one is - not set, a default avatar for this user will be returned instead. + The following example attempts to retrieve the user's current avatar and send it to a channel; if one is + not set, a default avatar for this user will be returned instead. @@ -9931,6 +10031,17 @@ A string representing the user's avatar URL; null if the user does not have an avatar in place. + + + Gets the banner URL for this user. + + The format to return. + The size of the image to return in. This can be any power of two between 16 and 2048. + + + A string representing the user's avatar URL; null if the user does not have an banner in place. + + Gets the default avatar URL for this user. @@ -9998,8 +10109,8 @@ This method is used to obtain or create a channel used to send a direct message. In event that the current user cannot send a message to the target user, a channel can and will - still be created by Discord. However, attempting to send a message will yield a - with a 403 as its + still be created by Discord. However, attempting to send a message will yield a + with a 403 as its . There are currently no official workarounds by Discord. @@ -10293,6 +10404,11 @@ Gets the user that created this webhook. + + + Gets the ID of the application owning this webhook. + + Modifies this webhook. diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index ad2e0317d..414b6fe73 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -941,5 +941,15 @@ namespace Discord /// A task that represents the asynchronous removal operation. /// Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); + + /// + /// Gets this guilds slash commands commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + Task> GetApplicationCommandsAsync (RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs index 23b00f2a2..de9c1a263 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -6,10 +6,22 @@ using System.Threading.Tasks; namespace Discord { + /// + /// ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + /// public enum ApplicationCommandType : byte { + /// + /// ApplicationCommandType.Slash is Slash command type + /// Slash = 1, + /// + /// ApplicationCommandType.User is Context Menu User command type + /// User = 2, + /// + /// ApplicationCommandType.Message is Context Menu Message command type + /// Message = 3 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs similarity index 73% rename from src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs rename to src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs index 7c7dac593..4ddc0a7b1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageCommandCreationProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs @@ -9,19 +9,13 @@ namespace Discord /// /// A class used to create Message commands. /// - public class MessageCommandCreationProperties + public class ContextMenuCommandCreationProperties { /// /// The name of this command. /// public string Name { get; set; } - /// - /// The discription of this command. - /// - public string Description { get; set; } - - /// /// Gets or sets the type for this command. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index ab4b3eac0..e15843d98 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -9,7 +9,7 @@ namespace Discord /// /// The base command model that belongs to an application. see /// - public interface IApplicationCommand : ISnowflakeEntity + public interface IApplicationCommand : ISnowflakeEntity, IDeletable { /// /// Gets the unique id of the parent application. @@ -40,12 +40,5 @@ namespace Discord /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. /// IReadOnlyCollection Options { get; } - - /// - /// Deletes this command - /// - /// The options to be used when sending the request. - /// A task that represents the asynchronous delete operation. - Task DeleteAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 466bf3e91..ae015c2a6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -39,5 +39,59 @@ namespace Discord /// read-only property, always 1. /// int Version { get; } + + /// + /// Responds to an Interaction with type . + /// + /// The text of the message to be sent. + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + Task RespondAsync (string text = null, Embed[] embeds = null, bool isTTS = false, + bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + Task FollowupAsync (string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Gets the original response for this interaction. + /// + /// The request options for this async request. + /// A that represents the initial response. + Task GetOriginalResponseAsync (RequestOptions options = null); + + /// + /// Edits original response for this interaction. + /// + /// A delegate containing the properties to modify the message with. + /// The request options for this async request. + /// A that represents the initial response. + Task ModifyOriginalResponseAsync (Action func, RequestOptions options = null); + + /// + /// Acknowledges this interaction. + /// + /// + /// A task that represents the asynchronous operation of acknowledging the interaction. + /// + Task DeferAsync (bool ephemeral = false, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs index bb2f80a81..085c62cef 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs @@ -13,7 +13,7 @@ namespace Discord /// /// The max length of a . /// - public const int MaxLabelLength = 80; + public const int MaxButtonLabelLength = 80; /// /// The max length of a . @@ -307,17 +307,22 @@ namespace Discord /// public class ButtonBuilder { + /// + /// The max length of a . + /// + public const int MaxLabelLength = 80; + /// /// Gets or sets the label of the current button. /// - /// length exceeds . + /// length exceeds . public string Label { get => _label; set { - if (value != null && value.Length > ComponentBuilder.MaxLabelLength) - throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + if (value != null && value.Length > ComponentBuilder.MaxButtonLabelLength) + throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxButtonLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } @@ -539,8 +544,8 @@ namespace Discord if (string.IsNullOrEmpty(this.Url)) throw new InvalidOperationException("Link buttons must have a link associated with them"); else - UrlValidation.Validate(this.Url); - } + UrlValidation.Validate(this.Url); + } else if (string.IsNullOrEmpty(this.CustomId)) throw new InvalidOperationException("Non-link buttons must have a custom id associated with them"); @@ -831,23 +836,33 @@ namespace Discord /// public class SelectMenuOptionBuilder { + /// + /// The maximum length of a . + /// + public const int MaxLabelLength = 100; + /// /// The maximum length of a . /// - public const int MaxDescriptionLength = 50; + public const int MaxDescriptionLength = 100; + + /// + /// The maximum length of a . + /// + public const int MaxSelectLabelLength = 100; /// /// Gets or sets the label of the current select menu. /// - /// length exceeds + /// length exceeds public string Label { get => _label; set { if (value != null) - if (value.Length > ComponentBuilder.MaxLabelLength) - throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + if (value.Length > MaxSelectLabelLength) + throw new ArgumentException(message: $"Button label must be {MaxSelectLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs index 792d7d19f..b658a181b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// A class used to build slash commands. + /// A class used to build Message commands. /// public class MessageCommandBuilder { @@ -16,13 +16,9 @@ namespace Discord /// Returns the maximun length a commands name allowed by Discord /// public const int MaxNameLength = 32; - /// - /// Returns the maximum length of a commands description allowed by Discord. - /// - public const int MaxDescriptionLength = 0; /// - /// The name of this slash command. + /// The name of this Message command. /// public string Name { @@ -45,36 +41,17 @@ namespace Discord } } - /// - /// A 1-100 length description of this slash command - /// - public string Description - { - get - { - return _description; - } - set - { - Preconditions.Equals(value, ""); - - _description = value; - } - } - private string _name { get; set; } - private string _description { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create user commands over rest. - public MessageCommandCreationProperties Build() + /// A that can be used to create message commands over rest. + public ContextMenuCommandCreationProperties Build() { - MessageCommandCreationProperties props = new MessageCommandCreationProperties() + ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() { Name = this.Name, - Description = this.Description, Type=ApplicationCommandType.Message }; @@ -93,17 +70,6 @@ namespace Discord { this.Name = name; return this; - } - - /// - /// Sets the description of the current command. - /// - /// The description of this command. - /// The current builder. - public MessageCommandBuilder WithDescription(string description) - { - this.Description = description; - return this; - } + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs index 0dc6526ba..f10b6b123 100644 --- a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// A class used to build slash commands. + /// A class used to build user commands. /// public class UserCommandBuilder { @@ -16,13 +16,9 @@ namespace Discord /// Returns the maximun length a commands name allowed by Discord /// public const int MaxNameLength = 32; - /// - /// Returns the maximum length of a commands description allowed by Discord. - /// - public const int MaxDescriptionLength = 0; /// - /// The name of this slash command. + /// The name of this User command. /// public string Name { @@ -45,36 +41,17 @@ namespace Discord } } - /// - /// A 1-100 length description of this slash command - /// - public string Description - { - get - { - return _description; - } - set - { - Preconditions.Equals(value, ""); - - _description = value; - } - } - private string _name { get; set; } - private string _description { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create user commands over rest. - public UserCommandCreationProperties Build() + /// A that can be used to create user commands over rest. + public ContextMenuCommandCreationProperties Build() { - UserCommandCreationProperties props = new UserCommandCreationProperties() + ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() { Name = this.Name, - Description = this.Description, Type=ApplicationCommandType.User }; @@ -93,17 +70,6 @@ namespace Discord { this.Name = name; return this; - } - - /// - /// Sets the description of the current command. - /// - /// The description of this command. - /// The current builder. - public UserCommandBuilder WithDescription(string description) - { - this.Description = description; - return this; - } + } } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 2ab2699a6..5b92e02a5 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -411,7 +411,7 @@ namespace Discord UrlValidation.Validate(Url); if (!string.IsNullOrEmpty(ThumbnailUrl)) UrlValidation.Validate(ThumbnailUrl); - if (!string.IsNullOrEmpty(ImageUrl)) + if (!string.IsNullOrEmpty(ImageUrl) && !ImageUrl.StartsWith("attachment://", StringComparison.Ordinal)) UrlValidation.Validate(ImageUrl); if (Author != null) { diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index 19cfacebe..abd09d856 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -22,7 +22,7 @@ namespace Discord /// Gets or sets a single embed for this message. /// /// - /// This property will be added to the array, in the future please use the array rather then this property. + /// This property will be added to the array, in the future please use the array rather than this property. /// public Optional Embed { get; set; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 1914a6f86..9503e5b3b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -81,8 +81,20 @@ namespace Discord public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); /// If true, a user may edit the webhooks for this guild. public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); - /// If true, a user may edit the emojis for this guild. + /// If true, a user may edit the emojis and stickers for this guild. public bool ManageEmojisAndStickers => Permissions.GetValue(RawValue, GuildPermission.ManageEmojisAndStickers); + /// If true, a user may use slash commands in this guild. + public bool UseSlashCommands => Permissions.GetValue(RawValue, GuildPermission.UseSlashCommands); + /// If true, a user may request to speak in stage channels. + public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak); + /// If true, a user may manage threads in this guild. + public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); + /// If true, a user may create public threads in this guild. + public bool UsePublicThreads => Permissions.GetValue(RawValue, GuildPermission.UsePublicThreads); + /// If true, a user may create private threads in this guild. + public bool UsePrivateThreads => Permissions.GetValue(RawValue, GuildPermission.UsePrivateThreads); + /// If true, a user may use external stickers in this guild. + public bool UseExternalStickers => Permissions.GetValue(RawValue, GuildPermission.UseExternalStickers); /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } @@ -121,7 +133,13 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojisAndStickers = null) + bool? manageEmojisAndStickers = null, + bool? useSlashCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? usePublicThreads = null, + bool? usePrivateThreads = null, + bool? useExternalStickers = null) { ulong value = initialValue; @@ -156,6 +174,12 @@ namespace Discord Permissions.SetValue(ref value, manageRoles, GuildPermission.ManageRoles); Permissions.SetValue(ref value, manageWebhooks, GuildPermission.ManageWebhooks); Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); + Permissions.SetValue(ref value, useSlashCommands, GuildPermission.UseSlashCommands); + Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak); + Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads); + Permissions.SetValue(ref value, usePublicThreads, GuildPermission.UsePublicThreads); + Permissions.SetValue(ref value, usePrivateThreads, GuildPermission.UseExternalStickers); + Permissions.SetValue(ref value, useExternalStickers, GuildPermission.UseExternalStickers); RawValue = value; } @@ -192,7 +216,13 @@ namespace Discord bool manageNicknames = false, bool manageRoles = false, bool manageWebhooks = false, - bool manageEmojis = false) + bool manageEmojisAndStickers = false, + bool useSlashCommands = false, + bool requestToSpeak = false, + bool manageThreads = false, + bool usePublicThreads = false, + bool usePrivateThreads = false, + bool useExternalStickers = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -224,7 +254,13 @@ namespace Discord changeNickname: changeNickname, manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, - manageEmojisAndStickers: manageEmojis) + manageEmojisAndStickers: manageEmojisAndStickers, + useSlashCommands: useSlashCommands, + requestToSpeak: requestToSpeak, + manageThreads: manageThreads, + usePublicThreads: usePublicThreads, + usePrivateThreads: usePrivateThreads, + useExternalStickers: useExternalStickers) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -259,11 +295,18 @@ namespace Discord bool? manageNicknames = null, bool? manageRoles = null, bool? manageWebhooks = null, - bool? manageEmojis = null) + bool? manageEmojisAndStickers = null, + bool? useSlashCommands = null, + bool? requestToSpeak = null, + bool? manageThreads = null, + bool? usePublicThreads = null, + bool? usePrivateThreads = null, + bool? useExternalStickers = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, - useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, + useSlashCommands, requestToSpeak, manageThreads, usePublicThreads, usePrivateThreads, useExternalStickers); /// /// Returns a value that indicates if a specific is enabled diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 7c2d152a4..ee50710e8 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -10,68 +10,70 @@ namespace Discord [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public struct Color { + /// Gets the max decimal value of color. + public const uint MaxDecimalValue = 0xFFFFFF; /// Gets the default user color value. - public static readonly Color Default = new Color(0); + public static readonly Color Default = new(0); /// Gets the teal color value. /// A color struct with the hex value of 1ABC9C. - public static readonly Color Teal = new Color(0x1ABC9C); + public static readonly Color Teal = new(0x1ABC9C); /// Gets the dark teal color value. /// A color struct with the hex value of 11806A. - public static readonly Color DarkTeal = new Color(0x11806A); + public static readonly Color DarkTeal = new(0x11806A); /// Gets the green color value. /// A color struct with the hex value of 2ECC71. - public static readonly Color Green = new Color(0x2ECC71); + public static readonly Color Green = new(0x2ECC71); /// Gets the dark green color value. /// A color struct with the hex value of 1F8B4C. - public static readonly Color DarkGreen = new Color(0x1F8B4C); + public static readonly Color DarkGreen = new(0x1F8B4C); /// Gets the blue color value. /// A color struct with the hex value of 3498DB. - public static readonly Color Blue = new Color(0x3498DB); + public static readonly Color Blue = new(0x3498DB); /// Gets the dark blue color value. /// A color struct with the hex value of 206694. - public static readonly Color DarkBlue = new Color(0x206694); + public static readonly Color DarkBlue = new(0x206694); /// Gets the purple color value. /// A color struct with the hex value of 9B59B6. - public static readonly Color Purple = new Color(0x9B59B6); + public static readonly Color Purple = new(0x9B59B6); /// Gets the dark purple color value. /// A color struct with the hex value of 71368A. - public static readonly Color DarkPurple = new Color(0x71368A); + public static readonly Color DarkPurple = new(0x71368A); /// Gets the magenta color value. /// A color struct with the hex value of E91E63. - public static readonly Color Magenta = new Color(0xE91E63); + public static readonly Color Magenta = new(0xE91E63); /// Gets the dark magenta color value. /// A color struct with the hex value of AD1457. - public static readonly Color DarkMagenta = new Color(0xAD1457); + public static readonly Color DarkMagenta = new(0xAD1457); /// Gets the gold color value. /// A color struct with the hex value of F1C40F. - public static readonly Color Gold = new Color(0xF1C40F); + public static readonly Color Gold = new(0xF1C40F); /// Gets the light orange color value. /// A color struct with the hex value of C27C0E. - public static readonly Color LightOrange = new Color(0xC27C0E); + public static readonly Color LightOrange = new(0xC27C0E); /// Gets the orange color value. /// A color struct with the hex value of E67E22. - public static readonly Color Orange = new Color(0xE67E22); + public static readonly Color Orange = new(0xE67E22); /// Gets the dark orange color value. /// A color struct with the hex value of A84300. - public static readonly Color DarkOrange = new Color(0xA84300); + public static readonly Color DarkOrange = new(0xA84300); /// Gets the red color value. /// A color struct with the hex value of E74C3C. - public static readonly Color Red = new Color(0xE74C3C); + public static readonly Color Red = new(0xE74C3C); /// Gets the dark red color value. /// A color struct with the hex value of 992D22. - public static readonly Color DarkRed = new Color(0x992D22); + public static readonly Color DarkRed = new(0x992D22); /// Gets the light grey color value. /// A color struct with the hex value of 979C9F. - public static readonly Color LightGrey = new Color(0x979C9F); + public static readonly Color LightGrey = new(0x979C9F); /// Gets the lighter grey color value. /// A color struct with the hex value of 95A5A6. - public static readonly Color LighterGrey = new Color(0x95A5A6); + public static readonly Color LighterGrey = new(0x95A5A6); /// Gets the dark grey color value. /// A color struct with the hex value of 607D8B. - public static readonly Color DarkGrey = new Color(0x607D8B); + public static readonly Color DarkGrey = new(0x607D8B); /// Gets the darker grey color value. /// A color struct with the hex value of 546E7A. - public static readonly Color DarkerGrey = new Color(0x546E7A); + public static readonly Color DarkerGrey = new(0x546E7A); /// Gets the encoded value for this color. /// @@ -91,22 +93,27 @@ namespace Discord /// Initializes a struct with the given raw value. /// /// - /// The following will create a color that has a hex value of + /// The following will create a color that has a hex value of /// #607D8B. /// /// Color darkGrey = new Color(0x607D8B); /// /// /// The raw value of the color (e.g. 0x607D8B). + /// Value exceeds . public Color(uint rawValue) { + if (rawValue > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!", nameof(rawValue)); + RawValue = rawValue; } + /// /// Initializes a struct with the given RGB bytes. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color((byte)0b_01100000, (byte)0b_01111101, (byte)0b_10001011); @@ -115,19 +122,24 @@ namespace Discord /// The byte that represents the red color. /// The byte that represents the green color. /// The byte that represents the blue color. + /// Value exceeds . public Color(byte r, byte g, byte b) { - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + uint value = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; + + if (value > MaxDecimalValue) + throw new ArgumentException($"{nameof(RawValue)} of color cannot be greater than {MaxDecimalValue}!"); + + RawValue = value; } /// /// Initializes a struct with the given RGB value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607D8B. /// /// Color darkGrey = new Color(96, 125, 139); @@ -145,16 +157,15 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,255]."); if (b < 0 || b > 255) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,255]."); - RawValue = - ((uint)r << 16) | - ((uint)g << 8) | - (uint)b; + RawValue = ((uint)r << 16) + | ((uint)g << 8) + | (uint)b; } /// /// Initializes a struct with the given RGB float value. /// /// - /// The following will create a color that has a value of + /// The following will create a color that has a value of /// #607c8c. /// /// Color darkGrey = new Color(0.38f, 0.49f, 0.55f); @@ -172,10 +183,9 @@ namespace Discord throw new ArgumentOutOfRangeException(nameof(g), "Value must be within [0,1]."); if (b < 0.0f || b > 1.0f) throw new ArgumentOutOfRangeException(nameof(b), "Value must be within [0,1]."); - RawValue = - ((uint)(r * 255.0f) << 16) | - ((uint)(g * 255.0f) << 8) | - (uint)(b * 255.0f); + RawValue = ((uint)(r * 255.0f) << 16) + | ((uint)(g * 255.0f) << 8) + | (uint)(b * 255.0f); } public static bool operator ==(Color lhs, Color rhs) @@ -184,15 +194,22 @@ namespace Discord public static bool operator !=(Color lhs, Color rhs) => lhs.RawValue != rhs.RawValue; + public static implicit operator Color(uint rawValue) + => new(rawValue); + + public static implicit operator uint(Color color) + => color.RawValue; + public override bool Equals(object obj) - => (obj is Color c && RawValue == c.RawValue); + => obj is Color c && RawValue == c.RawValue; public override int GetHashCode() => RawValue.GetHashCode(); - public static implicit operator StandardColor(Color color) => - StandardColor.FromArgb((int)color.RawValue); - public static explicit operator Color(StandardColor color) => - new Color((uint)color.ToArgb() << 8 >> 8); + public static implicit operator StandardColor(Color color) + => StandardColor.FromArgb((int)color.RawValue); + + public static explicit operator Color(StandardColor color) + => new((uint)color.ToArgb() << 8 >> 8); /// /// Gets the hexadecimal representation of the color (e.g. #000ccc). diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index 9596a8338..f265bb938 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -12,17 +12,29 @@ namespace Discord /// string AvatarId { get; } /// + /// Gets the identifier of this user's banner. + /// + string BannerId { get; } + /// + /// Gets the user's banner color. + /// + /// + /// A struct representing the accent color of this user's banner. + /// + Color? AccentColor { get; } + /// /// Gets the avatar URL for this user. /// /// /// This property retrieves a URL for this user's avatar. In event that the user does not have a valid avatar - /// (i.e. their avatar identifier is not set), this property will return null. If you wish to + /// (i.e. their avatar identifier is not set), this method will return null. If you wish to /// retrieve the default avatar for this user, consider using (see /// example). /// /// - /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is - /// not set, a default avatar for this user will be returned instead. + /// The following example attempts to retrieve the user's current avatar and send it to a channel; if one is + /// not set, a default avatar for this user will be returned instead. /// /// @@ -34,6 +46,16 @@ namespace Discord /// string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); /// + /// Gets the banner URL for this user. + /// + /// The format to return. + /// The size of the image to return in. This can be any power of two between 16 and 2048. + /// + /// + /// A string representing the user's avatar URL; null if the user does not have an banner in place. + /// + string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256); + /// /// Gets the default avatar URL for this user. /// /// @@ -93,8 +115,8 @@ namespace Discord /// This method is used to obtain or create a channel used to send a direct message. /// /// In event that the current user cannot send a message to the target user, a channel can and will - /// still be created by Discord. However, attempting to send a message will yield a - /// with a 403 as its + /// still be created by Discord. However, attempting to send a message will yield a + /// with a 403 as its /// . There are currently no official workarounds by /// Discord. /// diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs index b2d017316..d5bc70d71 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord @@ -49,6 +49,11 @@ namespace Discord /// IUser Creator { get; } + /// + /// Gets the ID of the application owning this webhook. + /// + ulong? ApplicationId { get; } + /// /// Modifies this webhook. /// diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index 294b0c7fd..775b6aabc 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -41,8 +41,8 @@ namespace Discord.API public Optional Emoji { get; set; } [JsonProperty("created_at")] public Optional CreatedAt { get; set; } - [JsonProperty("buttons")] - public Optional Buttons { get; set; } + //[JsonProperty("buttons")] + //public Optional Buttons { get; set; } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs index f03cb8870..ba233cc6b 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -18,7 +18,7 @@ namespace Discord.API // New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata [JsonProperty("flags")] - public Optional Flags { get; set; } + public Optional Flags { get; set; } [JsonProperty("components")] public Optional Components { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs b/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs deleted file mode 100644 index 66f34c82e..000000000 --- a/src/Discord.Net.Rest/API/Common/RichPresenceButton.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord.API -{ - internal class RichPresenceButton - { - [JsonProperty("label")] - public string Label { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - } -} diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index d1f436afb..4d1b5b2b7 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -15,6 +15,10 @@ namespace Discord.API public Optional Bot { get; set; } [JsonProperty("avatar")] public Optional Avatar { get; set; } + [JsonProperty("banner")] + public Optional Banner { get; set; } + [JsonProperty("accent_color")] + public Optional AccentColor { get; set; } //CurrentUser [JsonProperty("verified")] diff --git a/src/Discord.Net.Rest/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs index cbd5fdad5..eb504a27c 100644 --- a/src/Discord.Net.Rest/API/Common/Webhook.cs +++ b/src/Discord.Net.Rest/API/Common/Webhook.cs @@ -21,5 +21,7 @@ namespace Discord.API [JsonProperty("user")] public Optional Creator { get; set; } + [JsonProperty("application_id")] + public ulong? ApplicationId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index bd4cc1a6c..1806d487c 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -28,7 +28,7 @@ namespace Discord.API.Rest public Optional AllowedMentions { get; set; } [JsonProperty("flags")] - public Optional Flags { get; set; } + public Optional Flags { get; set; } [JsonProperty("components")] public Optional Components { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs index ba8fcbb4e..8298ff19c 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -12,5 +12,7 @@ namespace Discord.API.Rest public Optional Embeds { get; set; } [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index b1efafae2..16b165575 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -9,11 +9,11 @@ netstandard2.0;netstandard2.1 Temporary.png https://github.com/Discord-Net-Labs/Discord.Net-Labs - 3.0.0-pre + 3.0.1-pre Discord.Net.Labs.Rest https://github.com/Discord-Net-Labs/Discord.Net-Labs - 2.3.4 - 2.3.4 + 3.0.1 + 3.0.1 ..\Discord.Net.Rest\Discord.Net.Rest.xml diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index c6c81ee75..0fc188b2e 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -3493,6 +3493,16 @@ of webhooks found within the guild. + + + Gets this guilds slash commands commands + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection + of application commands found within the guild. + + Returns the name of the guild. @@ -3650,6 +3660,9 @@ + + + @@ -4667,7 +4680,7 @@ - Represents a thread user recieved over the REST api. + Represents a thread user received over the REST api. @@ -4690,7 +4703,7 @@ Gets the guild user for this thread user. - A task representing the asyncronous get operation. The task returns a + A task representing the asynchronous get operation. The task returns a that represents the current thread user. @@ -4711,6 +4724,12 @@ + + + + + + @@ -4753,6 +4772,9 @@ + + + @@ -4875,6 +4897,9 @@ + + + diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 42cde4cfc..8bf7f6e43 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -55,7 +55,7 @@ namespace Discord.API _restClientProvider = restClientProvider; UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; - _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Ignore }; + _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include }; UseSystemClock = useSystemClock; RequestQueue = new RequestQueue(); @@ -1113,11 +1113,8 @@ namespace Discord.API Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.Equals(command.Description, ""); - options = RequestOptions.CreateOrClone(options); - - + options = RequestOptions.CreateOrClone(options); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); } @@ -1126,12 +1123,9 @@ namespace Discord.API Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.Equals(command.Description, ""); options = RequestOptions.CreateOrClone(options); - - return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); } @@ -1174,7 +1168,6 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } public async Task ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1182,21 +1175,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1223,29 +1202,15 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } + public async Task ModifyGuildApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task BulkOverwriteGuildApplicationUserCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) { @@ -1263,7 +1228,6 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } public async Task ModifyGuildApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { @@ -1271,21 +1235,7 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - try - { - return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); - } - catch (HttpException x) - { - if (x.HttpCode == HttpStatusCode.BadRequest) - { - var json = (x.Request as JsonRestRequest).Json; - throw new ApplicationCommandException(json, x); - } - - // Re-throw the http exception - throw; - } + return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); } public async Task BulkOverwriteGuildApplicationMessageCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index cb28d19b6..c204a2d1c 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -112,25 +112,25 @@ namespace Discord.Rest => InteractionHelper.CreateGlobalCommand(this, properties, options); public Task CreateGlobalCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, func, options); - public Task CreateGlobalUserCommand(UserCommandCreationProperties properties, RequestOptions options = null) + public Task CreateGlobalUserCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) => InteractionHelper.CreateGlobalUserCommand(this, properties, options); - public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) + public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalUserCommand(this, func, options); - public Task CreateGlobalMessageCommand(MessageCommandCreationProperties properties, RequestOptions options = null) + public Task CreateGlobalMessageCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) => InteractionHelper.CreateGlobalMessageCommand(this, properties, options); - public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) + public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalMessageCommand(this, func, options); public Task CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, func, options); - public Task CreateGuildUserCommand(UserCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + public Task CreateGuildUserCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildUserCommand(this, guildId, properties, options); - public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildUserCommand(this, guildId, func, options); - public Task CreateGuildMessageCommand(MessageCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + public Task CreateGuildMessageCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildMessageCommand(this, guildId, properties, options); - public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildMessageCommand(this, guildId, func, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 2fab63347..126a211c8 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -869,6 +869,18 @@ namespace Discord.Rest public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + //Interactions + /// + /// Gets this guilds slash commands commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + public async Task> GetApplicationCommandsAsync (RequestOptions options = null) + => await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false); + /// /// Returns the name of the guild. /// @@ -1154,6 +1166,8 @@ namespace Discord.Rest /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); - + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index d5006a6a9..b5c354047 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -200,22 +200,20 @@ namespace Discord.Rest await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); } - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) { - var args = new UserCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGlobalUserCommand(client, args, options).ConfigureAwait(false); } - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, UserCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGlobalUserCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -223,22 +221,20 @@ namespace Discord.Rest return RestGlobalUserCommand.Create(client, cmd); } - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) { - var args = new MessageCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGlobalMessageCommand(client, args, options).ConfigureAwait(false); } - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, MessageCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -246,7 +242,7 @@ namespace Discord.Rest return RestGlobalMessageCommand.Create(client, cmd); } - public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, UserCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -255,13 +251,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.User); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -307,7 +301,7 @@ namespace Discord.Rest await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, MessageCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -316,13 +310,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.Message); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -465,22 +457,20 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) { - var args = new UserCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGuildUserCommand(client, guildId, args, options).ConfigureAwait(false); } - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -488,22 +478,20 @@ namespace Discord.Rest return RestGuildUserCommand.Create(client, cmd, guildId); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) { - var args = new MessageCommandCreationProperties(); + var args = new ContextMenuCommandCreationProperties(); func(args); return await CreateGuildMessageCommand(client, guildId, args, options).ConfigureAwait(false); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties arg, RequestOptions options = null) + public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Description, ""); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -511,7 +499,7 @@ namespace Discord.Rest return RestGuildMessageCommand.Create(client, cmd, guildId); } - public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, UserCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -520,13 +508,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.User); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -573,7 +559,7 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, MessageCommandCreationProperties[] args, RequestOptions options = null) + public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -582,13 +568,11 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); Preconditions.Equals(arg.Type, ApplicationCommandType.Message); var model = new CreateApplicationCommandParams() { Name = arg.Name, - Description = arg.Description, Type = arg.Type }; @@ -642,15 +626,33 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); + var embed = args.Embed; + var embeds = args.Embeds; + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); - bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : message.Embeds.Any(); - if (!hasText && !hasEmbed) + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || message.Embeds.Any(); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new API.Rest.ModifyInteractionResponseParams { Content = args.Content, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value.Select(x => x.ToModel()).ToArray() : Optional.Create(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, }; @@ -667,10 +669,33 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = !string.IsNullOrEmpty(args.Content.GetValueOrDefault()); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new ModifyInteractionResponseParams { Content = args.Content, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, Flags = args.Flags diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 83ad2777e..9546025b0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,3 +1,4 @@ +using Discord.API; using Discord.API.Rest; using System; using System.Collections.Generic; @@ -33,9 +34,13 @@ namespace Discord.Rest if (msg.Author.Id != client.CurrentUser.Id && (args.Content.IsSpecified || args.Embeds.IsSpecified || args.AllowedMentions.IsSpecified)) throw new InvalidOperationException("Only the author of a message may modify the message content, embed, or allowed mentions."); + var embed = args.Embed; + var embeds = args.Embeds; + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(msg.Content); - bool hasEmbed = args.Embeds.IsSpecified ? args.Embeds.Value != null : msg.Embeds.Any(); - if (!hasText && !hasEmbed) + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || msg.Embeds.Any(); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); if (args.AllowedMentions.IsSpecified) @@ -61,22 +66,24 @@ namespace Discord.Rest } } - var embeds = new List(); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; - if (args.Embed.IsSpecified) + if (embed.IsSpecified && embed.Value != null) { - embeds.Add(args.Embed.Value.ToModel()); + apiEmbeds.Add(embed.Value.ToModel()); } - if (args.Embeds.IsSpecified) + if (embeds.IsSpecified && embeds.Value != null) { - embeds.AddRange(args.Embeds.Value.Select(x => x.ToModel())); + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); } + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new ModifyMessageParams { Content = args.Content, - Embeds = embeds.ToArray(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, Flags = args.Flags, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Unspecified, @@ -90,7 +97,13 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); - if (args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value) && args.Embeds.IsSpecified && args.Embeds.Value == null) + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0); + + if (!hasText && !hasEmbeds) Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); if (args.AllowedMentions.IsSpecified) @@ -117,22 +130,24 @@ namespace Discord.Rest } } - var embeds = new List(); + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; - if (args.Embed.IsSpecified) + if (embed.IsSpecified && embed.Value != null) { - embeds.Add(args.Embed.Value.ToModel()); + apiEmbeds.Add(embed.Value.ToModel()); } - if (args.Embeds.IsSpecified) + if (embeds.IsSpecified && embeds.Value != null) { - embeds.AddRange(args.Embeds.Value.Select(x => x.ToModel())); + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); } + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var apiArgs = new API.Rest.ModifyMessageParams { Content = args.Content, - Embeds = embeds.ToArray(), + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create(), Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, diff --git a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs index d74591e75..bb981bfdb 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestThreadUser.cs @@ -9,7 +9,7 @@ using Model = Discord.API.ThreadMember; namespace Discord.Rest { /// - /// Represents a thread user recieved over the REST api. + /// Represents a thread user received over the REST api. /// public class RestThreadUser : RestEntity { @@ -51,7 +51,7 @@ namespace Discord.Rest /// Gets the guild user for this thread user. /// /// - /// A task representing the asyncronous get operation. The task returns a + /// A task representing the asynchronous get operation. The task returns a /// that represents the current thread user. /// public Task GetGuildUser() diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index 7bc1447fe..618804fef 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -22,6 +22,10 @@ namespace Discord.Rest /// public string AvatarId { get; private set; } /// + public string BannerId { get; private set; } + /// + public Color? AccentColor { get; private set; } + /// public UserProperties? PublicFlags { get; private set; } /// @@ -61,6 +65,10 @@ namespace Discord.Rest { if (model.Avatar.IsSpecified) AvatarId = model.Avatar.Value; + if (model.Banner.IsSpecified) + BannerId = model.Banner.Value; + if (model.AccentColor.IsSpecified) + AccentColor = model.AccentColor.Value; if (model.Discriminator.IsSpecified) DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); if (model.Bot.IsSpecified) @@ -92,6 +100,10 @@ namespace Discord.Rest public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// + public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256) + => CDN.GetUserBannerUrl(Id, BannerId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 9baddf003..5ae09fde0 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -24,6 +24,8 @@ namespace Discord.Rest public ulong? GuildId { get; private set; } /// public IUser Creator { get; private set; } + /// + public ulong? ApplicationId { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -66,6 +68,8 @@ namespace Discord.Rest GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + + ApplicationId = model.ApplicationId; } /// diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index f8676c783..0c2e0f8c2 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -68,6 +68,7 @@ namespace Discord.Rest model.Video = entity.Video.Value.ToModel(); return model; } + public static API.AllowedMentions ToModel(this AllowedMentions entity) { return new API.AllowedMentions() diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index af17578fb..167ccebb0 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -8,7 +8,7 @@ net461;netstandard2.0;netstandard2.1 netstandard2.0;netstandard2.1 true - 3.0.0-pre + 3.0.1-pre https://github.com/Discord-Net-Labs/Discord.Net-Labs https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png @@ -16,6 +16,8 @@ ..\Discord.Net.WebSocket\Discord.Net.WebSocket.xml + 3.0.1 + 3.0.1 TRACE; diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index 742bebac0..485a83a61 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3599,40 +3599,43 @@ - + + + + Represents a Websocket-based slash command received over the gateway. - + The data associated with this interaction. - + - Represents the data tied with the interaction. + Represents the data tied with the interaction. - + - + Represents a Websocket-based slash command received over the gateway. - + The data associated with this interaction. - + - Represents the data tied with the interaction. + Represents the data tied with the interaction. - + @@ -3664,15 +3667,19 @@ - + - Acknowledges this interaction with the . + Defers an interaction and responds with type 5 () + to send this message ephemerally, otherwise . The request options for this async request. A task that represents the asynchronous operation of acknowledging the interaction. + + + Represents the data sent with a . @@ -3812,6 +3819,11 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. + + + Base class for User, Message, and Slash command interactions + + The data associated with this interaction. @@ -3823,7 +3835,7 @@ - + Acknowledges this interaction with the . @@ -3831,6 +3843,27 @@ A task that represents the asynchronous operation of acknowledging the interaction. + + + Represents the base data tied with the interaction. + + + + + + + + The 's received with this interaction. + + + + + Represents the base Websocket-based recieved by the gateway + + + + + @@ -3943,14 +3976,25 @@ A task that represents the asynchronous operation of acknowledging the interaction. - + Acknowledges this interaction. + to send this message ephemerally, otherwise . + The request options for this async request. A task that represents the asynchronous operation of acknowledging the interaction. + + + + + + + + + Represents a WebSocket-based invite to a guild. @@ -4487,6 +4531,12 @@ + + + + + + @@ -4545,6 +4595,12 @@ + + + + + + @@ -4608,7 +4664,7 @@ Returns the position of the user within the role hierarchy. - The returned value equal to the position of the highest role the user has, or + The returned value equal to the position of the highest role the user has, or if user is the server owner. @@ -4730,6 +4786,12 @@ + + + + + + @@ -4783,6 +4845,12 @@ + + + + + + @@ -4890,6 +4958,12 @@ + + + + + + @@ -4920,6 +4994,12 @@ + + + + + + @@ -4958,6 +5038,9 @@ + + + @@ -5039,6 +5122,14 @@ + + + Webhook users does not support banners. + + + + Webhook users does not support accent colors. + diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 6847d8580..450145f1c 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -30,7 +30,16 @@ namespace Discord.WebSocket /// public override IActivity Activity { get => _shards[0].Activity; protected set { } } - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; + internal new DiscordSocketApiClient ApiClient + { + get + { + if (base.ApiClient.CurrentUserId == null) + base.ApiClient.CurrentUserId = CurrentUser?.Id; + + return base.ApiClient; + } + } /// public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(GetGuildCount); /// diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 9221a3faa..e19cedb33 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -79,7 +79,7 @@ namespace Discord.API if (msg != null) { #if DEBUG_PACKETS - Console.WriteLine($"<- {msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); #endif await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); @@ -96,7 +96,7 @@ namespace Discord.API if (msg != null) { #if DEBUG_PACKETS - Console.WriteLine($"<- {msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); + Console.WriteLine($"<- {(GatewayOpCode)msg.Operation} [{msg.Type ?? "none"}] : {(msg.Payload as Newtonsoft.Json.Linq.JToken)?.ToString().Length}"); #endif await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); @@ -105,6 +105,10 @@ namespace Discord.API }; WebSocketClient.Closed += async ex => { +#if DEBUG_PACKETS + Console.WriteLine(ex); +#endif + await DisconnectAsync().ConfigureAwait(false); await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; @@ -166,6 +170,11 @@ namespace Discord.API var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; } + +#if DEBUG_PACKETS + Console.WriteLine("Connecting to gateway: " + _gatewayUrl); +#endif + await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); ConnectionState = ConnectionState.Connected; @@ -237,7 +246,9 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var props = new Dictionary { - ["$device"] = "Discord.Net" + ["$device"] = "Discord.Net Labs", + ["$os"] = Environment.OSVersion.Platform.ToString(), + [$"browser"] = "Discord.Net Labs" }; 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 c32bb3f49..5c385fe01 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1478,6 +1478,9 @@ namespace Discord.WebSocket /// async Task> IGuild.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options).ConfigureAwait(false); + /// + async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) + => await GetApplicationCommandsAsync(options).ConfigureAwait(false); void IDisposable.Dispose() { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs similarity index 69% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs index 828ed14bb..5d8b0af43 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs @@ -10,14 +10,14 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationMessageCommand : SocketCommandBase + public class SocketMessageCommand : SocketCommandBase { /// /// The data associated with this interaction. /// - new public SocketApplicationMessageCommandData Data { get; } + new public SocketMessageCommandData Data { get; } - internal SocketApplicationMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { var dataModel = model.Data.IsSpecified ? @@ -28,12 +28,12 @@ namespace Discord.WebSocket if (this.Channel is SocketGuildChannel guildChannel) guildId = guildChannel.Guild.Id; - Data = SocketApplicationMessageCommandData.Create(client, dataModel, model.Id, guildId); + Data = SocketMessageCommandData.Create(client, dataModel, model.Id, guildId); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketApplicationMessageCommand(client, model, channel); + var entity = new SocketMessageCommand(client, model, channel); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs similarity index 89% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs index ead330ef6..9c925c4ce 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketApplicationMessageCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs @@ -6,12 +6,15 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// - public class SocketApplicationMessageCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketMessageCommandData : SocketEntity, IApplicationCommandInteractionData { /// public string Name { get; private set; } + /// + /// The message selected to run the command + /// public SocketMessage Message { get; private set; } internal Dictionary guildMembers { get; private set; } @@ -27,15 +30,11 @@ namespace Discord.WebSocket private ulong? guildId; - private ApplicationCommandType Type; - - internal SocketApplicationMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) + internal SocketMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) : base(client, model.Id) { this.guildId = guildId; - this.Type = (ApplicationCommandType)model.Type; - if (model.Resolved.IsSpecified) { var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; @@ -126,9 +125,9 @@ namespace Discord.WebSocket } } - internal static SocketApplicationMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { - var entity = new SocketApplicationMessageCommandData(client, model, guildId); + var entity = new SocketMessageCommandData(client, model, guildId); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs similarity index 70% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs index 603a11397..f4c3a8b7b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs @@ -10,14 +10,14 @@ namespace Discord.WebSocket /// /// Represents a Websocket-based slash command received over the gateway. /// - public class SocketApplicationUserCommand : SocketCommandBase + public class SocketUserCommand : SocketCommandBase { /// /// The data associated with this interaction. /// - new public SocketApplicationUserCommandData Data { get; } + new public SocketUserCommandData Data { get; } - internal SocketApplicationUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) + internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) { var dataModel = model.Data.IsSpecified ? @@ -28,12 +28,12 @@ namespace Discord.WebSocket if (this.Channel is SocketGuildChannel guildChannel) guildId = guildChannel.Guild.Id; - Data = SocketApplicationUserCommandData.Create(client, dataModel, model.Id, guildId); + Data = SocketUserCommandData.Create(client, dataModel, model.Id, guildId); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) { - var entity = new SocketApplicationUserCommand(client, model, channel); + var entity = new SocketUserCommand(client, model, channel); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs similarity index 86% rename from src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs rename to src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs index a6eb24ca5..1cdfed097 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketApplicationUserCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs @@ -6,13 +6,15 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { /// - /// Represents the data tied with the interaction. + /// Represents the data tied with the interaction. /// - public class SocketApplicationUserCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketUserCommandData : SocketEntity, IApplicationCommandInteractionData { /// public string Name { get; private set; } - + /// + /// The user used to run the command + /// public SocketUser Member { get; private set; } internal Dictionary guildMembers { get; private set; } @@ -28,15 +30,11 @@ namespace Discord.WebSocket private ulong? guildId; - private ApplicationCommandType Type; - - internal SocketApplicationUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) + internal SocketUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) : base(client, model.Id) { this.guildId = guildId; - this.Type = (ApplicationCommandType)model.Type; - if (model.Resolved.IsSpecified) { var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; @@ -99,9 +97,9 @@ namespace Discord.WebSocket } } - internal static SocketApplicationUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { - var entity = new SocketApplicationUserCommandData(client, model, guildId); + var entity = new SocketUserCommandData(client, model, guildId); entity.Update(model); return entity; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs index 921f81de4..cc91b098f 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -4,12 +4,13 @@ using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.MessageComponentInteractionData; using Discord.Rest; +using System.Collections.Generic; namespace Discord.WebSocket { - /// - /// Represents a Websocket-based interaction type for Message Components. - /// +/// +/// Represents a Websocket-based interaction type for Message Components. +/// public class SocketMessageComponent : SocketInteraction { /// @@ -123,7 +124,7 @@ namespace Discord.WebSocket }; if (ephemeral) - response.Data.Value.Flags = 64; + response.Data.Value.Flags = MessageFlags.Ephemeral; await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); } @@ -149,8 +150,28 @@ namespace Discord.WebSocket Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed."); } - if (args.Embeds.IsSpecified) - Preconditions.AtMost(args.Embeds.Value?.Length ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); + var embed = args.Embed; + var embeds = args.Embeds; + + bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(Message.Content); + bool hasEmbeds = (embed.IsSpecified && embed.Value != null) || (embeds.IsSpecified && embeds.Value?.Length > 0) || Message.Embeds.Any(); + + if (!hasText && !hasEmbeds) + Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); + + var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null; + + if (embed.IsSpecified && embed.Value != null) + { + apiEmbeds.Add(embed.Value.ToModel()); + } + + if (embeds.IsSpecified && embeds.Value != null) + { + apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel())); + } + + Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed."); // check that user flag and user Id list are exclusive, same with role flag and role Id list if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue) @@ -176,11 +197,11 @@ namespace Discord.WebSocket { Content = args.Content, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified, - Embeds = args.Embeds.IsSpecified ? args.Embeds.Value?.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, + Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified, Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - Flags = args.Flags.IsSpecified ? (int?)args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified + Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified } }; @@ -213,27 +234,43 @@ namespace Discord.WebSocket AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, IsTTS = isTTS, Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, }; if (ephemeral) - args.Flags = 64; + args.Flags = MessageFlags.Ephemeral; return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } /// - /// Acknowledges this interaction with the . + /// Defers an interaction and responds with type 5 () /// + /// to send this message ephemerally, otherwise . /// The request options for this async request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public override Task DeferAsync(RequestOptions options = null) + public Task DeferLoadingAsync(bool ephemeral = false, RequestOptions options = null) + { + var response = new API.InteractionResponse() + { + Type = InteractionResponseType.DeferredChannelMessageWithSource, + Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + + }; + + return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); + } + + /// + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { var response = new API.InteractionResponse() { Type = InteractionResponseType.DeferredUpdateMessage, + Data = ephemeral ? new API.InteractionCallbackData() { Flags = MessageFlags.Ephemeral } : Optional.Unspecified + }; return Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, this.Token, options); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs index 3013099c7..36542f15a 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -36,6 +36,6 @@ namespace Discord.WebSocket var entity = new SocketSlashCommand(client, model, channel); entity.Update(model); return entity; - } + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs index f33008cf3..f9c12257e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs @@ -86,9 +86,9 @@ namespace Discord.WebSocket break; case ApplicationCommandOptionType.Integer: { - if (model.Value.Value is int val) + if (model.Value.Value is long val) this.Value = val; - else if (int.TryParse(model.Value.Value.ToString(), out int res)) + else if (long.TryParse(model.Value.Value.ToString(), out long res)) this.Value = res; } break; @@ -109,7 +109,7 @@ namespace Discord.WebSocket } break; } - + } this.Options = model.Options.IsSpecified diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 4dbadc552..6ba9ba05a 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -9,6 +9,9 @@ using Model = Discord.API.Interaction; namespace Discord.WebSocket { + /// + /// Base class for User, Message, and Slash command interactions + /// public class SocketCommandBase : SocketInteraction { /// @@ -105,7 +108,7 @@ namespace Discord.WebSocket }; if (ephemeral) - response.Data.Value.Flags = 64; + response.Data.Value.Flags = MessageFlags.Ephemeral; await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); } @@ -140,7 +143,7 @@ namespace Discord.WebSocket }; if (ephemeral) - args.Flags = 64; + args.Flags = MessageFlags.Ephemeral; return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } @@ -151,7 +154,7 @@ namespace Discord.WebSocket /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public override Task DeferAsync(RequestOptions options = null) + public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) { var response = new API.InteractionResponse { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs index 7d7dbbce7..dde981eb9 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -5,13 +5,17 @@ using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket { + /// + /// Represents the base data tied with the interaction. + /// public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData { + /// public string Name { get; private set; } - + /// + /// The 's received with this interaction. + /// public IReadOnlyCollection Options { get; private set; } - // id - // type internal Dictionary guildMembers { get; private set; } = new Dictionary(); internal Dictionary users { get; private set; } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs index 369f1f868..6aa15978e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs @@ -9,8 +9,12 @@ using Model = Discord.API.ApplicationCommandInteractionDataOption; namespace Discord.WebSocket { + /// + /// Represents the base Websocket-based recieved by the gateway + /// public class SocketCommandBaseDataOption : IApplicationCommandInteractionDataOption { + /// public string Name { get; private set; } /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 59bae6f08..9b42ed0e2 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -71,9 +71,9 @@ namespace Discord.WebSocket if (dataModel != null) { if (dataModel.Type.Equals(ApplicationCommandType.User)) - return SocketApplicationUserCommand.Create(client, model, channel); + return SocketUserCommand.Create(client, model, channel); if (dataModel.Type.Equals(ApplicationCommandType.Message)) - return SocketApplicationMessageCommand.Create(client, model, channel); + return SocketMessageCommand.Create(client, model, channel); } } return SocketSlashCommand.Create(client, model, channel); @@ -172,20 +172,37 @@ namespace Discord.WebSocket /// A task that represents the asynchronous operation of acknowledging the interaction. /// [Obsolete("This method deprecated, please use DeferAsync instead")] - public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options); + public Task AcknowledgeAsync(RequestOptions options = null) => DeferAsync(options: options); /// /// Acknowledges this interaction. /// + /// to send this message ephemerally, otherwise . + /// The request options for this async request. /// /// A task that represents the asynchronous operation of acknowledging the interaction. /// - public abstract Task DeferAsync(RequestOptions options = null); + public abstract Task DeferAsync(bool ephemeral = false, RequestOptions options = null); private bool CheckToken() { // Tokens last for 15 minutes according to https://discord.com/developers/docs/interactions/slash-commands#responding-to-an-interaction return (DateTime.UtcNow - this.CreatedAt.UtcDateTime).TotalMinutes <= 15d; } + + // IDiscordInteraction + + /// + async Task IDiscordInteraction.FollowupAsync (string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, + RequestOptions options, MessageComponent component, Embed embed) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, options, component, embed).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.GetOriginalResponseAsync (RequestOptions options) + => await GetOriginalResponseAsync(options).ConfigureAwait(false); + + /// + async Task IDiscordInteraction.ModifyOriginalResponseAsync (Action func, RequestOptions options) + => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 82c656486..1b62d14dd 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -122,7 +122,10 @@ namespace Discord.WebSocket } internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) { - if (model.Type == MessageType.Default || model.Type == MessageType.Reply) + if (model.Type == MessageType.Default || + model.Type == MessageType.Reply || + model.Type == MessageType.ApplicationCommand || + model.Type == MessageType.ThreadStarterMessage) return SocketUserMessage.Create(discord, state, author, channel, model); else return SocketSystemMessage.Create(discord, state, author, channel, model); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 15c5182fc..b1bce5934 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -12,6 +12,8 @@ namespace Discord.WebSocket public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } public override string AvatarId { get; internal set; } + public override string BannerId { get; internal set; } + public override Color? AccentColor { get; internal set; } internal override SocketPresence Presence { get; set; } public override bool IsWebhook => false; @@ -47,7 +49,7 @@ namespace Discord.WebSocket discord.RemoveUser(Id); } } - + internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 805a88110..d99310540 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -29,6 +29,10 @@ namespace Discord.WebSocket /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index f79fc7afe..ac8409a32 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -38,6 +38,11 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } + /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); internal override SocketPresence Presence { get; set; } @@ -91,7 +96,7 @@ namespace Discord.WebSocket /// Returns the position of the user within the role hierarchy. /// /// - /// The returned value equal to the position of the highest role the user has, or + /// The returned value equal to the position of the highest role the user has, or /// if user is the server owner. /// public int Hierarchy diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index 7b11257a3..e821238ee 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -29,6 +29,10 @@ namespace Discord.WebSocket /// public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } /// + public override string BannerId { get { return GlobalUser.BannerId; } internal set { GlobalUser.BannerId = value; } } + /// + public override Color? AccentColor { get { return GlobalUser.AccentColor; } internal set { GlobalUser.AccentColor = value; } } + /// internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } /// public UserProperties Flags { get; internal set; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs index d1237d598..5fb1f56e5 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs @@ -36,7 +36,7 @@ namespace Discord.WebSocket /// public string Nickname - => GuildUser.Nickname; + => GuildUser.Nickname; /// public DateTimeOffset? PremiumSince @@ -53,6 +53,20 @@ namespace Discord.WebSocket internal set => GuildUser.AvatarId = value; } + /// + public override string BannerId + { + get => GuildUser.BannerId; + internal set => GuildUser.BannerId = value; + } + + /// + public override Color? AccentColor + { + get => GuildUser.AccentColor; + internal set => GuildUser.AccentColor = value; + } + /// public override ushort DiscriminatorValue { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index 840a1c30b..180e60a3b 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -19,9 +19,16 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + + /// + public override string BannerId { get; internal set; } + + /// + public override Color? AccentColor { get; internal set; } + /// public override bool IsBot { get; internal set; } - + /// public override bool IsWebhook => false; /// diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 025daf29a..c50fbee4f 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -25,6 +25,10 @@ namespace Discord.WebSocket /// public abstract string AvatarId { get; internal set; } /// + public abstract string BannerId { get; internal set; } + /// + public abstract Color? AccentColor { get; internal set; } + /// public abstract bool IsWebhook { get; } /// public UserProperties? PublicFlags { get; private set; } @@ -64,6 +68,16 @@ namespace Discord.WebSocket AvatarId = model.Avatar.Value; hasChanges = true; } + if (model.Banner.IsSpecified && model.Banner.Value != BannerId) + { + BannerId = model.Banner.Value; + hasChanges = true; + } + if (model.AccentColor.IsSpecified && model.AccentColor.Value != AccentColor?.RawValue) + { + AccentColor = model.AccentColor.Value; + hasChanges = true; + } if (model.Discriminator.IsSpecified) { var newVal = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture); @@ -99,6 +113,10 @@ namespace Discord.WebSocket public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + /// + public string GetBannerUrl(ImageFormat format = ImageFormat.Auto, ushort size = 256) + => CDN.GetUserBannerUrl(Id, BannerId, size, format); + /// public string GetDefaultAvatarUrl() => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index 2b0ecbb19..f1269e649 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -24,6 +24,23 @@ namespace Discord.WebSocket public override ushort DiscriminatorValue { get; internal set; } /// public override string AvatarId { get; internal set; } + + /// + /// Webhook users does not support banners. + public override string BannerId + { + get => throw new NotSupportedException("Webhook users does not support banners."); + internal set => throw new NotSupportedException("Webhook users does not support banners."); + } + + /// + /// Webhook users does not support accent colors. + public override Color? AccentColor + { + get => throw new NotSupportedException("Webhook users does not support accent colors."); + internal set => throw new NotSupportedException("Webhook users does not support accent colors."); + } + /// public override bool IsBot { get; internal set; } diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj index 13e5cf111..f04dedf43 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj @@ -6,7 +6,7 @@ Discord.Webhook A core Discord.Net Labs library containing the Webhook client and models. netstandard2.0;netstandard2.1 - 2.3.4 + 3.0.0-pre Discord.Net.Labs.Webhook https://github.com/Discord-Net-Labs/Discord.Net-Labs https://github.com/Discord-Net-Labs/Discord.Net-Labs diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml index f629c2c29..d1bafb9a3 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml @@ -31,7 +31,7 @@ Thrown if the is an invalid format. Thrown if the is null or whitespace. - + Sends a message to the channel for this webhook. Returns the ID of the created message. @@ -99,6 +99,11 @@ Gets or sets the allowed mentions of the message. + + + Gets or sets the components that the message should display. + + Could not find a webhook with the supplied credentials. diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 91d077411..d4affb08b 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -88,8 +88,8 @@ namespace Discord.Webhook /// Sends a message to the channel for this webhook. /// Returns the ID of the created message. public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, - string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null) - => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options); + string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent component = null) + => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, component); /// /// Modifies a message posted using this webhook. diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs index dec7b6e3b..ca2ff10a0 100644 --- a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -22,5 +22,9 @@ namespace Discord.Webhook /// Gets or sets the allowed mentions of the message. /// public Optional AllowedMentions { get; set; } + /// + /// Gets or sets the components that the message should display. + /// + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index bbb160fcd..2a5c4786e 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -17,6 +17,7 @@ namespace Discord.Webhook public string Name { get; private set; } public string AvatarId { get; private set; } public ulong? GuildId { get; private set; } + public ulong? ApplicationId { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -44,6 +45,8 @@ namespace Discord.Webhook GuildId = model.GuildId.Value; if (model.Name.IsSpecified) Name = model.Name.Value; + + ApplicationId = model.ApplicationId; } public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 528848f7f..6e3651323 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -21,7 +21,7 @@ namespace Discord.Webhook return RestInternalWebhook.Create(client, model); } public static async Task SendMessageAsync(DiscordWebhookClient client, - string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options) + string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, MessageComponent component) { var args = new CreateWebhookMessageParams { @@ -37,6 +37,8 @@ namespace Discord.Webhook args.AvatarUrl = avatarUrl; if (allowedMentions != null) args.AllowedMentions = allowedMentions.ToModel(); + if (component != null) + args.Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray(); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); return model.Id; @@ -83,7 +85,8 @@ namespace Discord.Webhook : Optional.Create(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() - : Optional.Create() + : Optional.Create(), + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, }; await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options) diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 595203eba..eb3bd9b6d 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net.Labs - 3.0.1-pre$suffix$ + 3.0.2-pre$suffix$ Discord.Net Labs Discord.Net Contributors quinchs @@ -14,23 +14,23 @@ https://avatars.githubusercontent.com/u/84047264 - - - + + + - - - + + + - - - + + + diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index 137dc5575..9be109c6e 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -91,7 +91,13 @@ namespace Discord AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames); AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles); AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks); - AssertFlag(() => new GuildPermissions(manageEmojis: true), GuildPermission.ManageEmojis); + AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); + AssertFlag(() => new GuildPermissions(useSlashCommands: true), GuildPermission.UseSlashCommands); + AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); + AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); + AssertFlag(() => new GuildPermissions(usePublicThreads: true), GuildPermission.UsePublicThreads); + AssertFlag(() => new GuildPermissions(usePrivateThreads: true), GuildPermission.UsePrivateThreads); + AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers); } /// @@ -161,7 +167,13 @@ namespace Discord AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable)); AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable)); AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable)); - AssertUtil(GuildPermission.ManageEmojis, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojis: enable)); + AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable)); + AssertUtil(GuildPermission.UseSlashCommands, x => x.UseSlashCommands, (p, enable) => p.Modify(useSlashCommands: enable)); + AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable)); + AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable)); + AssertUtil(GuildPermission.UsePublicThreads, x => x.UsePublicThreads, (p, enable) => p.Modify(usePublicThreads: enable)); + AssertUtil(GuildPermission.UsePrivateThreads, x => x.UsePrivateThreads, (p, enable) => p.Modify(usePrivateThreads: enable)); + AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable)); } } } From 8e19bd77ea510b7ae777f462ae832ab6d96aee95 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 21 Aug 2021 00:54:06 -0300 Subject: [PATCH 10/14] Code cleanup and simplification --- .../ApplicationCommandInteractionData.cs | 4 +- src/Discord.Net.Rest/API/Net/IResolvable.cs | 14 ++ .../Discord.Net.WebSocket.xml | 56 ++++---- .../Message Commands/SocketMessageCommand.cs | 2 +- .../SocketMessageCommandData.cs | 128 ++--------------- .../User Commands/SocketUserCommand.cs | 2 +- .../User Commands/SocketUserCommandData.cs | 98 ++----------- .../Slash Commands/SocketSlashCommand.cs | 2 +- .../Slash Commands/SocketSlashCommandData.cs | 94 +----------- .../SocketSlashCommandDataOption.cs | 22 +-- .../SocketBaseCommand/SocketCommandBase.cs | 2 +- .../SocketCommandBaseData.cs | 127 +++-------------- .../SocketCommandBaseDataOption.cs | 134 ------------------ .../SocketBaseCommand/SocketResolvableData.cs | 113 +++++++++++++++ 14 files changed, 216 insertions(+), 582 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Net/IResolvable.cs delete mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs create mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs index ed86b0b4a..bb395df13 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Discord.API { - internal class ApplicationCommandInteractionData : IDiscordInteractionData + internal class ApplicationCommandInteractionData : IResolvable, IDiscordInteractionData { [JsonProperty("id")] public ulong Id { get; set; } @@ -18,7 +18,7 @@ namespace Discord.API public Optional Resolved { get; set; } [JsonProperty("type")] - public Optional Type { get; set; } + public ApplicationCommandType Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Net/IResolvable.cs b/src/Discord.Net.Rest/API/Net/IResolvable.cs new file mode 100644 index 000000000..fe31cd75e --- /dev/null +++ b/src/Discord.Net.Rest/API/Net/IResolvable.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal interface IResolvable + { + Optional Resolved { get; } + + } +} diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index 485a83a61..f7137c90c 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3617,8 +3617,16 @@ Represents the data tied with the interaction. - + + + Gets the messagte associated with this message command. + + + + + Note Not implemented for + @@ -3635,8 +3643,16 @@ Represents the data tied with the interaction. - + + + The user used to run the command + + + + + Note Not implemented for + @@ -3715,14 +3731,6 @@ Represents the data tied with the interaction. - - - - - - The 's received with this interaction. - - Represents a Websocket-based recieved by the gateway @@ -3821,7 +3829,7 @@ - Base class for User, Message, and Slash command interactions + Base class for User, Message, and Slash command interactions @@ -3843,36 +3851,22 @@ A task that represents the asynchronous operation of acknowledging the interaction. - + Represents the base data tied with the interaction. - + - + - The 's received with this interaction. + The received with this interaction. - - - Represents the base Websocket-based recieved by the gateway - - - - - - - - - - - - + - The sub command options received for this sub command group. + Represents the base data tied with the interaction. diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs index 5d8b0af43..72f040cc6 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommand.cs @@ -15,7 +15,7 @@ namespace Discord.WebSocket /// /// The data associated with this interaction. /// - new public SocketMessageCommandData Data { get; } + public new SocketMessageCommandData Data { get; } internal SocketMessageCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs index 9c925c4ce..e78da223d 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/Message Commands/SocketMessageCommandData.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using Model = Discord.API.ApplicationCommandInteractionData; @@ -8,132 +7,29 @@ namespace Discord.WebSocket /// /// Represents the data tied with the interaction. /// - public class SocketMessageCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketMessageCommandData : SocketCommandBaseData { - /// - public string Name { get; private set; } /// - /// The message selected to run the command + /// Gets the messagte associated with this message command. /// - public SocketMessage Message { get; private set; } - - internal Dictionary guildMembers { get; private set; } - = new Dictionary(); - internal Dictionary users { get; private set; } - = new Dictionary(); - internal Dictionary channels { get; private set; } - = new Dictionary(); - internal Dictionary roles { get; private set; } - = new Dictionary(); - - IReadOnlyCollection IApplicationCommandInteractionData.Options => throw new System.NotImplementedException(); + public SocketMessage Message + => ResolvableData?.Messages.FirstOrDefault().Value; - private ulong? guildId; + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); internal SocketMessageCommandData(DiscordSocketClient client, Model model, ulong? guildId) - : base(client, model.Id) - { - this.guildId = guildId; - - if (model.Resolved.IsSpecified) - { - var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; - - var resolved = model.Resolved.Value; - - if (resolved.Users.IsSpecified) - { - foreach (var user in resolved.Users.Value) - { - var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); - - this.users.Add(ulong.Parse(user.Key), socketUser); - } - } - - if (resolved.Channels.IsSpecified) - { - foreach (var channel in resolved.Channels.Value) - { - SocketChannel socketChannel = guild != null - ? guild.GetChannel(channel.Value.Id) - : Discord.GetChannel(channel.Value.Id); - - if (socketChannel == null) - { - var channelModel = guild != null - ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() - : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + : base(client, model, guildId) { } - socketChannel = guild != null - ? SocketGuildChannel.Create(guild, Discord.State, channelModel) - : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); - } - - Discord.State.AddChannel(socketChannel); - this.channels.Add(ulong.Parse(channel.Key), socketChannel); - } - } - - if (resolved.Members.IsSpecified) - { - foreach (var member in resolved.Members.Value) - { - member.Value.User = resolved.Users.Value[member.Key]; - var user = guild.AddOrUpdateUser(member.Value); - this.guildMembers.Add(ulong.Parse(member.Key), user); - } - } - - if (resolved.Roles.IsSpecified) - { - foreach (var role in resolved.Roles.Value) - { - var socketRole = guild.AddOrUpdateRole(role.Value); - this.roles.Add(ulong.Parse(role.Key), socketRole); - } - } - - if (resolved.Messages.IsSpecified) - { - foreach (var msg in resolved.Messages.Value) - { - var channel = client.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; - - SocketUser author; - if (guild != null) - { - if (msg.Value.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, client.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); - else - author = guild.GetUser(msg.Value.Author.Value.Id); - } - else - author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); - - if (channel == null) - { - if (!msg.Value.GuildId.IsSpecified) // assume it is a DM - { - channel = client.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, client.State); - } - } - - this.Message = SocketMessage.Create(client, client.State, author, channel, msg.Value); - } - } - } - } - - internal static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal new static SocketMessageCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { var entity = new SocketMessageCommandData(client, model, guildId); entity.Update(model); return entity; } - internal void Update(Model model) - { - this.Name = model.Name; - } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs index f4c3a8b7b..5345c08f7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommand.cs @@ -15,7 +15,7 @@ namespace Discord.WebSocket /// /// The data associated with this interaction. /// - new public SocketUserCommandData Data { get; } + public new SocketUserCommandData Data { get; } internal SocketUserCommand(DiscordSocketClient client, Model model, ISocketMessageChannel channel) : base(client, model, channel) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs index 1cdfed097..70eb4adff 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket @@ -8,104 +6,28 @@ namespace Discord.WebSocket /// /// Represents the data tied with the interaction. /// - public class SocketUserCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketUserCommandData : SocketCommandBaseData { - /// - public string Name { get; private set; } /// - /// The user used to run the command + /// The user used to run the command /// public SocketUser Member { get; private set; } - internal Dictionary guildMembers { get; private set; } - = new Dictionary(); - internal Dictionary users { get; private set; } - = new Dictionary(); - internal Dictionary channels { get; private set; } - = new Dictionary(); - internal Dictionary roles { get; private set; } - = new Dictionary(); - - IReadOnlyCollection IApplicationCommandInteractionData.Options => throw new System.NotImplementedException(); - - private ulong? guildId; + /// + /// + /// Note Not implemented for + /// + public override IReadOnlyCollection Options + => throw new System.NotImplementedException(); internal SocketUserCommandData(DiscordSocketClient client, Model model, ulong? guildId) - : base(client, model.Id) - { - this.guildId = guildId; - - if (model.Resolved.IsSpecified) - { - var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; - - var resolved = model.Resolved.Value; - - if (resolved.Users.IsSpecified) - { - foreach (var user in resolved.Users.Value) - { - var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); - - this.users.Add(ulong.Parse(user.Key), socketUser); - } - } + : base(client, model, guildId) { } - if (resolved.Channels.IsSpecified) - { - foreach (var channel in resolved.Channels.Value) - { - SocketChannel socketChannel = guild != null - ? guild.GetChannel(channel.Value.Id) - : Discord.GetChannel(channel.Value.Id); - - if (socketChannel == null) - { - var channelModel = guild != null - ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() - : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); - - socketChannel = guild != null - ? SocketGuildChannel.Create(guild, Discord.State, channelModel) - : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); - } - - Discord.State.AddChannel(socketChannel); - this.channels.Add(ulong.Parse(channel.Key), socketChannel); - } - } - - if (resolved.Members.IsSpecified) - { - foreach (var member in resolved.Members.Value) - { - member.Value.User = resolved.Users.Value[member.Key]; - var user = guild.AddOrUpdateUser(member.Value); - this.guildMembers.Add(ulong.Parse(member.Key), user); - this.Member = user; - } - } - - if (resolved.Roles.IsSpecified) - { - foreach (var role in resolved.Roles.Value) - { - var socketRole = guild.AddOrUpdateRole(role.Value); - this.roles.Add(ulong.Parse(role.Key), socketRole); - } - } - } - } - - internal static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal new static SocketUserCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) { var entity = new SocketUserCommandData(client, model, guildId); entity.Update(model); return entity; } - internal void Update(Model model) - { - this.Name = model.Name; - } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs index 36542f15a..05c051f12 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -28,7 +28,7 @@ namespace Discord.WebSocket if (this.Channel is SocketGuildChannel guildChannel) guildId = guildChannel.Guild.Id; - Data = SocketSlashCommandData.Create(client, dataModel, model.Id, guildId); + Data = SocketSlashCommandData.Create(client, dataModel, guildId); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs index 6ed77b997..4b6764bf7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs @@ -8,108 +8,24 @@ namespace Discord.WebSocket /// /// Represents the data tied with the interaction. /// - public class SocketSlashCommandData : SocketEntity, IApplicationCommandInteractionData + public class SocketSlashCommandData : SocketCommandBaseData { - /// - public string Name { get; private set; } - - /// - /// The 's received with this interaction. - /// - public IReadOnlyCollection Options { get; private set; } - - internal Dictionary guildMembers { get; private set; } - = new Dictionary(); - internal Dictionary users { get; private set; } - = new Dictionary(); - internal Dictionary channels { get; private set; } - = new Dictionary(); - internal Dictionary roles { get; private set; } - = new Dictionary(); - - private ulong? guildId; - internal SocketSlashCommandData(DiscordSocketClient client, Model model, ulong? guildId) - : base(client, model.Id) - { - this.guildId = guildId; - - if (model.Resolved.IsSpecified) - { - var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; - - var resolved = model.Resolved.Value; - - if (resolved.Users.IsSpecified) - { - foreach (var user in resolved.Users.Value) - { - var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); - - this.users.Add(ulong.Parse(user.Key), socketUser); - } - } + : base(client, model, guildId) { } - if (resolved.Channels.IsSpecified) - { - foreach (var channel in resolved.Channels.Value) - { - SocketChannel socketChannel = guild != null - ? guild.GetChannel(channel.Value.Id) - : Discord.GetChannel(channel.Value.Id); - - if (socketChannel == null) - { - var channelModel = guild != null - ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() - : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); - - socketChannel = guild != null - ? SocketGuildChannel.Create(guild, Discord.State, channelModel) - : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); - } - - Discord.State.AddChannel(socketChannel); - this.channels.Add(ulong.Parse(channel.Key), socketChannel); - } - } - - if (resolved.Members.IsSpecified) - { - foreach (var member in resolved.Members.Value) - { - member.Value.User = resolved.Users.Value[member.Key]; - var user = guild.AddOrUpdateUser(member.Value); - this.guildMembers.Add(ulong.Parse(member.Key), user); - } - } - - if (resolved.Roles.IsSpecified) - { - foreach (var role in resolved.Roles.Value) - { - var socketRole = guild.AddOrUpdateRole(role.Value); - this.roles.Add(ulong.Parse(role.Key), socketRole); - } - } - } - } - - internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong id, ulong? guildId) + internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong? guildId) { var entity = new SocketSlashCommandData(client, model, guildId); entity.Update(model); return entity; } - internal void Update(Model model) + internal override void Update(Model model) { - this.Name = model.Name; + base.Update(model); this.Options = model.Options.IsSpecified ? model.Options.Value.Select(x => new SocketSlashCommandDataOption(this, x)).ToImmutableArray() : null; } - - IReadOnlyCollection IApplicationCommandInteractionData.Options => Options; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs index f9c12257e..a79a9724c 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs @@ -44,34 +44,34 @@ namespace Discord.WebSocket { case ApplicationCommandOptionType.User: { - var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; if (guildUser != null) this.Value = guildUser; else - this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; + this.Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; } break; case ApplicationCommandOptionType.Channel: - this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value; + this.Value = data.ResolvableData.Channels.FirstOrDefault(x => x.Key == valueId).Value; break; case ApplicationCommandOptionType.Role: - this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; + this.Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; break; case ApplicationCommandOptionType.Mentionable: { - if(data.guildMembers.Any(x => x.Key == valueId) || data.users.Any(x => x.Key == valueId)) + if(data.ResolvableData.GuildMembers.Any(x => x.Key == valueId) || data.ResolvableData.Users.Any(x => x.Key == valueId)) { - var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; + var guildUser = data.ResolvableData.GuildMembers.FirstOrDefault(x => x.Key == valueId).Value; if (guildUser != null) this.Value = guildUser; else - this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; + this.Value = data.ResolvableData.Users.FirstOrDefault(x => x.Key == valueId).Value; } - else if(data.roles.Any(x => x.Key == valueId)) + else if(data.ResolvableData.Roles.Any(x => x.Key == valueId)) { - this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; + this.Value = data.ResolvableData.Roles.FirstOrDefault(x => x.Key == valueId).Value; } } break; @@ -125,6 +125,8 @@ namespace Discord.WebSocket public static explicit operator string(SocketSlashCommandDataOption option) => option.Value.ToString(); - IReadOnlyCollection IApplicationCommandInteractionDataOption.Options => this.Options; + // IApplicationCommandInteractionDataOption + IReadOnlyCollection IApplicationCommandInteractionDataOption.Options + => this.Options; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 6ba9ba05a..7c8e2422e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -10,7 +10,7 @@ using Model = Discord.API.Interaction; namespace Discord.WebSocket { /// - /// Base class for User, Message, and Slash command interactions + /// Base class for User, Message, and Slash command interactions /// public class SocketCommandBase : SocketInteraction { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs index dde981eb9..64aeb5d24 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseData.cs @@ -8,123 +8,28 @@ namespace Discord.WebSocket /// /// Represents the base data tied with the interaction. /// - public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData + public class SocketCommandBaseData : SocketEntity, IApplicationCommandInteractionData where TOption : IApplicationCommandInteractionDataOption { /// public string Name { get; private set; } + /// - /// The 's received with this interaction. + /// The received with this interaction. /// - public IReadOnlyCollection Options { get; private set; } - internal Dictionary guildMembers { get; private set; } - = new Dictionary(); - internal Dictionary users { get; private set; } - = new Dictionary(); - internal Dictionary channels { get; private set; } - = new Dictionary(); - internal Dictionary roles { get; private set; } - = new Dictionary(); - - private ulong? guildId; + public virtual IReadOnlyCollection Options { get; internal set; } - internal SocketMessage Message { get; private set; } + internal readonly SocketResolvableData ResolvableData; private ApplicationCommandType Type { get; set; } internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId) : base(client, model.Id) { - this.guildId = guildId; - - this.Type = (ApplicationCommandType)model.Type; + this.Type = model.Type; if (model.Resolved.IsSpecified) { - var guild = this.guildId.HasValue ? Discord.GetGuild(this.guildId.Value) : null; - - var resolved = model.Resolved.Value; - - if (resolved.Users.IsSpecified) - { - foreach (var user in resolved.Users.Value) - { - var socketUser = Discord.GetOrCreateUser(this.Discord.State, user.Value); - - this.users.Add(ulong.Parse(user.Key), socketUser); - } - } - - if (resolved.Channels.IsSpecified) - { - foreach (var channel in resolved.Channels.Value) - { - SocketChannel socketChannel = guild != null - ? guild.GetChannel(channel.Value.Id) - : Discord.GetChannel(channel.Value.Id); - - if (socketChannel == null) - { - var channelModel = guild != null - ? Discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() - : Discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); - - socketChannel = guild != null - ? SocketGuildChannel.Create(guild, Discord.State, channelModel) - : (SocketChannel)SocketChannel.CreatePrivate(Discord, Discord.State, channelModel); - } - - Discord.State.AddChannel(socketChannel); - this.channels.Add(ulong.Parse(channel.Key), socketChannel); - } - } - - if (resolved.Members.IsSpecified) - { - foreach (var member in resolved.Members.Value) - { - member.Value.User = resolved.Users.Value[member.Key]; - var user = guild.AddOrUpdateUser(member.Value); - this.guildMembers.Add(ulong.Parse(member.Key), user); - } - } - - if (resolved.Roles.IsSpecified) - { - foreach (var role in resolved.Roles.Value) - { - var socketRole = guild.AddOrUpdateRole(role.Value); - this.roles.Add(ulong.Parse(role.Key), socketRole); - } - } - - if (resolved.Messages.IsSpecified) - { - foreach (var msg in resolved.Messages.Value) - { - var channel = client.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; - - SocketUser author; - if (guild != null) - { - if (msg.Value.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, client.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); - else - author = guild.GetUser(msg.Value.Author.Value.Id); - } - else - author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); - - if (channel == null) - { - if (!msg.Value.GuildId.IsSpecified) // assume it is a DM - { - channel = client.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, client.State); - } - } - - this.Message = SocketMessage.Create(client, client.State, author, channel, msg.Value); - } - } + ResolvableData = new SocketResolvableData(client, guildId, model); } } @@ -135,15 +40,21 @@ namespace Discord.WebSocket return entity; } - internal void Update(Model model) + internal virtual void Update(Model model) { this.Name = model.Name; - - this.Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => new SocketCommandBaseDataOption(this, x)).ToImmutableArray() - : null; } - IReadOnlyCollection IApplicationCommandInteractionData.Options => Options; + IReadOnlyCollection IApplicationCommandInteractionData.Options + => (IReadOnlyCollection)Options; + } + + /// + /// Represents the base data tied with the interaction. + /// + public class SocketCommandBaseData : SocketCommandBaseData + { + internal SocketCommandBaseData(DiscordSocketClient client, Model model, ulong? guildId) + : base(client, model, guildId) { } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs deleted file mode 100644 index 6aa15978e..000000000 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBaseDataOption.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Model = Discord.API.ApplicationCommandInteractionDataOption; - -namespace Discord.WebSocket -{ - /// - /// Represents the base Websocket-based recieved by the gateway - /// - public class SocketCommandBaseDataOption : IApplicationCommandInteractionDataOption - { - /// - public string Name { get; private set; } - - /// - public object Value { get; private set; } - - /// - public ApplicationCommandOptionType Type { get; private set; } - - /// - /// The sub command options received for this sub command group. - /// - public IReadOnlyCollection Options { get; private set; } - - internal SocketCommandBaseDataOption() { } - internal SocketCommandBaseDataOption(SocketCommandBaseData data, Model model) - { - this.Name = model.Name; - this.Type = model.Type; - - if (model.Value.IsSpecified) - { - switch (Type) - { - case ApplicationCommandOptionType.User: - case ApplicationCommandOptionType.Role: - case ApplicationCommandOptionType.Channel: - case ApplicationCommandOptionType.Mentionable: - if (ulong.TryParse($"{model.Value.Value}", out var valueId)) - { - switch (this.Type) - { - case ApplicationCommandOptionType.User: - { - var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; - - if (guildUser != null) - this.Value = guildUser; - else - this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; - } - break; - case ApplicationCommandOptionType.Channel: - this.Value = data.channels.FirstOrDefault(x => x.Key == valueId).Value; - break; - case ApplicationCommandOptionType.Role: - this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; - break; - case ApplicationCommandOptionType.Mentionable: - { - if (data.guildMembers.Any(x => x.Key == valueId) || data.users.Any(x => x.Key == valueId)) - { - var guildUser = data.guildMembers.FirstOrDefault(x => x.Key == valueId).Value; - - if (guildUser != null) - this.Value = guildUser; - else - this.Value = data.users.FirstOrDefault(x => x.Key == valueId).Value; - } - else if (data.roles.Any(x => x.Key == valueId)) - { - this.Value = data.roles.FirstOrDefault(x => x.Key == valueId).Value; - } - } - break; - default: - this.Value = model.Value.Value; - break; - } - } - break; - case ApplicationCommandOptionType.String: - this.Value = model.Value.ToString(); - break; - case ApplicationCommandOptionType.Integer: - { - if (model.Value.Value is int val) - this.Value = val; - else if (int.TryParse(model.Value.Value.ToString(), out int res)) - this.Value = res; - } - break; - case ApplicationCommandOptionType.Boolean: - { - if (model.Value.Value is bool val) - this.Value = val; - else if (bool.TryParse(model.Value.Value.ToString(), out bool res)) - this.Value = res; - } - break; - case ApplicationCommandOptionType.Number: - { - if (model.Value.Value is int val) - this.Value = val; - else if (double.TryParse(model.Value.Value.ToString(), out double res)) - this.Value = res; - } - break; - } - - } - - this.Options = model.Options.IsSpecified - ? model.Options.Value.Select(x => new SocketCommandBaseDataOption(data, x)).ToImmutableArray() - : null; - } - - // Converters - public static explicit operator bool(SocketCommandBaseDataOption option) - => (bool)option.Value; - public static explicit operator int(SocketCommandBaseDataOption option) - => (int)option.Value; - public static explicit operator string(SocketCommandBaseDataOption option) - => option.Value.ToString(); - - IReadOnlyCollection IApplicationCommandInteractionDataOption.Options => this.Options; - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs new file mode 100644 index 000000000..17c96724c --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketResolvableData.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.WebSocket +{ + internal class SocketResolvableData where T : API.IResolvable + { + internal readonly Dictionary GuildMembers + = new Dictionary(); + internal readonly Dictionary Users + = new Dictionary(); + internal readonly Dictionary Channels + = new Dictionary(); + internal readonly Dictionary Roles + = new Dictionary(); + + internal readonly Dictionary Messages + = new Dictionary(); + + internal SocketResolvableData(DiscordSocketClient discord, ulong? guildId, T model) + { + var guild = guildId.HasValue ? discord.GetGuild(guildId.Value) : null; + + var resolved = model.Resolved.Value; + + if (resolved.Users.IsSpecified) + { + foreach (var user in resolved.Users.Value) + { + var socketUser = discord.GetOrCreateUser(discord.State, user.Value); + + this.Users.Add(ulong.Parse(user.Key), socketUser); + } + } + + if (resolved.Channels.IsSpecified) + { + foreach (var channel in resolved.Channels.Value) + { + SocketChannel socketChannel = guild != null + ? guild.GetChannel(channel.Value.Id) + : discord.GetChannel(channel.Value.Id); + + if (socketChannel == null) + { + var channelModel = guild != null + ? discord.Rest.ApiClient.GetChannelAsync(guild.Id, channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult() + : discord.Rest.ApiClient.GetChannelAsync(channel.Value.Id).ConfigureAwait(false).GetAwaiter().GetResult(); + + socketChannel = guild != null + ? SocketGuildChannel.Create(guild, discord.State, channelModel) + : (SocketChannel)SocketChannel.CreatePrivate(discord, discord.State, channelModel); + } + + discord.State.AddChannel(socketChannel); + this.Channels.Add(ulong.Parse(channel.Key), socketChannel); + } + } + + if (resolved.Members.IsSpecified) + { + foreach (var member in resolved.Members.Value) + { + member.Value.User = resolved.Users.Value[member.Key]; + var user = guild.AddOrUpdateUser(member.Value); + this.GuildMembers.Add(ulong.Parse(member.Key), user); + } + } + + if (resolved.Roles.IsSpecified) + { + foreach (var role in resolved.Roles.Value) + { + var socketRole = guild.AddOrUpdateRole(role.Value); + this.Roles.Add(ulong.Parse(role.Key), socketRole); + } + } + + if (resolved.Messages.IsSpecified) + { + foreach (var msg in resolved.Messages.Value) + { + var channel = discord.GetChannel(msg.Value.ChannelId) as ISocketMessageChannel; + + SocketUser author; + if (guild != null) + { + if (msg.Value.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, discord.State, msg.Value.Author.Value, msg.Value.WebhookId.Value); + else + author = guild.GetUser(msg.Value.Author.Value.Id); + } + else + author = (channel as SocketChannel).GetUser(msg.Value.Author.Value.Id); + + if (channel == null) + { + if (!msg.Value.GuildId.IsSpecified) // assume it is a DM + { + channel = discord.CreateDMChannel(msg.Value.ChannelId, msg.Value.Author.Value, discord.State); + } + } + + var message = SocketMessage.Create(discord, discord.State, author, channel, msg.Value); + this.Messages.Add(message.Id, message); + } + } + } + } +} From ae95d284faff7bcb361d6df73119e9e53d179733 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 21 Aug 2021 02:40:10 -0300 Subject: [PATCH 11/14] Simply internal usage of context menus --- src/Discord.Net.Core/Discord.Net.Core.xml | 197 +++--- .../ApplicationCommandProperties.cs | 26 +- .../Interactions/ApplicationCommandTypes.cs | 10 +- .../MessageCommandBuilder.cs | 11 +- .../Context Menus/MessageCommandProperties.cs | 16 + .../{ => Context Menus}/UserCommandBuilder.cs | 9 +- .../Context Menus/UserCommandProperties.cs | 16 + .../ContextMenuCommandCreationProperties.cs | 24 - .../Interactions/IApplicationCommand.cs | 12 +- .../Interactions/SlashCommandBuilder.cs | 9 +- ...roperties.cs => SlashCommandProperties.cs} | 17 +- .../UserCommandCreationProperties.cs | 30 - .../API/Common/ApplicationCommand.cs | 8 + src/Discord.Net.Rest/API/Net/IResolvable.cs | 1 - .../Rest/ModifyApplicationCommandParams.cs | 3 - src/Discord.Net.Rest/Discord.Net.Rest.xml | 121 +--- src/Discord.Net.Rest/DiscordRestClient.cs | 28 +- .../Interactions/InteractionHelper.cs | 568 ++++-------------- .../Interactions/RestApplicationCommand.cs | 39 +- .../RestApplicationCommandOption.cs | 7 +- .../RestApplicationCommandType.cs | 28 - .../Interactions/RestGlobalCommand.cs | 11 +- .../Interactions/RestGlobalMessageCommand.cs | 41 -- .../Interactions/RestGlobalUserCommand.cs | 41 -- .../Entities/Interactions/RestGuildCommand.cs | 10 +- .../Interactions/RestGuildMessageCommand.cs | 61 -- .../Interactions/RestGuildUserCommand.cs | 61 -- .../ApplicationCommandCreatedUpdatedEvent.cs | 22 +- .../Discord.Net.WebSocket.xml | 25 +- .../DiscordSocketClient.cs | 33 +- .../SocketApplicationCommand.cs | 70 ++- 31 files changed, 429 insertions(+), 1126 deletions(-) rename src/Discord.Net.Core/Entities/Interactions/{ => Context Menus}/MessageCommandBuilder.cs (81%) create mode 100644 src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs rename src/Discord.Net.Core/Entities/Interactions/{ => Context Menus}/UserCommandBuilder.cs (81%) create mode 100644 src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs delete mode 100644 src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs rename src/Discord.Net.Core/Entities/Interactions/{SlashCommandCreationProperties.cs => SlashCommandProperties.cs} (66%) delete mode 100644 src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs delete mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs delete mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs delete mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs delete mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs delete mode 100644 src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index 8be13e6bd..187461b93 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -4461,7 +4461,7 @@ - Provides properties that are used to modify a with the specified changes. + Represents the base class to create/modify application commands. @@ -4469,64 +4469,101 @@ Gets or sets the name of this command. - + - Gets or sets the discription of this command. + ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message - + - Gets or sets the type for this command. + ApplicationCommandType.Slash is Slash command type - + - Gets or sets the options for this command. + ApplicationCommandType.User is Context Menu User command type - + - Whether the command is enabled by default when the app is added to a guild. Default is + ApplicationCommandType.Message is Context Menu Message command type - + - ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + A class used to build Message commands. - + + + Returns the maximun length a commands name allowed by Discord + + + - ApplicationCommandType.Slash is Slash command type + The name of this Message command. - + - ApplicationCommandType.User is Context Menu User command type + Build the current builder into a class. + + A that can be used to create message commands. + - + - ApplicationCommandType.Message is Context Menu Message command type + Sets the field name. + The value to set the field name to. + + The current builder. + - + - A class used to create Message commands. + A class used to create message commands. - + - The name of this command. + A class used to build user commands. + + + + + Returns the maximun length a commands name allowed by Discord + + + + + The name of this User command. + + + + + Build the current builder into a class. + A that can be used to create user commands. - + - Gets or sets the type for this command. + Sets the field name. + + The value to set the field name to. + + The current builder. + + + + + A class used to create User commands. - The base command model that belongs to an application. see + The base command model that belongs to an application. @@ -4559,6 +4596,16 @@ If the option is a subcommand or subcommand group type, this nested options will be the parameters. + + + Modifies the current application command. + + The new properties to use when modifying the command. + The options to be used when sending the request. + + A task that represents the asynchronous modification operation. + + Represents data of an Interaction Command, see . @@ -5571,36 +5618,6 @@ Will render this option as selected by default. - - - A class used to build Message commands. - - - - - Returns the maximun length a commands name allowed by Discord - - - - - The name of this Message command. - - - - - Build the current builder into a class. - - A that can be used to create message commands over rest. - - - - Sets the field name. - - The value to set the field name to. - - The current builder. - - A class used to build slash commands. @@ -5643,9 +5660,9 @@ - Build the current builder into a class. + Build the current builder into a class. - A that can be used to create slash commands over rest. + A that can be used to create slash commands over rest. @@ -5833,86 +5850,26 @@ The type to set. The current builder. - + A class used to create slash commands. - - - The name of this command. - - - + The discription of this command. - - - Gets or sets the type for this command. - - - + Gets or sets the options for this command. - + Whether the command is enabled by default when the app is added to a guild. Default is - - - A class used to build user commands. - - - - - Returns the maximun length a commands name allowed by Discord - - - - - The name of this User command. - - - - - Build the current builder into a class. - - A that can be used to create user commands over rest. - - - - Sets the field name. - - The value to set the field name to. - - The current builder. - - - - - A class used to create User commands. - - - - - The name of this command. - - - - - The discription of this command. - - - - - Gets or sets the type for this command. - - Represents a generic invite object. diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index 70b430bfc..2ccb0148b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -7,33 +7,17 @@ using System.Threading.Tasks; namespace Discord { /// - /// Provides properties that are used to modify a with the specified changes. + /// Represents the base class to create/modify application commands. /// - public class ApplicationCommandProperties + public abstract class ApplicationCommandProperties { + internal abstract ApplicationCommandType Type { get; } + /// /// Gets or sets the name of this command. /// public Optional Name { get; set; } - /// - /// Gets or sets the discription of this command. - /// - public Optional Description { get; set; } - - /// - /// Gets or sets the type for this command. - /// - public Optional Type { get; set; } - - /// - /// Gets or sets the options for this command. - /// - public Optional> Options { get; set; } - - /// - /// Whether the command is enabled by default when the app is added to a guild. Default is - /// - public Optional DefaultPermission { get; set; } + internal ApplicationCommandProperties() { } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs index de9c1a263..3cfa97a5a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs @@ -7,20 +7,22 @@ using System.Threading.Tasks; namespace Discord { /// - /// ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message + /// ApplicationCommandType is enum of current valid Application Command Types: Slash, User, Message /// public enum ApplicationCommandType : byte { /// - /// ApplicationCommandType.Slash is Slash command type + /// ApplicationCommandType.Slash is Slash command type /// Slash = 1, + /// - /// ApplicationCommandType.User is Context Menu User command type + /// ApplicationCommandType.User is Context Menu User command type /// User = 2, + /// - /// ApplicationCommandType.Message is Context Menu Message command type + /// ApplicationCommandType.Message is Context Menu Message command type /// Message = 3 } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs similarity index 81% rename from src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs rename to src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs index b658a181b..9907dd2cf 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs @@ -44,15 +44,16 @@ namespace Discord private string _name { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create message commands over rest. - public ContextMenuCommandCreationProperties Build() + /// + /// A that can be used to create message commands. + /// + public MessageCommandProperties Build() { - ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() + MessageCommandProperties props = new MessageCommandProperties() { Name = this.Name, - Type=ApplicationCommandType.Message }; return props; diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs new file mode 100644 index 000000000..3af9b47f3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to create message commands. + /// + public class MessageCommandProperties : ApplicationCommandProperties + { + internal override ApplicationCommandType Type => ApplicationCommandType.Message; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs similarity index 81% rename from src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs rename to src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs index f10b6b123..d415a99a6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs @@ -44,15 +44,14 @@ namespace Discord private string _name { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create user commands over rest. - public ContextMenuCommandCreationProperties Build() + /// A that can be used to create user commands. + public UserCommandProperties Build() { - ContextMenuCommandCreationProperties props = new ContextMenuCommandCreationProperties() + UserCommandProperties props = new UserCommandProperties() { Name = this.Name, - Type=ApplicationCommandType.User }; return props; diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs new file mode 100644 index 000000000..091166a17 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to create User commands. + /// + public class UserCommandProperties : ApplicationCommandProperties + { + internal override ApplicationCommandType Type => ApplicationCommandType.User; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs deleted file mode 100644 index 4ddc0a7b1..000000000 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenuCommandCreationProperties.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord -{ - /// - /// A class used to create Message commands. - /// - public class ContextMenuCommandCreationProperties - { - /// - /// The name of this command. - /// - public string Name { get; set; } - - /// - /// Gets or sets the type for this command. - /// - public ApplicationCommandType Type { get; set; } - } -} diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index e15843d98..e0cf605d2 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// The base command model that belongs to an application. see + /// The base command model that belongs to an application. /// public interface IApplicationCommand : ISnowflakeEntity, IDeletable { @@ -40,5 +40,15 @@ namespace Discord /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. /// IReadOnlyCollection Options { get; } + + /// + /// Modifies the current application command. + /// + /// The new properties to use when modifying the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs index 933f511e1..8a1bd8314 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommandBuilder.cs @@ -93,17 +93,16 @@ namespace Discord private List _options { get; set; } /// - /// Build the current builder into a class. + /// Build the current builder into a class. /// - /// A that can be used to create slash commands over rest. - public SlashCommandCreationProperties Build() + /// A that can be used to create slash commands over rest. + public SlashCommandProperties Build() { - SlashCommandCreationProperties props = new SlashCommandCreationProperties() + SlashCommandProperties props = new SlashCommandProperties() { Name = this.Name, Description = this.Description, DefaultPermission = this.DefaultPermission, - Type = ApplicationCommandType.Slash }; if (this.Options != null && this.Options.Any()) diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommandProperties.cs similarity index 66% rename from src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs rename to src/Discord.Net.Core/Entities/Interactions/SlashCommandProperties.cs index 3021d7a2c..72c7c322b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommandProperties.cs @@ -9,22 +9,13 @@ namespace Discord /// /// A class used to create slash commands. /// - public class SlashCommandCreationProperties + public class SlashCommandProperties : ApplicationCommandProperties { - /// - /// The name of this command. - /// - public string Name { get; set; } - + internal override ApplicationCommandType Type => ApplicationCommandType.Slash; /// /// The discription of this command. /// - public string Description { get; set; } - - /// - /// Gets or sets the type for this command. - /// - public ApplicationCommandType Type { get; set; } + public Optional Description { get; set; } /// /// Gets or sets the options for this command. @@ -35,5 +26,7 @@ namespace Discord /// Whether the command is enabled by default when the app is added to a guild. Default is /// public Optional DefaultPermission { get; set; } + + internal SlashCommandProperties() { } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs deleted file mode 100644 index 323b2ce1d..000000000 --- a/src/Discord.Net.Core/Entities/Interactions/UserCommandCreationProperties.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord -{ - /// - /// A class used to create User commands. - /// - public class UserCommandCreationProperties - { - /// - /// The name of this command. - /// - public string Name { get; set; } - - /// - /// The discription of this command. - /// - public string Description { get; set; } - - - /// - /// Gets or sets the type for this command. - /// - public ApplicationCommandType Type { get; set; } - } -} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs index 39c40a1ee..9de272706 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs @@ -11,14 +11,22 @@ namespace Discord.API { [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("type")] + public ApplicationCommandType Type { get; set; } = ApplicationCommandType.Slash; // defaults to 1 which is slash. + [JsonProperty("application_id")] public ulong ApplicationId { get; set; } + [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("description")] public string Description { get; set; } + [JsonProperty("options")] public Optional Options { get; set; } + [JsonProperty("default_permission")] public Optional DefaultPermissions { get; set; } } diff --git a/src/Discord.Net.Rest/API/Net/IResolvable.cs b/src/Discord.Net.Rest/API/Net/IResolvable.cs index fe31cd75e..7485f5de8 100644 --- a/src/Discord.Net.Rest/API/Net/IResolvable.cs +++ b/src/Discord.Net.Rest/API/Net/IResolvable.cs @@ -9,6 +9,5 @@ namespace Discord.API internal interface IResolvable { Optional Resolved { get; } - } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs index 29a6ff796..2ed9466c0 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -15,9 +15,6 @@ namespace Discord.API.Rest [JsonProperty("description")] public Optional Description { get; set; } - [JsonProperty("type")] - public Optional Type { get; set; } - [JsonProperty("options")] public Optional Options { get; set; } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index 0fc188b2e..0378df092 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -3762,17 +3762,15 @@ The options of this command. - - - The type of this rest application command. - - + + + Represents a Rest-based implementation of . @@ -3814,24 +3812,9 @@ A collection of 's for this command. - - - Represents a type of Rest-based command. - - - - - Specifies that this command is a Global command. - - - - - Specifies that this command is a Guild specific command. - - - Represents a global Slash command. + Represents a Rest-based global application command. @@ -3847,35 +3830,9 @@ The modified command. - - - - - - Modifies this . - - The delegate containing the properties to modify the command with. - The options to be used when sending the request. - - The modified command. - - - - - - - - Modifies this . - - The delegate containing the properties to modify the command with. - The options to be used when sending the request. - - The modified command. - - - Represents a Rest-based guild command. + Represents a Rest-based guild application command. @@ -3928,74 +3885,6 @@ . - - - Represents a Rest-based guild command. - - - - - The guild Id where this command originates. - - - - - - - - Modifies this . - - The delegate containing the properties to modify the command with. - The options to be used when sending the request. - - The modified command - - - - - Gets the guild that this slash command resides in. - - if you want the approximate member and presence counts for the guild, otherwise . - The options to be used when sending the request. - - A task that represents the asynchronous get operation. The task result contains a - . - - - - - Represents a Rest-based guild command. - - - - - The guild Id where this command originates. - - - - - - - - Modifies this . - - The delegate containing the properties to modify the command with. - The options to be used when sending the request. - - The modified command - - - - - Gets the guild that this slash command resides in. - - if you want the approximate member and presence counts for the guild, otherwise . - The options to be used when sending the request. - - A task that represents the asynchronous get operation. The task result contains a - . - - diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index c204a2d1c..32dbaf40e 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -108,37 +108,21 @@ namespace Discord.Rest public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); - public Task CreateGlobalCommand(SlashCommandCreationProperties properties, RequestOptions options = null) + public Task CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, properties, options); - public Task CreateGlobalCommand(Action func, RequestOptions options = null) + public Task CreateGlobalCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, func, options); - public Task CreateGlobalUserCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) - => InteractionHelper.CreateGlobalUserCommand(this, properties, options); - public Task CreateGlobalUserCommand(Action func, RequestOptions options = null) - => InteractionHelper.CreateGlobalUserCommand(this, func, options); - public Task CreateGlobalMessageCommand(ContextMenuCommandCreationProperties properties, RequestOptions options = null) - => InteractionHelper.CreateGlobalMessageCommand(this, properties, options); - public Task CreateGlobalMessageCommand(Action func, RequestOptions options = null) - => InteractionHelper.CreateGlobalMessageCommand(this, func, options); - public Task CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + public Task CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); - public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, func, options); - public Task CreateGuildUserCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildUserCommand(this, guildId, properties, options); - public Task CreateGuildUserCommand(Action func, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildUserCommand(this, guildId, func, options); - public Task CreateGuildMessageCommand(ContextMenuCommandCreationProperties properties, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildMessageCommand(this, guildId, properties, options); - public Task CreateGuildMessageCommand(Action func, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildMessageCommand(this, guildId, func, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); public Task> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) => ClientHelper.GetGuildApplicationCommands(this, guildId, options); - public Task> BulkOverwriteGlobalCommands(SlashCommandCreationProperties[] commandProperties, RequestOptions options = null) + public Task> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null) => InteractionHelper.BulkOverwriteGlobalCommands(this, commandProperties, options); - public Task> BulkOverwriteGuildCommands(SlashCommandCreationProperties[] commandProperties, ulong guildId, RequestOptions options = null) + public Task> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null) => InteractionHelper.BulkOverwriteGuildCommands(this, guildId, commandProperties, options); public Task> BatchEditGuildCommandPermissions(ulong guildId, IDictionary permissions, RequestOptions options = null) => InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options); diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index b5c354047..fbbce4f4b 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -44,41 +44,45 @@ namespace Discord.Rest } // Global commands - public static async Task CreateGlobalCommand(BaseDiscordClient client, - Action func, RequestOptions options = null) + public static async Task CreateGlobalCommand(BaseDiscordClient client, + Action func, RequestOptions options) where TArg : ApplicationCommandProperties { - var args = new SlashCommandCreationProperties(); - func(args); - return await CreateGlobalCommand(client, args, options).ConfigureAwait(false); + var args = Activator.CreateInstance(typeof(TArg)); + func((TArg)args); + return await CreateGlobalCommand(client, (TArg)args, options); } public static async Task CreateGlobalCommand(BaseDiscordClient client, - SlashCommandCreationProperties arg, RequestOptions options = null) + ApplicationCommandProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); - - if (arg.Options.IsSpecified) - Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options)); var model = new CreateApplicationCommandParams() { - Name = arg.Name, - Description = arg.Description, - Type= arg.Type, - Options = arg.Options.IsSpecified - ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = arg.DefaultPermission.IsSpecified - ? arg.DefaultPermission.Value - : Optional.Unspecified + Name = arg.Name.Value, + Type = arg.Type, }; + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; + } + var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); return RestGlobalCommand.Create(client, cmd); } public static async Task> BulkOverwriteGlobalCommands(BaseDiscordClient client, - SlashCommandCreationProperties[] args, RequestOptions options = null) + ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -87,24 +91,28 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); - - if (arg.Options.IsSpecified) - Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options)); var model = new CreateApplicationCommandParams() { - Name = arg.Name, - Description = arg.Description, + Name = arg.Name.Value, Type = arg.Type, - Options = arg.Options.IsSpecified - ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = arg.DefaultPermission.IsSpecified - ? arg.DefaultPermission.Value - : Optional.Unspecified }; + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); + + model.Description = slashProps.Description.Value; + + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; + } + models.Add(model); } @@ -114,7 +122,7 @@ namespace Discord.Rest } public static async Task> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId, - SlashCommandCreationProperties[] args, RequestOptions options = null) + ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -123,236 +131,88 @@ namespace Discord.Rest foreach (var arg in args) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.NotNullOrEmpty(arg.Description, nameof(arg.Description)); - - if (arg.Options.IsSpecified) - Preconditions.AtMost(arg.Options.Value.Count, 25, nameof(arg.Options)); var model = new CreateApplicationCommandParams() { - Name = arg.Name, - Description = arg.Description, + Name = arg.Name.Value, Type = arg.Type, - Options = arg.Options.IsSpecified - ? arg.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = arg.DefaultPermission.IsSpecified - ? arg.DefaultPermission.Value - : Optional.Unspecified }; - models.Add(model); - } - - var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options); - - return apiModels.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray(); - } + if (arg is SlashCommandProperties slashProps) + { + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); - public static async Task ModifyGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); + model.Description = slashProps.Description.Value; - if (args.Name.IsSpecified) - { - Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); - } - if (args.Description.IsSpecified) - { - Preconditions.AtMost(args.Description.Value.Length, 100, nameof(args.Description)); - Preconditions.AtLeast(args.Description.Value.Length, 1, nameof(args.Description)); - } + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; + } - if (args.Options.IsSpecified) - { - if (args.Options.Value.Count > 10) - throw new ArgumentException("Option count must be 10 or less"); + models.Add(model); } - var model = new Discord.API.Rest.ModifyApplicationCommandParams() - { - Name = args.Name, - Description = args.Description, - Type = args.Type, - Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = args.DefaultPermission.IsSpecified - ? args.DefaultPermission.Value - : Optional.Unspecified - }; - - var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; - } - - - public static async Task DeleteGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, RequestOptions options = null) - { - Preconditions.NotNull(command, nameof(command)); - Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); - - await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); - } - - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, Action func, RequestOptions options = null) - { - var args = new ContextMenuCommandCreationProperties(); - func(args); - return await CreateGlobalUserCommand(client, args, options).ConfigureAwait(false); - } - - public static async Task CreateGlobalUserCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) - { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; - - var cmd = await client.ApiClient.CreateGlobalApplicationUserCommandAsync(model, options).ConfigureAwait(false); - return RestGlobalUserCommand.Create(client, cmd); - } + var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options); - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, Action func, RequestOptions options = null) - { - var args = new ContextMenuCommandCreationProperties(); - func(args); - return await CreateGlobalMessageCommand(client, args, options).ConfigureAwait(false); + return apiModels.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray(); } - public static async Task CreateGlobalMessageCommand(BaseDiscordClient client, ContextMenuCommandCreationProperties arg, RequestOptions options = null) + public static Task ModifyGlobalCommand(BaseDiscordClient client, IApplicationCommand command, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; - - var cmd = await client.ApiClient.CreateGlobalApplicationMessageCommandAsync(model, options).ConfigureAwait(false); - return RestGlobalMessageCommand.Create(client, cmd); + var arg = (TArg)Activator.CreateInstance(typeof(TArg)); + func(arg); + return ModifyGlobalCommand(client, command, arg, options); } - public static async Task> BulkOverwriteGlobalUserCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) + public static async Task ModifyGlobalCommand(BaseDiscordClient client, IApplicationCommand command, + ApplicationCommandProperties args, RequestOptions options = null) { - Preconditions.NotNull(args, nameof(args)); - - List models = new List(); - - foreach (var arg in args) - { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Type, ApplicationCommandType.User); - - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; - - models.Add(model); - } - - var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationUserCommands(models.ToArray(), options); - - return apiModels.Select(x => RestGlobalUserCommand.Create(client, x)).ToArray(); - } - public static async Task ModifyGlobalUserCommand(BaseDiscordClient client, RestGlobalUserCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); - if (args.Name.IsSpecified) { Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); } - if (args.Description.IsSpecified) - { - Preconditions.Equals(args.Description.Value, ""); - } var model = new Discord.API.Rest.ModifyApplicationCommandParams() { Name = args.Name, - Description = args.Description }; - var msg = await client.ApiClient.ModifyGlobalApplicationUserCommandAsync(model, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; - } - - public static async Task DeleteGlobalUserCommand(BaseDiscordClient client, RestGlobalUserCommand command, RequestOptions options = null) - { - Preconditions.NotNull(command, nameof(command)); - Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); - - await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false); - } - - public static async Task> BulkOverwriteGlobalMessageCommands(BaseDiscordClient client, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) - { - Preconditions.NotNull(args, nameof(args)); - - List models = new List(); - - foreach (var arg in args) + if(args is SlashCommandProperties slashProps) { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Type, ApplicationCommandType.Message); - - var model = new CreateApplicationCommandParams() + if (slashProps.Description.IsSpecified) { - Name = arg.Name, - Type = arg.Type - }; + Preconditions.AtMost(slashProps.Description.Value.Length, 100, nameof(slashProps.Description)); + Preconditions.AtLeast(slashProps.Description.Value.Length, 1, nameof(slashProps.Description)); + } - models.Add(model); - } + if (slashProps.Options.IsSpecified) + { + if (slashProps.Options.Value.Count > 10) + throw new ArgumentException("Option count must be 10 or less"); + } - var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationMessageCommands(models.ToArray(), options); + model.Description = slashProps.Description; - return apiModels.Select(x => RestGlobalMessageCommand.Create(client, x)).ToArray(); - } - public static async Task ModifyGlobalMessageCommand(BaseDiscordClient client, RestGlobalMessageCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; - if (args.Name.IsSpecified) - { - Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); - } - if (args.Description.IsSpecified) - { - Preconditions.Equals(args.Description.Value, ""); + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; } - var model = new Discord.API.Rest.ModifyApplicationCommandParams() - { - Name = args.Name, - Description = args.Description - }; - - var msg = await client.ApiClient.ModifyGlobalApplicationMessageCommandAsync(model, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; + return await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); } - public static async Task DeleteGlobalMessageCommand(BaseDiscordClient client, RestGlobalMessageCommand command, RequestOptions options = null) + + public static async Task DeleteGlobalCommand(BaseDiscordClient client, IApplicationCommand command, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); @@ -361,197 +221,77 @@ namespace Discord.Rest } // Guild Commands - public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, - Action func, RequestOptions options = null) + public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + Action func, RequestOptions options) where TArg : ApplicationCommandProperties { - var args = new SlashCommandCreationProperties(); - func(args); - - return await CreateGuildCommand(client, guildId, args, options).ConfigureAwait(false); + var args = Activator.CreateInstance(typeof(TArg)); + func((TArg)args); + return await CreateGuildCommand(client, guildId, (TArg)args, options); } + public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, - SlashCommandCreationProperties args, RequestOptions options = null) + ApplicationCommandProperties arg, RequestOptions options = null) { - Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); - Preconditions.NotNullOrEmpty(args.Description, nameof(args.Description)); - - Preconditions.AtMost(args.Name.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Length, 3, nameof(args.Name)); - Preconditions.AtMost(args.Description.Length, 100, nameof(args.Description)); - Preconditions.AtLeast(args.Description.Length, 1, nameof(args.Description)); - - if (args.Options.IsSpecified) - { - if (args.Options.Value.Count > 10) - throw new ArgumentException("Option count must be 10 or less"); - - foreach (var item in args.Options.Value) - { - Preconditions.NotNullOrEmpty(item.Name, nameof(item.Name)); - Preconditions.NotNullOrEmpty(item.Description, nameof(item.Description)); - } - } - var model = new CreateApplicationCommandParams() { - Name = args.Name, - Description = args.Description, - Type = args.Type, - Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = args.DefaultPermission.IsSpecified - ? args.DefaultPermission.Value - : Optional.Unspecified + Name = arg.Name.Value, + Type = arg.Type, }; - var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); - return RestGuildCommand.Create(client, cmd, guildId); - } - public static async Task ModifyGuildCommand(BaseDiscordClient client, RestGuildCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); - - if (args.Name.IsSpecified) - { - Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); - } - if (args.Description.IsSpecified) + if (arg is SlashCommandProperties slashProps) { - Preconditions.AtMost(args.Description.Value.Length, 100, nameof(args.Description)); - Preconditions.AtLeast(args.Description.Value.Length, 1, nameof(args.Description)); - } + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); - if (args.Options.IsSpecified) - { - if (args.Options.Value.Count > 10) - throw new ArgumentException("Option count must be 10 or less"); - } + model.Description = slashProps.Description.Value; - var model = new Discord.API.Rest.ModifyApplicationCommandParams() - { - Name = args.Name, - Description = args.Description, - Type = args.Type, - Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified, - DefaultPermission = args.DefaultPermission.IsSpecified - ? args.DefaultPermission.Value - : Optional.Unspecified - }; - - var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; - } - - public static async Task DeleteGuildCommand(BaseDiscordClient client, ulong guildId, IApplicationCommand command, RequestOptions options = null) - { - Preconditions.NotNull(command, nameof(command)); - Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); - - await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); - } - - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) - { - var args = new ContextMenuCommandCreationProperties(); - func(args); - return await CreateGuildUserCommand(client, guildId, args, options).ConfigureAwait(false); - } + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; - public static async Task CreateGuildUserCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) - { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; + } - var cmd = await client.ApiClient.CreateGuildApplicationUserCommandAsync(model, guildId, options).ConfigureAwait(false); - return RestGuildUserCommand.Create(client, cmd, guildId); + var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); + return RestGuildCommand.Create(client, cmd, guildId); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options = null) + public static Task ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { - var args = new ContextMenuCommandCreationProperties(); - func(args); - return await CreateGuildMessageCommand(client, guildId, args, options).ConfigureAwait(false); + var arg = (TArg)Activator.CreateInstance(typeof(TArg)); + func(arg); + return ModifyGuildCommand(client, command, guildId, arg, options); } - public static async Task CreateGuildMessageCommand(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties arg, RequestOptions options = null) + public static async Task ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId, + ApplicationCommandProperties arg, RequestOptions options = null) { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - - var model = new CreateApplicationCommandParams() + var model = new ModifyApplicationCommandParams() { - Name = arg.Name, - Type = arg.Type + Name = arg.Name.Value, }; - var cmd = await client.ApiClient.CreateGuildApplicationMessageCommandAsync(model, guildId, options).ConfigureAwait(false); - return RestGuildMessageCommand.Create(client, cmd, guildId); - } - - public static async Task> BulkOverwriteGuildUserCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) - { - Preconditions.NotNull(args, nameof(args)); - - List models = new List(); - - foreach (var arg in args) + if (arg is SlashCommandProperties slashProps) { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Type, ApplicationCommandType.User); + Preconditions.NotNullOrEmpty(slashProps.Description, nameof(slashProps.Description)); - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; + model.Description = slashProps.Description.Value; - models.Add(model); - } - - var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationUserCommands(guildId, models.ToArray(), options); - - return apiModels.Select(x => RestGuildUserCommand.Create(client, x, guildId)).ToArray(); - } - public static async Task ModifyGuildUserCommand(BaseDiscordClient client, RestGuildUserCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); + model.Options = slashProps.Options.IsSpecified + ? slashProps.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; - if (args.Name.IsSpecified) - { - Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); - } - if (args.Description.IsSpecified) - { - Preconditions.Equals(args.Description.Value, ""); + model.DefaultPermission = slashProps.DefaultPermission.IsSpecified + ? slashProps.DefaultPermission.Value + : Optional.Unspecified; } - var model = new Discord.API.Rest.ModifyApplicationCommandParams() - { - Name = args.Name, - Description = args.Description, - Type=args.Type - }; - - var msg = await client.ApiClient.ModifyGuildApplicationUserCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; + return await client.ApiClient.ModifyGuildApplicationCommandAsync(model, guildId, command.Id, options).ConfigureAwait(false); } - public static async Task DeleteGuildUserCommand(BaseDiscordClient client, ulong guildId, RestGuildUserCommand command, RequestOptions options = null) + public static async Task DeleteGuildCommand(BaseDiscordClient client, ulong guildId, IApplicationCommand command, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); @@ -559,67 +299,19 @@ namespace Discord.Rest await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGuildMessageCommands(BaseDiscordClient client, ulong guildId, ContextMenuCommandCreationProperties[] args, RequestOptions options = null) + public static Task DeleteUnknownApplicationCommand(BaseDiscordClient client, ulong? guildId, IApplicationCommand command, RequestOptions options = null) { - Preconditions.NotNull(args, nameof(args)); - - List models = new List(); - - foreach (var arg in args) + if (guildId.HasValue) { - Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); - Preconditions.Equals(arg.Type, ApplicationCommandType.Message); - - var model = new CreateApplicationCommandParams() - { - Name = arg.Name, - Type = arg.Type - }; - - models.Add(model); - } - - var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationMessageCommands(guildId, models.ToArray(), options); - - return apiModels.Select(x => RestGuildMessageCommand.Create(client, x, guildId)).ToArray(); - } - public static async Task ModifyGuildMessageCommand(BaseDiscordClient client, RestGuildMessageCommand command, - Action func, RequestOptions options = null) - { - ApplicationCommandProperties args = new ApplicationCommandProperties(); - func(args); - - if (args.Name.IsSpecified) - { - Preconditions.AtMost(args.Name.Value.Length, 32, nameof(args.Name)); - Preconditions.AtLeast(args.Name.Value.Length, 3, nameof(args.Name)); + return DeleteGuildCommand(client, guildId.Value, command, options); } - if (args.Description.IsSpecified) + else { - Preconditions.Equals(args.Description.Value, ""); + return DeleteGlobalCommand(client, command, options); } - - var model = new Discord.API.Rest.ModifyApplicationCommandParams() - { - Name = args.Name, - Description = args.Description, - Type = args.Type - }; - - var msg = await client.ApiClient.ModifyGuildApplicationMessageCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); - command.Update(msg); - return command; } - public static async Task DeleteGuildMessageCommand(BaseDiscordClient client, ulong guildId, RestGuildMessageCommand command, RequestOptions options = null) - { - Preconditions.NotNull(command, nameof(command)); - Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); - - await client.ApiClient.DeleteGuildApplicationCommandAsync(guildId, command.Id, options).ConfigureAwait(false); - } - - + // Responses public static async Task ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action func, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index 2744c7967..dcba1ee87 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -33,11 +33,6 @@ namespace Discord.Rest /// public IReadOnlyCollection Options { get; private set; } - /// - /// The type of this rest application command. - /// - public RestApplicationCommandType CommandType { get; internal set; } - /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(this.Id); @@ -48,31 +43,15 @@ namespace Discord.Rest } - internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, RestApplicationCommandType type, ulong guildId = 0) + internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, ulong? guildId) { - switch (type) + if (guildId.HasValue) + { + return RestGuildCommand.Create(client, model, guildId.Value); + } + else { - case RestApplicationCommandType.GlobalCommand: - return RestGlobalCommand.Create(client, model); - break; - case RestApplicationCommandType.GlobalUserCommand: - return RestGlobalUserCommand.Create(client, model); - break; - case RestApplicationCommandType.GlobalMessageCommand: - return RestGlobalMessageCommand.Create(client, model); - break; - case RestApplicationCommandType.GuildCommand: - return RestGuildCommand.Create(client, model, guildId); - break; - case RestApplicationCommandType.GuildUserCommand: - return RestGuildUserCommand.Create(client, model, guildId); - break; - case RestApplicationCommandType.GuildMessageCommand: - return RestGuildMessageCommand.Create(client, model, guildId); - break; - default: - return null; - break; + return RestGlobalCommand.Create(client, model); } } @@ -92,7 +71,9 @@ namespace Discord.Rest /// public abstract Task DeleteAsync(RequestOptions options = null); - IReadOnlyCollection IApplicationCommand.Options => Options; + /// + public abstract Task ModifyAsync(Action func, RequestOptions options = null); + IReadOnlyCollection IApplicationCommand.Options => Options; } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs index a8e37873e..b135ae578 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs @@ -68,7 +68,10 @@ namespace Discord.Rest : null; } - IReadOnlyCollection IApplicationCommandOption.Options => Options; - IReadOnlyCollection IApplicationCommandOption.Choices => Choices; + //IApplicationCommandOption + IReadOnlyCollection IApplicationCommandOption.Options + => Options; + IReadOnlyCollection IApplicationCommandOption.Choices + => Choices; } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs deleted file mode 100644 index cf2fb1110..000000000 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord.Rest -{ - /// - /// Represents a type of Rest-based command. - /// - public enum RestApplicationCommandType - { - /// - /// Specifies that this command is a Global command. - /// - GlobalCommand, - GlobalUserCommand, - GlobalMessageCommand, - - /// - /// Specifies that this command is a Guild specific command. - /// - GuildCommand, - GuildUserCommand, - GuildMessageCommand - } -} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs index 230243c7a..7e3ca0a4e 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs @@ -8,14 +8,14 @@ using Model = Discord.API.ApplicationCommand; namespace Discord.Rest { /// - /// Represents a global Slash command. + /// Represents a Rest-based global application command. /// public class RestGlobalCommand : RestApplicationCommand { internal RestGlobalCommand(BaseDiscordClient client, ulong id) : base(client, id) { - this.CommandType = RestApplicationCommandType.GlobalCommand; + } internal static RestGlobalCommand Create(BaseDiscordClient client, Model model) @@ -37,7 +37,10 @@ namespace Discord.Rest /// /// The modified command. /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); + public override async Task ModifyAsync(Action func, RequestOptions options = null) + { + var cmd = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); + this.Update(cmd); + } } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs deleted file mode 100644 index 2c0ed222c..000000000 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalMessageCommand.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Model = Discord.API.ApplicationCommand; - -namespace Discord.Rest -{ - public class RestGlobalMessageCommand : RestApplicationCommand - { - internal RestGlobalMessageCommand(BaseDiscordClient client, ulong id) - : base(client, id) - { - this.CommandType = RestApplicationCommandType.GlobalMessageCommand; -} - - internal static RestGlobalMessageCommand Create(BaseDiscordClient client, Model model) - { - var entity = new RestGlobalMessageCommand(client, model.Id); - entity.Update(model); - return entity; - } - - /// - public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGlobalMessageCommand(Discord, this).ConfigureAwait(false); - - /// - /// Modifies this . - /// - /// The delegate containing the properties to modify the command with. - /// The options to be used when sending the request. - /// - /// The modified command. - /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGlobalMessageCommand(Discord, this, func, options).ConfigureAwait(false); - } -} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs deleted file mode 100644 index 981686379..000000000 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalUserCommand.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Model = Discord.API.ApplicationCommand; - -namespace Discord.Rest -{ - public class RestGlobalUserCommand : RestApplicationCommand - { - internal RestGlobalUserCommand(BaseDiscordClient client, ulong id) - : base(client, id) - { - this.CommandType = RestApplicationCommandType.GlobalUserCommand; - } - - internal static RestGlobalUserCommand Create(BaseDiscordClient client, Model model) - { - var entity = new RestGlobalUserCommand(client, model.Id); - entity.Update(model); - return entity; - } - - /// - public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGlobalUserCommand(Discord, this).ConfigureAwait(false); - - /// - /// Modifies this . - /// - /// The delegate containing the properties to modify the command with. - /// The options to be used when sending the request. - /// - /// The modified command. - /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGlobalUserCommand(Discord, this, func, options).ConfigureAwait(false); - } -} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs index 33ab78dbb..aa236d4b1 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs @@ -8,7 +8,7 @@ using Model = Discord.API.ApplicationCommand; namespace Discord.Rest { /// - /// Represents a Rest-based guild command. + /// Represents a Rest-based guild application command. /// public class RestGuildCommand : RestApplicationCommand { @@ -20,7 +20,6 @@ namespace Discord.Rest internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId) : base(client, id) { - this.CommandType = RestApplicationCommandType.GuildCommand; this.GuildId = guildId; } @@ -43,8 +42,11 @@ namespace Discord.Rest /// /// The modified command /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false); + public override async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await InteractionHelper.ModifyGuildCommand(Discord, this, GuildId, func, options).ConfigureAwait(false); + this.Update(model); + } /// /// Gets this commands permissions inside of the current guild. diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs deleted file mode 100644 index e057b2fe3..000000000 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGuildMessageCommand.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Model = Discord.API.ApplicationCommand; - -namespace Discord.Rest -{ - /// - /// Represents a Rest-based guild command. - /// - public class RestGuildMessageCommand : RestApplicationCommand - { - /// - /// The guild Id where this command originates. - /// - public ulong GuildId { get; private set; } - - internal RestGuildMessageCommand(BaseDiscordClient client, ulong id, ulong guildId) - : base(client, id) - { - this.CommandType = RestApplicationCommandType.GuildMessageCommand; - this.GuildId = guildId; - } - - internal static RestGuildMessageCommand Create(BaseDiscordClient client, Model model, ulong guildId) - { - var entity = new RestGuildMessageCommand(client, model.Id, guildId); - entity.Update(model); - return entity; - } - - /// - public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGuildMessageCommand(Discord, GuildId, this).ConfigureAwait(false); - - /// - /// Modifies this . - /// - /// The delegate containing the properties to modify the command with. - /// The options to be used when sending the request. - /// - /// The modified command - /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGuildMessageCommand(Discord, this, func, options).ConfigureAwait(false); - - /// - /// Gets the guild that this slash command resides in. - /// - /// if you want the approximate member and presence counts for the guild, otherwise . - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous get operation. The task result contains a - /// . - /// - public Task GetGuild(bool withCounts = false, RequestOptions options = null) - => ClientHelper.GetGuildAsync(this.Discord, this.GuildId, withCounts, options); - } -} diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs deleted file mode 100644 index 02dc173db..000000000 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGuildUserCommand.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Model = Discord.API.ApplicationCommand; - -namespace Discord.Rest -{ - /// - /// Represents a Rest-based guild command. - /// - public class RestGuildUserCommand : RestApplicationCommand - { - /// - /// The guild Id where this command originates. - /// - public ulong GuildId { get; private set; } - - internal RestGuildUserCommand(BaseDiscordClient client, ulong id, ulong guildId) - : base(client, id) - { - this.CommandType = RestApplicationCommandType.GuildUserCommand; - this.GuildId = guildId; - } - - internal static RestGuildUserCommand Create(BaseDiscordClient client, Model model, ulong guildId) - { - var entity = new RestGuildUserCommand(client, model.Id, guildId); - entity.Update(model); - return entity; - } - - /// - public override async Task DeleteAsync(RequestOptions options = null) - => await InteractionHelper.DeleteGuildUserCommand(Discord, GuildId, this).ConfigureAwait(false); - - /// - /// Modifies this . - /// - /// The delegate containing the properties to modify the command with. - /// The options to be used when sending the request. - /// - /// The modified command - /// - public async Task ModifyAsync(Action func, RequestOptions options = null) - => await InteractionHelper.ModifyGuildUserCommand(Discord, this, func, options).ConfigureAwait(false); - - /// - /// Gets the guild that this slash command resides in. - /// - /// if you want the approximate member and presence counts for the guild, otherwise . - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous get operation. The task result contains a - /// . - /// - public Task GetGuild(bool withCounts = false, RequestOptions options = null) - => ClientHelper.GetGuildAsync(this.Discord, this.GuildId, withCounts, options); - } -} diff --git a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs index 190eca89d..9d41ecf48 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs @@ -7,27 +7,9 @@ using System.Threading.Tasks; namespace Discord.API.Gateway { - internal class ApplicationCommandCreatedUpdatedEvent + internal class ApplicationCommandCreatedUpdatedEvent : API.ApplicationCommand { - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("id")] - public ulong Id { get; set; } - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("application_id")] - public ulong ApplicationId { get; set; } - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - - [JsonProperty("options")] - public Optional> Options { get; set; } - - [JsonProperty("default_permission")] - public Optional DefaultPermission { get; set; } + public Optional GuildId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index f7137c90c..b2a823741 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3752,7 +3752,12 @@ - Represends a Websocket-based recieved over the gateway. + Represends a Websocket-based . + + + + + if this command is a global command, otherwise . @@ -3772,20 +3777,34 @@ - A collection of 's recieved over the gateway. + A collection of 's for this command. + + If the is not a slash command, this field will be an empty collection. + - The where this application was created. + Returns the guild this command resides in, if this command is a global command then it will return + + + Modifies the current application command. + + The new properties to use when modifying the command. + The options to be used when sending the request. + + A task that represents the asynchronous modification operation. + + Thrown when you pass in an invalid type. + Represents a choice for a . diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 52a5d309d..0da71faf2 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1946,11 +1946,14 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if(guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } var applicationCommand = SocketApplicationCommand.Create(this, data); @@ -1964,11 +1967,14 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } var applicationCommand = SocketApplicationCommand.Create(this, data); @@ -1982,11 +1988,14 @@ namespace Discord.WebSocket var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild == null) + if (data.GuildId.IsSpecified) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } } var applicationCommand = SocketApplicationCommand.Create(this, data); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index 9f53af562..8d4a4d485 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -10,10 +10,16 @@ using Model = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; namespace Discord.WebSocket { /// - /// Represends a Websocket-based recieved over the gateway. + /// Represends a Websocket-based . /// public class SocketApplicationCommand : SocketEntity, IApplicationCommand { + /// + /// if this command is a global command, otherwise . + /// + public bool IsGlobalCommand + => Guild == null; + /// public ulong ApplicationId { get; private set; } @@ -30,8 +36,11 @@ namespace Discord.WebSocket public bool DefaultPermission { get; private set; } /// - /// A collection of 's recieved over the gateway. + /// A collection of 's for this command. /// + /// + /// If the is not a slash command, this field will be an empty collection. + /// public IReadOnlyCollection Options { get; private set; } /// @@ -39,32 +48,31 @@ namespace Discord.WebSocket => SnowflakeUtils.FromSnowflake(this.Id); /// - /// The where this application was created. + /// Returns the guild this command resides in, if this command is a global command then it will return /// public SocketGuild Guild - => Discord.GetGuild(this.GuildId); - private ulong GuildId { get; set; } + => GuildId.HasValue ? Discord.GetGuild(this.GuildId.Value) : null; + + private ulong? GuildId { get; set; } - internal SocketApplicationCommand(DiscordSocketClient client, ulong id) + internal SocketApplicationCommand(DiscordSocketClient client, ulong id, ulong? guildId) : base(client, id) { - + this.GuildId = guildId; } internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model) { - var entity = new SocketApplicationCommand(client, model.Id); + var entity = new SocketApplicationCommand(client, model.Id, model.GuildId.ToNullable()); entity.Update(model); return entity; } - internal void Update(Model model) + internal void Update(API.ApplicationCommand model) { this.ApplicationId = model.ApplicationId; this.Description = model.Description; this.Name = model.Name; - this.GuildId = model.GuildId; - this.DefaultPermission = model.DefaultPermission.GetValueOrDefault(true); - + this.DefaultPermission = model.DefaultPermissions.GetValueOrDefault(true); this.Options = model.Options.IsSpecified ? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() @@ -73,8 +81,44 @@ namespace Discord.WebSocket /// public Task DeleteAsync(RequestOptions options = null) - => InteractionHelper.DeleteGuildCommand(Discord, this.GuildId, this, options); + => InteractionHelper.DeleteUnknownApplicationCommand(Discord, this.GuildId, this, options); + + /// + /// Modifies the current application command. + /// + /// The new properties to use when modifying the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + /// Thrown when you pass in an invalid type. + public async Task ModifyAsync(Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties + { + switch (typeof(TArg)) + { + case Type messageCommand when messageCommand == typeof(MessageCommandProperties) && this.Type != ApplicationCommandType.Message: + case Type slashCommand when slashCommand == typeof(SlashCommandProperties) && this.Type != ApplicationCommandType.Slash: + case Type userCommand when userCommand == typeof(UserCommandProperties) && this.Type != ApplicationCommandType.User: + throw new InvalidOperationException($"Cannot modify this application command with the parameter type {nameof(TArg)}"); + } + + API.ApplicationCommand command = null; + + if (this.IsGlobalCommand) + { + command = await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false); + } + else + { + command = await InteractionHelper.ModifyGuildCommand(Discord, this, this.GuildId.Value, func, options); + } + + this.Update(command); + } + // IApplicationCommand IReadOnlyCollection IApplicationCommand.Options => Options; + Task IApplicationCommand.ModifyAsync(Action func, RequestOptions options) + => ModifyAsync(func, options); } } From 6326a1efd4f4d74a0d58df9f7f868c46b7d411f3 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 21 Aug 2021 05:06:09 -0300 Subject: [PATCH 12/14] Add more methods for application commands, clean up of code, and add proper caching of socket application commands. --- src/Discord.Net.Core/Discord.Net.Core.xml | 35 ++++++++- .../Entities/Guilds/IGuild.cs | 15 +++- src/Discord.Net.Core/IDiscordClient.cs | 21 ++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 8 +++ src/Discord.Net.Rest/ClientHelper.cs | 20 +++++- src/Discord.Net.Rest/Discord.Net.Rest.xml | 23 ++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 71 +++++-------------- src/Discord.Net.Rest/DiscordRestClient.cs | 7 ++ .../Entities/Guilds/RestGuild.cs | 22 +++++- .../Interactions/InteractionHelper.cs | 22 ++++-- src/Discord.Net.WebSocket/ClientState.cs | 19 +++++ .../Discord.Net.WebSocket.xml | 27 +++++++ .../DiscordSocketClient.cs | 63 +++++++++++++++- .../Entities/Guilds/SocketGuild.cs | 63 +++++++++------- .../Slash Commands/SocketSlashCommandCache.cs | 48 ------------- .../SocketApplicationCommand.cs | 16 +++-- 16 files changed, 333 insertions(+), 147 deletions(-) delete mode 100644 src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index 187461b93..879e5613d 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -3916,7 +3916,7 @@ - Gets this guilds slash commands commands + Gets this guilds application commands. The options to be used when sending the request. @@ -3924,6 +3924,18 @@ of application commands found within the guild. + + + Gets an application command within this guild with the specified id. + + The id of the application command to get. + The that determines whether the object should be fetched from cache. + The options to be used when sending the request. + + A ValueTask that represents the asynchronous get operation. The task result contains a + if found, otherwise . + + Holds information for a guild integration feature. @@ -10971,6 +10983,27 @@ A task that represents the asynchronous get operation. The task result contains a read-only collection of connections. + + + Gets a global application command. + + The id of the command. + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains the application command if found, otherwise + . + + + + + Gets a collection of all global commands. + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection of global + application commands. + + Gets a guild. diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index 414b6fe73..a94c1ed33 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -943,7 +943,7 @@ namespace Discord Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); /// - /// Gets this guilds slash commands commands + /// Gets this guilds application commands. /// /// The options to be used when sending the request. /// @@ -951,5 +951,18 @@ namespace Discord /// of application commands found within the guild. /// Task> GetApplicationCommandsAsync (RequestOptions options = null); + + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// + Task GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index d7d6d2856..a728e6096 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -141,6 +141,27 @@ namespace Discord /// Task> GetConnectionsAsync(RequestOptions options = null); + /// + /// Gets a global application command. + /// + /// The id of the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains the application command if found, otherwise + /// . + /// + Task GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null); + + /// + /// Gets a collection of all global commands. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of global + /// application commands. + /// + Task> GetGlobalApplicationCommandsAsync(RequestOptions options = null); + /// /// Gets a guild. /// diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 68589a4f1..c94723281 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -216,6 +216,14 @@ namespace Discord.Rest Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) => Task.FromResult(null); + /// + Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => Task.FromResult(null); + + /// + Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + /// Task IDiscordClient.StartAsync() => Task.Delay(0); diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 0161483c9..19c3b1325 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -194,7 +194,8 @@ namespace Discord.Rest }; } - public static async Task> GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options) + public static async Task> GetGlobalApplicationCommands(BaseDiscordClient client, + RequestOptions options = null) { var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false); @@ -203,8 +204,16 @@ namespace Discord.Rest return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); } + public static async Task GetGlobalApplicationCommand(BaseDiscordClient client, ulong id, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options); + + return model != null ? RestGlobalCommand.Create(client, model) : null; + } - public static async Task> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options) + public static async Task> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, + RequestOptions options = null) { var response = await client.ApiClient.GetGuildApplicationCommandsAsync(guildId, options).ConfigureAwait(false); @@ -213,6 +222,13 @@ namespace Discord.Rest return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); } + public static async Task GetGuildApplicationCommand(BaseDiscordClient client, ulong id, ulong guildId, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, id, options); + + return model != null ? RestGuildCommand.Create(client, model, guildId) : null; + } public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index 0378df092..2167ad1c5 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -226,6 +226,12 @@ + + + + + + @@ -299,6 +305,12 @@ + + + + + + Represents a configuration class for . @@ -3503,6 +3515,17 @@ of application commands found within the guild. + + + Gets an application command within this guild with the specified id. + + The id of the application command to get. + The options to be used when sending the request. + + A ValueTask that represents the asynchronous get operation. The task result contains a + if found, otherwise . + + Returns the name of the guild. diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 8bf7f6e43..f73799943 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1063,6 +1063,18 @@ namespace Discord.API return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false); } + public async Task GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null) + { + Preconditions.NotEqual(id, 0, nameof(id)); + + options = RequestOptions.CreateOrClone(options); + + try + { + return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/commands/{id}", new BucketIds(), options: options).ConfigureAwait(false); + } + catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } + } public async Task CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) { @@ -1074,7 +1086,6 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options)).ConfigureAwait(false); } public async Task ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) @@ -1158,7 +1169,11 @@ namespace Discord.API var bucket = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options); + try + { + return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options); + } + catch(HttpException x) when (x.HttpCode == HttpStatusCode.NotFound) { return null; } } public async Task CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) @@ -1195,58 +1210,6 @@ namespace Discord.API return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); } - public async Task CreateGuildApplicationUserCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } - - public async Task ModifyGuildApplicationUserCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); - } - public async Task BulkOverwriteGuildApplicationUserCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); - } - - public async Task CreateGuildApplicationMessageCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options)).ConfigureAwait(false); - } - public async Task ModifyGuildApplicationMessageCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options)).ConfigureAwait(false); - } - - public async Task BulkOverwriteGuildApplicationMessageCommands(ulong guildId, CreateApplicationCommandParams[] commands, RequestOptions options = null) - { - options = RequestOptions.CreateOrClone(options); - - var bucket = new BucketIds(guildId: guildId); - - return await TrySendApplicationCommand(SendJsonAsync("PUT", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, bucket, options: options)).ConfigureAwait(false); - } - //Interaction Responses public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 32dbaf40e..feef4c12a 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -226,5 +226,12 @@ namespace Discord.Rest /// async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options).ConfigureAwait(false); + + /// + async Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => await GetGlobalApplicationCommands(options).ConfigureAwait(false); + /// + async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => await ClientHelper.GetGlobalApplicationCommand(this, id, options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 126a211c8..bf4365a5f 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -878,8 +878,19 @@ namespace Discord.Rest /// A task that represents the asynchronous get operation. The task result contains a read-only collection /// of application commands found within the guild. /// - public async Task> GetApplicationCommandsAsync (RequestOptions options = null) + public async Task> GetApplicationCommandsAsync (RequestOptions options = null) => await ClientHelper.GetGuildApplicationCommands(Discord, Id, options).ConfigureAwait(false); + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// + public async Task GetApplicationCommandAsync(ulong id, RequestOptions options = null) + => await ClientHelper.GetGuildApplicationCommand(Discord, id, this.Id, options); /// /// Returns the name of the guild. @@ -1169,5 +1180,14 @@ namespace Discord.Rest /// async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) => await GetApplicationCommandsAsync(options).ConfigureAwait(false); + async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + { + return await GetApplicationCommandAsync(id, options); + } + else + return null; + } } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index fbbce4f4b..adf6226e2 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -44,12 +44,20 @@ namespace Discord.Rest } // Global commands - public static async Task CreateGlobalCommand(BaseDiscordClient client, - Action func, RequestOptions options) where TArg : ApplicationCommandProperties + public static async Task GetGlobalCommandAsync(BaseDiscordClient client, ulong id, + RequestOptions options = null) + { + var model = await client.ApiClient.GetGlobalApplicationCommandAsync(id, options).ConfigureAwait(false); + + + return RestGlobalCommand.Create(client, model); + } + public static Task CreateGlobalCommand(BaseDiscordClient client, + Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); func((TArg)args); - return await CreateGlobalCommand(client, (TArg)args, options); + return CreateGlobalCommand(client, (TArg)args, options); } public static async Task CreateGlobalCommand(BaseDiscordClient client, ApplicationCommandProperties arg, RequestOptions options = null) @@ -116,7 +124,7 @@ namespace Discord.Rest models.Add(model); } - var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options); + var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options).ConfigureAwait(false); return apiModels.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); } @@ -156,7 +164,7 @@ namespace Discord.Rest models.Add(model); } - var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options); + var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options).ConfigureAwait(false); return apiModels.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray(); } @@ -221,12 +229,12 @@ namespace Discord.Rest } // Guild Commands - public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + public static Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); func((TArg)args); - return await CreateGuildCommand(client, guildId, (TArg)args, options); + return CreateGuildCommand(client, guildId, (TArg)args, options); } public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f2e370d02..653eec4c8 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -16,12 +16,14 @@ namespace Discord.WebSocket private readonly ConcurrentDictionary _guilds; private readonly ConcurrentDictionary _users; private readonly ConcurrentHashSet _groupChannels; + private readonly ConcurrentDictionary _commands; internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); internal IReadOnlyCollection DMChannels => _dmChannels.ToReadOnlyCollection(); internal IReadOnlyCollection GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); internal IReadOnlyCollection Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + internal IReadOnlyCollection Commands => _commands.ToReadOnlyCollection(); internal IReadOnlyCollection PrivateChannels => _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( @@ -139,5 +141,22 @@ namespace Discord.WebSocket foreach (var guild in _guilds.Values) guild.PurgeGuildUserCache(); } + + internal SocketApplicationCommand GetCommand(ulong id) + { + if (_commands.TryGetValue(id, out SocketApplicationCommand command)) + return command; + return null; + } + internal void AddCommand(SocketApplicationCommand command) + { + _commands[command.Id] = command; + } + internal SocketApplicationCommand RemoveCommand(ulong id) + { + if (_commands.TryRemove(id, out SocketApplicationCommand command)) + return command; + return null; + } } } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index b2a823741..faacf90ab 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -1078,6 +1078,27 @@ + + + Gets a global application command. + + The id of the command. + The options to be used when sending the request. + + A ValueTask that represents the asynchronous get operation. The task result contains the application command if found, otherwise + . + + + + + Gets a collection of all global commands. + + The options to be used when sending the request. + + A task that represents the asynchronous get operation. The task result contains a read-only collection of global + application commands. + + Clears cached users from the client. @@ -1166,6 +1187,12 @@ + + + + + + diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 0da71faf2..53d712dbf 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -343,6 +343,54 @@ namespace Discord.WebSocket /// public override SocketUser GetUser(string username, string discriminator) => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); + + /// + /// Gets a global application command. + /// + /// The id of the command. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains the application command if found, otherwise + /// . + /// + public async ValueTask GetGlobalApplicationCommandAsync(ulong id, RequestOptions options = null) + { + var command = State.GetCommand(id); + + if (command != null) + return command; + + var model = await ApiClient.GetGlobalApplicationCommandAsync(id, options); + + if (model == null) + return null; + + command = SocketApplicationCommand.Create(this, model); + + State.AddCommand(command); + + return command; + } + /// + /// Gets a collection of all global commands. + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection of global + /// application commands. + /// + public async Task> GetGlobalApplicationCommandsAsync(RequestOptions options = null) + { + var commands = (await ApiClient.GetGlobalApplicationCommandsAsync(options)).Select(x => SocketApplicationCommand.Create(this, x)); + + foreach(var command in commands) + { + State.AddCommand(command); + } + + return commands.ToImmutableArray(); + } + /// /// Clears cached users from the client. /// @@ -1891,8 +1939,6 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); - // 0x546861742062696720656e6469616e20656e636f64696e67206d616b6573206d79316d687a20636c6f636b207469636b - var data = (payload as JToken).ToObject(_serializer); SocketChannel channel = null; @@ -1958,6 +2004,8 @@ namespace Discord.WebSocket var applicationCommand = SocketApplicationCommand.Create(this, data); + State.AddCommand(applicationCommand); + await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); } break; @@ -1979,6 +2027,8 @@ namespace Discord.WebSocket var applicationCommand = SocketApplicationCommand.Create(this, data); + State.AddCommand(applicationCommand); + await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); } break; @@ -2000,6 +2050,8 @@ namespace Discord.WebSocket var applicationCommand = SocketApplicationCommand.Create(this, data); + State.RemoveCommand(applicationCommand.Id); + await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); } break; @@ -2611,6 +2663,13 @@ namespace Discord.WebSocket async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); + /// + async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options) + => await GetGlobalApplicationCommandAsync(id, options); + /// + async Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) + => await GetGlobalApplicationCommandsAsync(options); + /// async Task IDiscordClient.StartAsync() => await StartAsync().ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 5c385fe01..3b92e6a22 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -785,13 +785,13 @@ namespace Discord.WebSocket //Interactions /// - /// Deletes all slash commands in the current guild. + /// Deletes all application commands in the current guild. /// /// The options to be used when sending the request. /// /// A task that represents the asynchronous delete operation. /// - public Task DeleteSlashCommandsAsync(RequestOptions options = null) + public Task DeleteApplicationCommandsAsync(RequestOptions options = null) => InteractionHelper.DeleteAllGuildCommandsAsync(Discord, this.Id, options); /// @@ -802,20 +802,39 @@ namespace Discord.WebSocket /// A task that represents the asynchronous get operation. The task result contains a read-only collection of /// slash commands created by the current user. /// - public Task> GetSlashCommandsAsync(RequestOptions options = null) - => GuildHelper.GetSlashCommandsAsync(this, Discord, options); + public async Task> GetApplicationCommandsAsync(RequestOptions options = null) + { + var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x)); - /// - /// Gets a slash command in the current guild. - /// - /// The unique identifier of the slash command. - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous get operation. The task result contains a - /// slash command created by the current user. - /// - public Task GetSlashCommandAsync(ulong id, RequestOptions options = null) - => GuildHelper.GetSlashCommandAsync(this, id, Discord, options); + foreach (var command in commands) + { + Discord.State.AddCommand(command); + } + + return commands.ToImmutableArray(); + } + + public async ValueTask GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) + { + var command = Discord.State.GetCommand(id); + + if (command != null) + return command; + + if (mode == CacheMode.CacheOnly) + return null; + + var model = await Discord.ApiClient.GetGlobalApplicationCommandAsync(id, options); + + if (model == null) + return null; + + command = SocketApplicationCommand.Create(Discord, model); + + Discord.State.AddCommand(command); + + return command; + } //Invites /// @@ -1079,18 +1098,6 @@ namespace Discord.WebSocket public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); - //Interactions - /// - /// Gets this guilds slash commands commands - /// - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous get operation. The task result contains a read-only collection - /// of application commands found within the guild. - /// - public async Task> GetApplicationCommandsAsync(RequestOptions options = null) - => await Discord.Rest.GetGuildApplicationCommands(this.Id, options); - //Emotes /// public Task> GetEmotesAsync(RequestOptions options = null) @@ -1481,6 +1488,8 @@ namespace Discord.WebSocket /// async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) => await GetApplicationCommandsAsync(options).ConfigureAwait(false); + async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) + => await GetApplicationCommandAsync(id, mode, options); void IDisposable.Dispose() { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs deleted file mode 100644 index 7dd30151d..000000000 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandCache.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord.WebSocket.Entities.Interaction -{ - internal class SlashCommandCache - { - private readonly ConcurrentDictionary _slashCommands; - private readonly ConcurrentQueue _orderedSlashCommands; - private readonly int _size; - - public IReadOnlyCollection Messages => _slashCommands.ToReadOnlyCollection(); - - public SlashCommandCache(DiscordSocketClient client) - { - _size = 256; - _slashCommands = new ConcurrentDictionary(); - - } - - public void Add(SocketSlashCommand slashCommand) - { - if (_slashCommands.TryAdd(slashCommand.Id, slashCommand)) - { - _orderedSlashCommands.Enqueue(slashCommand.Id); - - while (_orderedSlashCommands.Count > _size && _orderedSlashCommands.TryDequeue(out ulong msgId)) - _slashCommands.TryRemove(msgId, out _); - } - } - - public SocketSlashCommand Remove(ulong id) - { - _slashCommands.TryRemove(id, out var slashCommand); - return slashCommand; - } - - public SocketSlashCommand Get(ulong id) - { - _slashCommands.TryGetValue(id, out var slashCommands); - return slashCommands; - } - } -} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index 8d4a4d485..77a43a1e3 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -5,7 +5,8 @@ using System.Collections.Immutable; using System.Linq; using System.Text; using System.Threading.Tasks; -using Model = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; +using GatewayModel = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; +using Model = Discord.API.ApplicationCommand; namespace Discord.WebSocket { @@ -60,14 +61,21 @@ namespace Discord.WebSocket { this.GuildId = guildId; } - internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model) + internal static SocketApplicationCommand Create(DiscordSocketClient client, GatewayModel model) { var entity = new SocketApplicationCommand(client, model.Id, model.GuildId.ToNullable()); entity.Update(model); return entity; } - internal void Update(API.ApplicationCommand model) + internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model, ulong? guildId = null) + { + var entity = new SocketApplicationCommand(client, model.Id, guildId); + entity.Update(model); + return entity; + } + + internal void Update(Model model) { this.ApplicationId = model.ApplicationId; this.Description = model.Description; @@ -102,7 +110,7 @@ namespace Discord.WebSocket throw new InvalidOperationException($"Cannot modify this application command with the parameter type {nameof(TArg)}"); } - API.ApplicationCommand command = null; + Model command = null; if (this.IsGlobalCommand) { From 8b5d5724c96542414292910053d0d33497b6c9bd Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 21 Aug 2021 05:35:24 -0300 Subject: [PATCH 13/14] Fix some bugs --- .../Context Menus/MessageCommandBuilder.cs | 5 ---- .../Context Menus/UserCommandBuilder.cs | 5 ---- .../API/Common/InteractionCallbackData.cs | 1 - .../Discord.Net.WebSocket.xml | 29 +++---------------- .../User Commands/SocketUserCommandData.cs | 6 ++-- .../SocketBaseCommand/SocketCommandBase.cs | 6 ++-- 6 files changed, 10 insertions(+), 42 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs index 9907dd2cf..801aca302 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs @@ -32,11 +32,6 @@ namespace Discord Preconditions.AtLeast(value.Length, 3, nameof(Name)); Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); - // Discord updated the docs, this regex prevents special characters like @!$%(... etc, - // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand - if (!Regex.IsMatch(value, @"^[\w -]{3,32}$")) - throw new ArgumentException("Command name cannot contain any special characters or whitespaces!"); - _name = value; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs index d415a99a6..5629a7014 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs @@ -32,11 +32,6 @@ namespace Discord Preconditions.AtLeast(value.Length, 3, nameof(Name)); Preconditions.AtMost(value.Length, MaxNameLength, nameof(Name)); - // Discord updated the docs, this regex prevents special characters like @!$%(... etc, - // https://discord.com/developers/docs/interactions/slash-commands#applicationcommand - if (!Regex.IsMatch(value, @"^[\w -]{3,32}$")) - throw new ArgumentException("Command name cannot contain any special characters or whitespaces!"); - _name = value; } } diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs index ba233cc6b..2101f79f4 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -16,7 +16,6 @@ namespace Discord.API [JsonProperty("allowed_mentions")] public Optional AllowedMentions { get; set; } - // New flags prop. this make the response "ephemeral". see https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata [JsonProperty("flags")] public Optional Flags { get; set; } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index faacf90ab..1a7504102 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3271,16 +3271,16 @@ voice regions the guild can access. - + - Deletes all slash commands in the current guild. + Deletes all application commands in the current guild. The options to be used when sending the request. A task that represents the asynchronous delete operation. - + Gets a collection of slash commands created by the current user in this guild. @@ -3290,17 +3290,6 @@ slash commands created by the current user. - - - Gets a slash command in the current guild. - - The unique identifier of the slash command. - The options to be used when sending the request. - - A task that represents the asynchronous get operation. The task result contains a - slash command created by the current user. - - Gets a collection of all invites in this guild. @@ -3439,16 +3428,6 @@ of webhooks found within the guild. - - - Gets this guilds slash commands commands - - The options to be used when sending the request. - - A task that represents the asynchronous get operation. The task result contains a read-only collection - of application commands found within the guild. - - @@ -3672,7 +3651,7 @@ - The user used to run the command + Gets the user who this command targets. diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs index 70eb4adff..d6c2a7990 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Context Menu Commands/User Commands/SocketUserCommandData.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Model = Discord.API.ApplicationCommandInteractionData; namespace Discord.WebSocket @@ -9,9 +10,10 @@ namespace Discord.WebSocket public class SocketUserCommandData : SocketCommandBaseData { /// - /// The user used to run the command + /// Gets the user who this command targets. /// - public SocketUser Member { get; private set; } + public SocketUser Member + => (SocketUser)ResolvableData.GuildMembers.Values.FirstOrDefault() ?? ResolvableData.Users.Values.FirstOrDefault(); /// /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 7c8e2422e..8ca20aa7c 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -103,13 +103,11 @@ namespace Discord.WebSocket AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, TTS = isTTS ? true : Optional.Unspecified, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + Flags = ephemeral ? MessageFlags.Ephemeral : Optional.Unspecified } }; - if (ephemeral) - response.Data.Value.Flags = MessageFlags.Ephemeral; - await InteractionHelper.SendInteractionResponse(this.Discord, response, this.Id, Token, options); } From f5d064dfc389b831538e6bd51cdacdb40db84690 Mon Sep 17 00:00:00 2001 From: quin lynch Date: Sat, 21 Aug 2021 07:10:49 -0300 Subject: [PATCH 14/14] Add more routes to guilds and clients. --- src/Discord.Net.Core/Discord.Net.Core.xml | 40 ++++++++++++ .../Entities/Guilds/IGuild.cs | 21 +++++++ src/Discord.Net.Core/IDiscordClient.cs | 20 ++++++ src/Discord.Net.Rest/BaseDiscordClient.cs | 5 ++ src/Discord.Net.Rest/ClientHelper.cs | 27 ++++++++ src/Discord.Net.Rest/Discord.Net.Rest.xml | 29 +++++++++ src/Discord.Net.Rest/DiscordRestClient.cs | 12 ++-- .../Entities/Guilds/RestGuild.cs | 37 +++++++++++ .../Interactions/InteractionHelper.cs | 26 +++----- src/Discord.Net.WebSocket/ClientState.cs | 12 ++++ .../Discord.Net.WebSocket.csproj | 2 +- .../Discord.Net.WebSocket.xml | 32 ++++++++++ .../DiscordSocketClient.cs | 29 +++++++++ .../Entities/Guilds/SocketGuild.cs | 63 ++++++++++++++++++- 14 files changed, 328 insertions(+), 27 deletions(-) diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index 879e5613d..c8695e4f1 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -3936,6 +3936,26 @@ if found, otherwise . + + + Creates an application command within this guild. + + The properties to use when creating the command. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains the command that was created. + + + + + Overwrites the application commands within this guild. + + A collection of properties to use when creating the commands. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + + Holds information for a guild integration feature. @@ -11004,6 +11024,26 @@ application commands. + + + Creates a global application command. + + The properties to use when creating the command. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains the created application command. + + + + + Bulk overwrites all global application commands. + + A collection of properties to use when creating the commands. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains a collection of application commands that were created. + + Gets a guild. diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index a94c1ed33..cd578e621 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -964,5 +964,26 @@ namespace Discord /// Task GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null); + + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + Task> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index a728e6096..f6981d552 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -162,6 +162,26 @@ namespace Discord /// Task> GetGlobalApplicationCommandsAsync(RequestOptions options = null); + /// + /// Creates a global application command. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the created application command. + /// + Task CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options = null); + + /// + /// Bulk overwrites all global application commands. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of application commands that were created. + /// + Task> BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties, RequestOptions options = null); + /// /// Gets a guild. /// diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index c94723281..93b82c929 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -223,6 +223,11 @@ namespace Discord.Rest /// Task> IDiscordClient.GetGlobalApplicationCommandsAsync(RequestOptions options) => Task.FromResult>(ImmutableArray.Create()); + Task IDiscordClient.CreateGlobalApplicationCommand(ApplicationCommandProperties properties, RequestOptions options) + => Task.FromResult(null); + Task> IDiscordClient.BulkOverwriteGlobalApplicationCommand(ApplicationCommandProperties[] properties, + RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); /// Task IDiscordClient.StartAsync() diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 19c3b1325..2fc382900 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -229,7 +229,34 @@ namespace Discord.Rest return model != null ? RestGuildCommand.Create(client, model, guildId) : null; } + public static async Task CreateGuildApplicationCommand(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties properties, + RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommand(client, guildId, properties, options); + + return RestGuildCommand.Create(client, model, guildId); + } + public static async Task CreateGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties properties, + RequestOptions options = null) + { + var model = await InteractionHelper.CreateGlobalCommand(client, properties, options); + + return RestGlobalCommand.Create(client, model); + } + public static async Task> BulkOverwriteGlobalApplicationCommand(BaseDiscordClient client, ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGlobalCommands(client, properties, options); + return models.Select(x => RestGlobalCommand.Create(client, x)).ToImmutableArray(); + } + public static async Task> BulkOverwriteGuildApplicationCommand(BaseDiscordClient client, ulong guildId, + ApplicationCommandProperties[] properties, RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommands(client, guildId, properties, options); + + return models.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); + } public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) => client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.xml b/src/Discord.Net.Rest/Discord.Net.Rest.xml index 2167ad1c5..02065499d 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.xml +++ b/src/Discord.Net.Rest/Discord.Net.Rest.xml @@ -3526,6 +3526,26 @@ if found, otherwise . + + + Creates an application command within this guild. + + The properties to use when creating the command. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains the command that was created. + + + + + Overwrites the application commands within this guild. + + A collection of properties to use when creating the commands. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + + Returns the name of the guild. @@ -3686,6 +3706,15 @@ + + + + + + + + + diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index feef4c12a..10a4c40a9 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -109,21 +109,17 @@ namespace Discord.Rest => ClientHelper.GetWebhookAsync(this, id, options); public Task CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null) - => InteractionHelper.CreateGlobalCommand(this, properties, options); - public Task CreateGlobalCommand(Action func, RequestOptions options = null) - => InteractionHelper.CreateGlobalCommand(this, func, options); + => ClientHelper.CreateGlobalApplicationCommand(this, properties, options); public Task CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); - public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) - => InteractionHelper.CreateGuildCommand(this, guildId, func, options); + => ClientHelper.CreateGuildApplicationCommand(this, guildId, properties, options); public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); public Task> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) => ClientHelper.GetGuildApplicationCommands(this, guildId, options); public Task> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null) - => InteractionHelper.BulkOverwriteGlobalCommands(this, commandProperties, options); + => ClientHelper.BulkOverwriteGlobalApplicationCommand(this, commandProperties, options); public Task> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null) - => InteractionHelper.BulkOverwriteGuildCommands(this, guildId, commandProperties, options); + => ClientHelper.BulkOverwriteGuildApplicationCommand(this, guildId, commandProperties, options); public Task> BatchEditGuildCommandPermissions(ulong guildId, IDictionary permissions, RequestOptions options = null) => InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options); public Task DeleteAllGlobalCommandsAsync(RequestOptions options = null) diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index bf4365a5f..1c453910d 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -891,6 +891,35 @@ namespace Discord.Rest /// public async Task GetApplicationCommandAsync(ulong id, RequestOptions options = null) => await ClientHelper.GetGuildApplicationCommand(Discord, id, this.Id, options); + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommand(Discord, this.Id, properties, options); + + return RestGuildCommand.Create(Discord, model, this.Id); + } + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + public async Task> BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, this.Id, properties, options); + + return models.Select(x => RestGuildCommand.Create(Discord, x, this.Id)).ToImmutableArray(); + } /// /// Returns the name of the guild. @@ -1180,6 +1209,14 @@ namespace Discord.Rest /// async Task> IGuild.GetApplicationCommandsAsync (RequestOptions options) => await GetApplicationCommandsAsync(options).ConfigureAwait(false); + /// + async Task IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options) + => await CreateApplicationCommandAsync(properties, options); + /// + async Task> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options) + => await BulkOverwriteApplicationCommandsAsync(properties, options); + /// async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index adf6226e2..59d5c4f2b 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -52,14 +52,14 @@ namespace Discord.Rest return RestGlobalCommand.Create(client, model); } - public static Task CreateGlobalCommand(BaseDiscordClient client, + public static Task CreateGlobalCommand(BaseDiscordClient client, Action func, RequestOptions options = null) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); func((TArg)args); return CreateGlobalCommand(client, (TArg)args, options); } - public static async Task CreateGlobalCommand(BaseDiscordClient client, + public static async Task CreateGlobalCommand(BaseDiscordClient client, ApplicationCommandProperties arg, RequestOptions options = null) { Preconditions.NotNullOrEmpty(arg.Name, nameof(arg.Name)); @@ -85,11 +85,10 @@ namespace Discord.Rest : Optional.Unspecified; } - var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); - return RestGlobalCommand.Create(client, cmd); + return await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGlobalCommands(BaseDiscordClient client, + public static async Task BulkOverwriteGlobalCommands(BaseDiscordClient client, ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -124,12 +123,10 @@ namespace Discord.Rest models.Add(model); } - var apiModels = await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options).ConfigureAwait(false); - - return apiModels.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); + return await client.ApiClient.BulkOverwriteGlobalApplicationCommands(models.ToArray(), options).ConfigureAwait(false); } - public static async Task> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId, + public static async Task> BulkOverwriteGuildCommands(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties[] args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); @@ -164,9 +161,7 @@ namespace Discord.Rest models.Add(model); } - var apiModels = await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options).ConfigureAwait(false); - - return apiModels.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray(); + return await client.ApiClient.BulkOverwriteGuildApplicationCommands(guildId, models.ToArray(), options).ConfigureAwait(false); } public static Task ModifyGlobalCommand(BaseDiscordClient client, IApplicationCommand command, @@ -229,7 +224,7 @@ namespace Discord.Rest } // Guild Commands - public static Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + public static Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, Action func, RequestOptions options) where TArg : ApplicationCommandProperties { var args = Activator.CreateInstance(typeof(TArg)); @@ -237,7 +232,7 @@ namespace Discord.Rest return CreateGuildCommand(client, guildId, (TArg)args, options); } - public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + public static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, ApplicationCommandProperties arg, RequestOptions options = null) { var model = new CreateApplicationCommandParams() @@ -261,8 +256,7 @@ namespace Discord.Rest : Optional.Unspecified; } - var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); - return RestGuildCommand.Create(client, cmd, guildId); + return await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); } public static Task ModifyGuildCommand(BaseDiscordClient client, IApplicationCommand command, ulong guildId, diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index 653eec4c8..7129feb48 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -39,6 +39,7 @@ namespace Discord.WebSocket _guilds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); _groupChannels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); + _commands = new ConcurrentDictionary(); } internal SocketChannel GetChannel(ulong id) @@ -152,11 +153,22 @@ namespace Discord.WebSocket { _commands[command.Id] = command; } + internal SocketApplicationCommand GetOrAddCommand(ulong id, Func commandFactory) + { + return _commands.GetOrAdd(id, commandFactory); + } internal SocketApplicationCommand RemoveCommand(ulong id) { if (_commands.TryRemove(id, out SocketApplicationCommand command)) return command; return null; } + internal void PurgeCommands(Func precondition) + { + var ids = _commands.Where(x => precondition(x.Value)).Select(x => x.Key); + + foreach (var id in ids) + _commands.TryRemove(id, out var _); + } } } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index 167ccebb0..e6352780e 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -20,7 +20,7 @@ 3.0.1 - TRACE; + TRACE diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index 1a7504102..1fb19d207 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3290,6 +3290,38 @@ slash commands created by the current user. + + + Gets an application command within this guild with the specified id. + + The id of the application command to get. + The that determines whether the object should be fetched from cache. + The options to be used when sending the request. + + A ValueTask that represents the asynchronous get operation. The task result contains a + if found, otherwise . + + + + + Creates an application command within this guild. + + The properties to use when creating the command. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains the command that was created. + + + + + Overwrites the application commands within this guild. + + A collection of properties to use when creating the commands. + The options to be used when sending the request. + + A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + + Gets a collection of all invites in this guild. diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 53d712dbf..2f99e60d3 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -391,6 +391,35 @@ namespace Discord.WebSocket return commands.ToImmutableArray(); } + public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGlobalCommand(this, properties, options).ConfigureAwait(false); + + var entity = State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(this, model)); + + // update it incase it was cached + entity.Update(model); + + return entity; + } + public async Task> BulkOverwriteGlobalApplicationCommandsAsync( + ApplicationCommandProperties[] properties, RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGlobalCommands(this, properties, options); + + var entities = models.Select(x => SocketApplicationCommand.Create(this, x)); + + // purge our previous commands + State.PurgeCommands(x => x.IsGlobalCommand); + + foreach(var entity in entities) + { + State.AddCommand(entity); + } + + return entities.ToImmutableArray(); + } + /// /// Clears cached users from the client. /// diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 3b92e6a22..19af8038f 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -804,7 +804,7 @@ namespace Discord.WebSocket /// public async Task> GetApplicationCommandsAsync(RequestOptions options = null) { - var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x)); + var commands = (await Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Id, options)).Select(x => SocketApplicationCommand.Create(Discord, x, this.Id)); foreach (var command in commands) { @@ -814,6 +814,16 @@ namespace Discord.WebSocket return commands.ToImmutableArray(); } + /// + /// Gets an application command within this guild with the specified id. + /// + /// The id of the application command to get. + /// The that determines whether the object should be fetched from cache. + /// The options to be used when sending the request. + /// + /// A ValueTask that represents the asynchronous get operation. The task result contains a + /// if found, otherwise . + /// public async ValueTask GetApplicationCommandAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) { var command = Discord.State.GetCommand(id); @@ -829,13 +839,57 @@ namespace Discord.WebSocket if (model == null) return null; - command = SocketApplicationCommand.Create(Discord, model); + command = SocketApplicationCommand.Create(Discord, model, this.Id); Discord.State.AddCommand(command); return command; } + /// + /// Creates an application command within this guild. + /// + /// The properties to use when creating the command. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains the command that was created. + /// + public async Task CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) + { + var model = await InteractionHelper.CreateGuildCommand(Discord, this.Id, properties, options); + + var entity = Discord.State.GetOrAddCommand(model.Id, (id) => SocketApplicationCommand.Create(Discord, model)); + + entity.Update(model); + + return entity; + } + + /// + /// Overwrites the application commands within this guild. + /// + /// A collection of properties to use when creating the commands. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous creation operation. The task result contains a collection of commands that was created. + /// + public async Task> BulkOverwriteApplicationCommandAsync(ApplicationCommandProperties[] properties, + RequestOptions options = null) + { + var models = await InteractionHelper.BulkOverwriteGuildCommands(Discord, this.Id, properties, options); + + var entities = models.Select(x => SocketApplicationCommand.Create(Discord, x)); + + Discord.State.PurgeCommands(x => !x.IsGlobalCommand && x.Guild.Id == this.Id); + + foreach(var entity in entities) + { + Discord.State.AddCommand(entity); + } + + return entities.ToImmutableArray(); + } + //Invites /// /// Gets a collection of all invites in this guild. @@ -1490,6 +1544,11 @@ namespace Discord.WebSocket => await GetApplicationCommandsAsync(options).ConfigureAwait(false); async Task IGuild.GetApplicationCommandAsync(ulong id, CacheMode mode, RequestOptions options) => await GetApplicationCommandAsync(id, mode, options); + async Task IGuild.CreateApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options) + => await CreateApplicationCommandAsync(properties, options); + async Task> IGuild.BulkOverwriteApplicationCommandsAsync(ApplicationCommandProperties[] properties, + RequestOptions options) + => await BulkOverwriteApplicationCommandAsync(properties, options); void IDisposable.Dispose() {