diff --git a/StyleAnalyzer.targets b/StyleAnalyzer.targets index bbb90b800..2df86122a 100644 --- a/StyleAnalyzer.targets +++ b/StyleAnalyzer.targets @@ -1,9 +1,9 @@ - + diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs index 9d876a3bf..3a03e028b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs @@ -11,13 +11,44 @@ namespace Discord /// public enum ApplicationCommandOptionType : byte { + /// + /// A sub command + /// SubCommand = 1, + + /// + /// A group of sub commands + /// SubCommandGroup = 2, + + /// + /// A of text + /// String = 3, + + /// + /// An + /// Integer = 4, + + /// + /// A + /// Boolean = 5, + + /// + /// A + /// User = 6, + + /// + /// A + /// Channel = 7, + + /// + /// A + /// Role = 8 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index 5b0c0ecf6..08406f844 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -6,6 +6,10 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Provides properties that are used to modify a with the specified changes. + /// + /// public class ApplicationCommandProperties { public string Name { get; set; } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs index 903da5bd4..cd119cf4b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs @@ -31,6 +31,16 @@ namespace Discord /// string Description { get; } + /// + /// Modifies this command + /// + /// The delegate containing the properties to modify the command with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + Task ModifyAsync(Action func, RequestOptions options = null); + IEnumerable? Options { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs index 73d468fe0..c2b00ac4a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs @@ -6,10 +6,24 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents data of an Interaction Command, see + /// public interface IApplicationCommandInteractionData { + /// + /// The snowflake id of this command + /// ulong Id { get; } + + /// + /// The name of this command + /// string Name { get; } - IEnumerable Options { get; } + + /// + /// The params + values from the user + /// + IReadOnlyCollection Options { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs index 7454e0c99..931c12f2a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs @@ -6,11 +6,25 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a option group for a command, see + /// public interface IApplicationCommandInteractionDataOption { + /// + /// The name of the parameter + /// string Name { get; } - ApplicationCommandOptionType Value { get; } - IEnumerable Options { get; } + + /// + /// The value of the pair + /// + ApplicationCommandOptionType? Value { get; } + + /// + /// Present if this option is a group or subcommand + /// + IReadOnlyCollection Options { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs index 7e8e7668d..d7d81ab0d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs @@ -6,6 +6,9 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Specifies choices for command group + /// public interface IApplicationCommandOptionChoice { /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 02398a190..80b0e9153 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -16,12 +16,40 @@ namespace Discord /// id of the interaction /// ulong Id { get; } + + /// + /// The type of this + /// InteractionType Type { get; } + + /// + /// The command data payload + /// IApplicationCommandInteractionData? Data { get; } + + /// + /// The guild it was sent from + /// ulong GuildId { get; } + + /// + /// The channel it was sent from + /// ulong ChannelId { get; } - IGuildUser Member { get; } + + /// + /// Guild member id for the invoking user + /// + ulong MemberId { get; } + + /// + /// A continuation token for responding to the interaction + /// string Token { get; } + + /// + /// read-only property, always 1 + /// int Version { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs new file mode 100644 index 000000000..afb1ff13c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionResponseType.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// The response type for an + /// + public enum InteractionResponseType : byte + { + /// + /// ACK a Ping + /// + Pong = 1, + + /// + /// ACK a command without sending a message, eating the user's input + /// + Acknowledge = 2, + + /// + /// Respond with a message, eating the user's input + /// + ChannelMessage = 3, + + /// + /// respond with a message, showing the user's input + /// + ChannelMessageWithSource = 4, + + /// + /// ACK a command without sending a message, showing the user's input + /// + ACKWithSource = 5 + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs index 19a5eb829..0b5f32f1f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/InteractionType.cs @@ -6,9 +6,19 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a type of Interaction from discord. + /// public enum InteractionType : byte { + /// + /// A ping from discord + /// Ping = 1, + + /// + /// An sent from discord + /// ApplicationCommand = 2 } } diff --git a/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs new file mode 100644 index 000000000..3d9434d94 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class InteractionApplicationCommandCallbackData + { + [JsonProperty("tts")] + public Optional TTS { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/InteractionFollowupMessage.cs b/src/Discord.Net.Rest/API/Common/InteractionFollowupMessage.cs new file mode 100644 index 000000000..28de67ee6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InteractionFollowupMessage.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class InteractionFollowupMessage + { + public string Content { get; set; } + public Optional Username { get; set; } + public Optional AvatarUrl { get; set; } + public Optional TTS { get; set; } + public Optional File { get; set; } + public Embed[] Embeds { get; set; } + + } +} diff --git a/src/Discord.Net.Rest/API/Common/InteractionResponse.cs b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs new file mode 100644 index 000000000..6be48340b --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/InteractionResponse.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class InteractionResponse + { + [JsonProperty("type")] + public InteractionResponseType Type { get; set; } + + [JsonProperty("data")] + public Optional Data { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs new file mode 100644 index 000000000..4012debd7 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal class ModifyInteractionResponseParams + { + [JsonProperty("content")] + public string Content { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 29e8dca0e..60a82d799 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -803,13 +803,9 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - try - { - return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return null; } + return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false); } - public async Task EditGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null) + public async Task ModifyGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); @@ -817,28 +813,13 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - try - { - return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return null; } + return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false); } public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) - { - try - { - await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return; } - } + => await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false); + public async Task GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = null) - { - try - { - return await SendAsync("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return null; } - } + => await SendAsync("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false); public async Task CreateGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); @@ -847,13 +828,9 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - try - { - return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return null; } + return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false); } - public async Task EditGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) + public async Task ModifyGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); @@ -861,19 +838,27 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - try - { - return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return null; } + return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) + => await SendAsync("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); + + //Interaction Responses + public async Task CreateInteractionResponse(InteractionResponse response, string interactionId, string interactionToken, RequestOptions options = null) { - try - { - await SendAsync("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); - } - catch (HttpException ex) { return; } + if(response.Data.IsSpecified) + Preconditions.AtMost(response.Data.Value.Content.Length, 2000, nameof(response.Data.Value.Content)); + + await SendJsonAsync("POST", $"/interactions/{interactionId}/{interactionToken}/callback", response, options: options); + } + public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) + => await SendJsonAsync("POST", $"/webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", args, options: options); + public async Task DeleteInteractionResponse(string interactionToken, RequestOptions options = null) + => await SendAsync("DELETE", $"/webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", options: options); + + public async Task CreateInteractionFollowupMessage() + { + } //Guilds diff --git a/src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs index dd1141d99..40dd1f6fc 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/ApplicationCommands/ApplicationCommandHelper.cs @@ -27,7 +27,7 @@ namespace Discord.Rest : Optional.Unspecified, }; - return await client.ApiClient.EditGlobalApplicationCommandAsync(apiArgs, command.Id, options); + return await client.ApiClient.ModifyGlobalApplicationCommandAsync(apiArgs, command.Id, options); } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 0d78b405d..67e24e616 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1780,7 +1780,7 @@ namespace Discord.WebSocket break; case "INTERACTION_CREATE": { - await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 7786ce339..dbf2c369b 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -3,32 +3,55 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Model = Discord.API.Gateway.InteractionCreated; namespace Discord.WebSocket.Entities.Interaction { public class SocketInteraction : SocketEntity, IDiscordInteraction { - public ulong Id { get; } - - public InteractionType Type { get; } - - public IApplicationCommandInteractionData Data { get; } - - public ulong GuildId { get; } - - public ulong ChannelId { get; } - - public IGuildUser Member { get; } - - public string Token { get; } + public SocketGuild Guild + => Discord.GetGuild(GuildId); + public SocketTextChannel Channel + => Guild.GetTextChannel(ChannelId); + public SocketGuildUser Member + => Guild.GetUser(MemberId); + + public InteractionType Type { get; private set; } + public IApplicationCommandInteractionData Data { get; private set; } + public string Token { get; private set; } + public int Version { get; private set; } + public DateTimeOffset CreatedAt { get; } - public int Version { get; } + public ulong GuildId { get; private set; } + public ulong ChannelId { get; private set; } + public ulong MemberId { get; private set; } - public DateTimeOffset CreatedAt { get; } - public SocketInteraction(DiscordSocketClient client, ulong id) + + internal SocketInteraction(DiscordSocketClient client, ulong id) : base(client, id) { + } + internal static SocketInteraction Create(DiscordSocketClient client, Model model) + { + var entitiy = new SocketInteraction(client, model.Id); + entitiy.Update(model); + return entitiy; } + + internal void Update(Model model) + { + this.Data = model.Data.IsSpecified + ? SocketInteractionData.Create(this.Discord, model.Data.Value) + : null; + + this.GuildId = model.GuildId; + this.ChannelId = model.ChannelId; + this.Token = model.Token; + this.Version = model.Version; + this.MemberId = model.Member.User.Id; + this.Type = model.Type; + } + } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs new file mode 100644 index 000000000..8e2ae3bf5 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionData; + +namespace Discord.WebSocket.Entities.Interaction +{ + public class SocketInteractionData : SocketEntity, IApplicationCommandInteractionData + { + public string Name { get; private set; } + public IReadOnlyCollection Options { get; private set; } + + internal SocketInteractionData(DiscordSocketClient client, ulong id) + : base(client, id) + { + + } + + internal static SocketInteractionData Create(DiscordSocketClient client, Model model) + { + var entity = new SocketInteractionData(client, model.Id); + 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 SocketInteractionDataOption(x)).ToImmutableArray() + : null; + + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs new file mode 100644 index 000000000..086ef1b87 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandInteractionDataOption; + +namespace Discord.WebSocket.Entities.Interaction +{ + public class SocketInteractionDataOption : IApplicationCommandInteractionDataOption + { + public string Name { get; private set; } + public ApplicationCommandOptionType? Value { get; private set; } + + public IReadOnlyCollection Options { get; private set; } + + internal SocketInteractionDataOption(Model model) + { + this.Name = Name; + this.Value = model.Value.IsSpecified ? model.Value.Value : null; + + this.Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray() + : null; + } + } +}