From 8c1a26f6978414b1a4aff3fa9ccff5c4f75f7321 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Sat, 14 Aug 2021 11:10:22 -0400 Subject: [PATCH] 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