diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index b164aad27..346c49f2f 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 - 2.3.3 + 2.3.4 Discord.Net.Labs.Core https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs index 7151cc5af..c29ef44a2 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs @@ -7,16 +7,21 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a Row for child components to live in. + /// public class ActionRowComponent : IMessageComponent { - [JsonProperty("type")] + /// public ComponentType Type { get; } = ComponentType.ActionRow; - [JsonProperty("components")] - public IReadOnlyCollection Components { get; internal set; } + /// + /// The child components in this row. + /// + public IReadOnlyCollection Components { get; internal set; } internal ActionRowComponent() { } - internal ActionRowComponent(IReadOnlyCollection components) + internal ActionRowComponent(List components) { this.Components = components; } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs index 2e2b98f98..aede74687 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs @@ -7,27 +7,45 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a Button. + /// public class ButtonComponent : IMessageComponent { - [JsonProperty("type")] + /// public ComponentType Type { get; } = ComponentType.Button; - [JsonProperty("style")] + /// + /// The of this button, example buttons with each style can be found Here. + /// public ButtonStyle Style { get; } - [JsonProperty("label")] + /// + /// The label of the button, this is the text that is shown. + /// public string Label { get; } - [JsonProperty("emoji")] + /// + /// A that will be displayed with this button. + /// public IEmote Emote { get; } - [JsonProperty("custom_id")] + /// + /// A unique id that will be sent with a . This is how you know what button was pressed. + /// public string CustomId { get; } - [JsonProperty("url")] + /// + /// A URL for a button. + /// + /// + /// You cannot have a button with a URL and a CustomId. + /// public string Url { get; } - [JsonProperty("disabled")] + /// + /// Whether this button is disabled or not. + /// public bool Disabled { get; } internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool disabled) @@ -39,5 +57,7 @@ namespace Discord this.Url = url; this.Disabled = disabled; } + + } } 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 147667e72..a7da0d936 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs @@ -6,10 +6,19 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a builder for creating a . + /// public class ComponentBuilder { + /// + /// The max amount of rows a message can have. + /// public const int MaxActionRowCount = 5; + /// + /// Gets or sets the Action Rows for this Component Builder. + /// public List ActionRows { get => _actionRows; @@ -25,11 +34,22 @@ namespace Discord private List _actionRows { get; set; } + /// + /// Adds a button to the specified row. + /// + /// The label text for the newly added button. + /// The style of this newly added button. + /// A to be used with this button. + /// The custom id of the newly added button. + /// A URL to be used only if the is a Link. + /// Whether or not the newly created button is disabled. + /// The row the button should be placed on. + /// The current builder. public ComponentBuilder WithButton( string label, + string customId, ButtonStyle style = ButtonStyle.Primary, IEmote emote = null, - string customId = null, string url = null, bool disabled = false, int row = 0) @@ -45,9 +65,20 @@ namespace Discord return this.WithButton(button, row); } + /// + /// Adds a button to the first row. + /// + /// The button to add to the first row. + /// The current builder. public ComponentBuilder WithButton(ButtonBuilder button) => this.WithButton(button, 0); + /// + /// Adds a button to the specified row. + /// + /// The button to add. + /// The row to add the button. + /// The current builder. public ComponentBuilder WithButton(ButtonBuilder button, int row) { var builtButton = button.Build(); @@ -75,6 +106,10 @@ namespace Discord return this; } + /// + /// Builds this builder into a used to send your components. + /// + /// A that can be sent with public MessageComponent Build() { if (this._actionRows != null) @@ -84,10 +119,20 @@ namespace Discord } } + /// + /// Represents a class used to build Action rows. + /// public class ActionRowBuilder { + /// + /// The max amount of child components this row can hold. + /// public const int MaxChildCount = 5; - public List Components + + /// + /// Gets or sets the components inside this row. + /// + public List Components { get => _components; set @@ -99,33 +144,70 @@ namespace Discord } } - private List _components { get; set; } + private List _components { get; set; } - public ActionRowBuilder WithComponents(List components) + /// + /// Adds a list of components to the current row. + /// + /// The list of components to add. + /// The current builder. + public ActionRowBuilder WithComponents(List components) { this.Components = components; return this; } - public ActionRowBuilder WithComponent(IMessageComponent component) + /// + /// Adds a component at the end of the current row. + /// + /// The component to add. + /// The current builder. + public ActionRowBuilder WithComponent(ButtonComponent component) { if (this.Components == null) - this.Components = new List(); + this.Components = new List(); this.Components.Add(component); return this; } + /// + /// Builds the current builder to a that can be used within a + /// + /// A that can be used within a + /// cannot be null. + /// There must be at least 1 component in a row. public ActionRowComponent Build() - => new ActionRowComponent(this._components); + { + if (this.Components == null) + throw new ArgumentNullException($"{nameof(Components)} cannot be null!"); + + if (this.Components.Count == 0) + throw new ArgumentException("There must be at least 1 component in a row"); + + return new ActionRowComponent(this._components); + } } + /// + /// Represents a class used to build 's. + /// public class ButtonBuilder { + /// + /// The max length of a . + /// public const int MaxLabelLength = 80; + + /// + /// The max length of a . + /// public const int MaxCustomIdLength = 100; + /// + /// Gets or sets the label of the current button. + /// public string Label { get => _label; @@ -139,6 +221,9 @@ namespace Discord } } + /// + /// Gets or sets the custom id of the current button. + /// public string CustomId { get => _customId; @@ -151,15 +236,36 @@ namespace Discord } } + /// + /// Gets or sets the of the current button. + /// public ButtonStyle Style { get; set; } + + /// + /// Gets or sets the of the current button. + /// public IEmote Emote { get; set; } + + /// + /// Gets or sets the url of the current button. + /// public string Url { get; set; } + + /// + /// Gets or sets whether the current button is disabled. + /// public bool Disabled { get; set; } private string _label; private string _customId; + /// + /// Creates a button with the style. + /// + /// The label to use on the newly created link button. + /// The url for this link button to go to. + /// A builder with the newly created button. public static ButtonBuilder CreateLinkButton(string label, string url) { var builder = new ButtonBuilder() @@ -170,6 +276,12 @@ namespace Discord return builder; } + /// + /// Creates a button with the style. + /// + /// The label for this danger button. + /// The custom id for this danger button. + /// A builder with the newly created button. public static ButtonBuilder CreateDangerButton(string label, string customId) { var builder = new ButtonBuilder() @@ -180,6 +292,12 @@ namespace Discord return builder; } + /// + /// Creates a button with the style. + /// + /// The label for this primary button. + /// The custom id for this primary button. + /// A builder with the newly created button. public static ButtonBuilder CreatePrimaryButton(string label, string customId) { var builder = new ButtonBuilder() @@ -190,6 +308,12 @@ namespace Discord return builder; } + /// + /// Creates a button with the style. + /// + /// The label for this secondary button. + /// The custom id for this secondary button. + /// A builder with the newly created button. public static ButtonBuilder CreateSecondaryButton(string label, string customId) { var builder = new ButtonBuilder() @@ -200,6 +324,12 @@ namespace Discord return builder; } + /// + /// Creates a button with the style. + /// + /// The label for this success button. + /// The custom id for this success button. + /// A builder with the newly created button. public static ButtonBuilder CreateSuccessButton(string label, string customId) { var builder = new ButtonBuilder() @@ -210,41 +340,78 @@ namespace Discord return builder; } + /// + /// Sets the current buttons label to the specified text. + /// + /// The text for the label + /// The current builder. public ButtonBuilder WithLabel(string label) { this.Label = label; return this; } + /// + /// Sets the current buttons style. + /// + /// The style for this builders button. + /// The current builder. public ButtonBuilder WithStyle(ButtonStyle style) { this.Style = style; return this; } + /// + /// Sets the current buttons emote. + /// + /// The emote to use for the current button. + /// The current builder. public ButtonBuilder WithEmote(IEmote emote) { this.Emote = emote; return this; } + /// + /// Sets the current buttons url. + /// + /// The url to use for the current button. + /// The current builder. public ButtonBuilder WithUrl(string url) { this.Url = url; return this; } + /// + /// Sets the custom id of the current button. + /// + /// The id to use for the current button. + /// The current builder. public ButtonBuilder WithCustomId(string id) { this.CustomId = id; return this; } + + /// + /// Sets whether the current button is disabled. + /// + /// Whether the current button is disabled or not. + /// The current builder. public ButtonBuilder WithDisabled(bool disabled) { this.Disabled = disabled; return this; } + /// + /// Builds this builder into a to be used in a . + /// + /// A to be used in a . + /// A button cannot contain a URL and a CustomId. + /// A button must have an Emote or a label. public ButtonComponent Build() { if (string.IsNullOrEmpty(this.Label) && this.Emote == null) @@ -255,7 +422,8 @@ namespace Discord if (this.Style == ButtonStyle.Link && !string.IsNullOrEmpty(this.CustomId)) this.CustomId = null; - else if (!string.IsNullOrEmpty(this.Url)) + + else if (this.Style != ButtonStyle.Link && !string.IsNullOrEmpty(this.Url)) // Thanks 𝑴𝒓π‘ͺπ’‚π’Œπ’†π‘Ίπ’π’‚π’šπ’†π’“ :D this.Url = null; return new ButtonComponent(this.Style, this.Label, this.Emote, this.CustomId, this.Url, this.Disabled); diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs index 1ee8dd953..82df2550e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs @@ -6,19 +6,25 @@ using System.Threading.Tasks; namespace Discord { + /// + /// Represents a component object used to send components with messages. + /// public class MessageComponent { - public IReadOnlyCollection Components { get; } + /// + /// The components to be used in a message. + /// + public IReadOnlyCollection Components { get; } internal MessageComponent(List components) { this.Components = components; } + /// + /// Returns a empty . + /// internal static MessageComponent Empty => new MessageComponent(new List()); - - internal IMessageComponent[] ToModel() - => this.Components.ToArray(); } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index b632d6a18..1e4846a94 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -21,5 +21,10 @@ namespace Discord /// Gets or sets the embed the message should display. /// public Optional Embed { get; set; } + + /// + /// Gets or sets the components for this message. + /// + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs new file mode 100644 index 000000000..7fddac1cf --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class ActionRowComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("components")] + public List Components { get; set; } + + internal ActionRowComponent() { } + internal ActionRowComponent(Discord.ActionRowComponent c) + { + this.Type = c.Type; + this.Components = c.Components?.Select(x => new ButtonComponent(x)).ToList(); + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs new file mode 100644 index 000000000..775f78101 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class ButtonComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("style")] + public ButtonStyle Style { get; set; } + + [JsonProperty("label")] + public Optional Label { get; set; } + + [JsonProperty("emoji")] + public Optional Emote { get; set; } + + [JsonProperty("custom_id")] + public Optional CustomId { get; set; } + + [JsonProperty("url")] + public Optional Url { get; set; } + + [JsonProperty("disabled")] + public Optional Disabled { get; set; } + + + public ButtonComponent() { } + + public ButtonComponent(Discord.ButtonComponent c) + { + this.Type = c.Type; + this.Style = c.Style; + this.Label = c.Label; + this.CustomId = c.CustomId; + this.Url = c.Url; + this.Disabled = c.Disabled; + + if (c.Emote != null) + { + if (c.Emote is Emote e) + { + this.Emote = new Emoji() + { + Name = e.Name, + Animated = e.Animated, + Id = e.Id, + }; + } + else + { + this.Emote = new Emoji() + { + Name = c.Emote.Name + }; + } + } + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs index f5ce75250..3f7cf93e0 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionApplicationCommandCallbackData.cs @@ -26,7 +26,7 @@ namespace Discord.API public Optional Flags { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } public InteractionApplicationCommandCallbackData() { } public InteractionApplicationCommandCallbackData(string text) diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index c3d88eedb..4ce8956eb 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -59,6 +59,6 @@ namespace Discord.API [JsonProperty("referenced_message")] public Optional ReferencedMessage { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index 703101313..0d6710a03 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -25,7 +25,7 @@ namespace Discord.API.Rest public Optional MessageReference { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } public CreateMessageParams(string content) { Content = content; diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index c668ee484..528bf8e07 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -31,7 +31,7 @@ namespace Discord.API.Rest public Optional Flags { get; set; } [JsonProperty("components")] - public Optional Components { get; set; } + public Optional Components { get; set; } public CreateWebhookMessageParams(string content) { diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index fdff4de15..195525afc 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -1,4 +1,4 @@ -ο»Ώ#pragma warning disable CS1591 +#pragma warning disable CS1591 using Newtonsoft.Json; namespace Discord.API.Rest @@ -10,5 +10,7 @@ namespace Discord.API.Rest public Optional Content { get; set; } [JsonProperty("embed")] public Optional Embed { get; set; } + [JsonProperty("components")] + public Optional Components { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index 3b78489aa..27336a11a 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -21,7 +21,7 @@ namespace Discord.API.Rest public Optional Embed { get; set; } public Optional AllowedMentions { get; set; } public Optional MessageReference { get; set; } - public Optional MessageComponent { get; set; } + public Optional MessageComponent { get; set; } public bool IsSpoiler { get; set; } = false; public UploadFileParams(Stream file) diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 66a25c918..3efa4dda7 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -9,9 +9,11 @@ netstandard2.0;netstandard2.1 Temporary.png https://github.com/Discord-Net-Labs/Discord.Net-Labs - 2.3.2 + 2.3.5 Discord.Net.Labs.Rest https://github.com/Discord-Net-Labs/Discord.Net-Labs + 2.3.4 + 2.3.4 diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 171a0eaf0..da3410807 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -944,7 +944,7 @@ namespace Discord.API public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) { if(response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) - Preconditions.AtMost(response.Data.Value.Content.Value.Length, 2000, nameof(response.Data.Value.Content)); + Preconditions.AtMost(response.Data.Value.Content.Value?.Length ?? 0, 2000, nameof(response.Data.Value.Content)); options = RequestOptions.CreateOrClone(options); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 69fcd2cb4..894b820c8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -221,7 +221,7 @@ namespace Discord.Rest } } - var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.ToModel() }; + var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel(), AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } @@ -281,7 +281,7 @@ namespace Discord.Rest } } - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional.Unspecified, MessageComponent = component?.ToModel() ?? Optional.Unspecified, IsSpoiler = isSpoiler }; + var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional.Unspecified, MessageComponent = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, IsSpoiler = isSpoiler }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index b86a5dbf3..b66ac1a01 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -41,7 +41,8 @@ namespace Discord.Rest var apiArgs = new API.Rest.ModifyMessageParams { Content = args.Content, - Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create() + Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create(), + Components = args?.Components.GetValueOrDefault()?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 068ed1511..90bdc8636 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -129,9 +129,16 @@ namespace Discord.Rest if (model.Components.IsSpecified) { - Components = model.Components.Value.Select(x => - (x as Newtonsoft.Json.Linq.JToken).ToObject() - ).ToList(); + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(x => + new ButtonComponent( + x.Style, + x.Label.GetValueOrDefault(), + x.Emote.IsSpecified ? x.Emote.Value.Id.HasValue ? new Emote(x.Emote.Value.Id.Value, x.Emote.Value.Name, x.Emote.Value.Animated.GetValueOrDefault()) : new Emoji(x.Emote.Value.Name) : null, + x.CustomId.GetValueOrDefault(), + x.Url.GetValueOrDefault(), + x.Disabled.GetValueOrDefault()) + ).ToList() + )).ToList(); } else Components = new List(); diff --git a/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs b/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs index 8c451a552..6e9ebb4fb 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs @@ -13,6 +13,9 @@ namespace Discord.API.Gateway [JsonProperty("id")] public ulong Id { get; set; } + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + [JsonProperty("type")] public InteractionType Type { get; set; } @@ -20,18 +23,25 @@ namespace Discord.API.Gateway public Optional Data { get; set; } [JsonProperty("guild_id")] - public ulong GuildId { get; set; } + public Optional GuildId { get; set; } [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + public Optional ChannelId { get; set; } [JsonProperty("member")] - public GuildMember Member { get; set; } + public Optional Member { get; set; } + + [JsonProperty("user")] + public Optional User { get; set; } [JsonProperty("token")] public string Token { get; set; } [JsonProperty("version")] public int Version { get; set; } + + [JsonProperty("message")] + public Optional Message { get; set; } + } } diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index de066aefe..8ee51c0f0 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 - 2.3.2 + 2.3.4 https://github.com/Discord-Net-Labs/Discord.Net-Labs https://github.com/Discord-Net-Labs/Discord.Net-Labs Temporary.png diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 115367e6f..df8e79638 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1785,26 +1785,33 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) + if (data.Member.IsSpecified && data.ChannelId.IsSpecified) { - var guild = channel.Guild; - if (!guild.IsSynced) + if (State.GetChannel(data.ChannelId.Value) is SocketGuildChannel channel) { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var guild = channel.Guild; + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var interaction = SocketInteraction.Create(this, data); + var interaction = SocketInteraction.Create(this, data); - if (this.AlwaysAcknowledgeInteractions) - await interaction.AcknowledgeAsync().ConfigureAwait(false); + if (this.AlwaysAcknowledgeInteractions) + await interaction.AcknowledgeAsync().ConfigureAwait(false); - await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); + await TimedInvokeAsync(_interactionCreatedEvent, nameof(InteractionCreated), interaction).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); + return; + } } else { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + // DM TODO } } break; 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 a05fae3cc..0b077fda5 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -10,10 +10,21 @@ using Discord.Rest; namespace Discord.WebSocket { + /// + /// Represents a Websocket-based interaction type for Message Components. + /// public class SocketMessageComponent : SocketInteraction { + /// + /// The data received with this interaction, contains the button that was clicked. + /// new public SocketMessageComponentData Data { get; } + /// + /// The message that contained the trigger for this interaction. + /// + public SocketMessage Message { get; private set; } + internal SocketMessageComponent(DiscordSocketClient client, Model model) : base(client, model.Id) { @@ -22,6 +33,8 @@ namespace Discord.WebSocket : null; this.Data = new SocketMessageComponentData(dataModel); + + } new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model) @@ -31,6 +44,23 @@ namespace Discord.WebSocket return entity; } + internal override void Update(Model model) + { + base.Update(model); + + if (model.Message.IsSpecified) + { + if (this.Message == null) + { + this.Message = SocketMessage.Create(this.Discord, this.Discord.State, this.User, this.Channel, model.Message.Value); + } + else + { + this.Message.Update(this.Discord.State, model.Message.Value); + } + } + } + /// /// Responds to an Interaction. /// @@ -51,7 +81,6 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The parameters provided were invalid or the token was invalid. - public override async Task RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType type = InteractionResponseType.ChannelMessageWithSource, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null) { @@ -94,7 +123,7 @@ namespace Discord.WebSocket ? new API.Embed[] { embed.ToModel() } : Optional.Unspecified, TTS = isTTS, - Components = component?.ToModel() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified } }; @@ -134,7 +163,7 @@ namespace Discord.WebSocket Embeds = embed != null ? new API.Embed[] { embed.ToModel() } : Optional.Unspecified, - Components = component?.ToModel() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; if (ephemeral) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs index fad959e1d..8477432c7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs @@ -7,6 +7,9 @@ using Model = Discord.API.MessageComponentInteractionData; namespace Discord.WebSocket { + /// + /// Represents the data sent with a . + /// public class SocketMessageComponentData { /// 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 c3f760ebc..69b18c44d 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -21,7 +21,7 @@ namespace Discord.WebSocket (model.Data.Value as JToken).ToObject() : null; - Data = SocketSlashCommandData.Create(client, dataModel, model.GuildId); + Data = SocketSlashCommandData.Create(client, dataModel, model.Id); } new internal static SocketInteraction Create(DiscordSocketClient client, Model model) @@ -37,7 +37,7 @@ namespace Discord.WebSocket (model.Data.Value as JToken).ToObject() : null; - this.Data.Update(data, model.GuildId); + this.Data.Update(data); base.Update(model); } @@ -107,7 +107,7 @@ namespace Discord.WebSocket ? new API.Embed[] { embed.ToModel() } : Optional.Unspecified, TTS = isTTS, - Components = component?.ToModel() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified } }; @@ -146,7 +146,7 @@ namespace Discord.WebSocket Embeds = embed != null ? new API.Embed[] { embed.ToModel() } : Optional.Unspecified, - Components = component?.ToModel() ?? Optional.Unspecified + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified }; if (ephemeral) 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 eebc18a9a..3ef6678e1 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandData.cs @@ -15,27 +15,24 @@ namespace Discord.WebSocket /// public IReadOnlyCollection Options { get; private set; } - private ulong guildId; - internal SocketSlashCommandData(DiscordSocketClient client, ulong id) : base(client, id) { } - internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong guildId) + internal static SocketSlashCommandData Create(DiscordSocketClient client, Model model, ulong id) { var entity = new SocketSlashCommandData(client, model.Id); - entity.Update(model, guildId); + entity.Update(model); return entity; } - internal void Update(Model model, ulong guildId) + internal void Update(Model model) { this.Name = model.Name; - this.guildId = guildId; this.Options = model.Options.Any() - ? model.Options.Select(x => new SocketSlashCommandDataOption(x, this.Discord, guildId)).ToImmutableArray() + ? model.Options.Select(x => new SocketSlashCommandDataOption(x, this.Discord)).ToImmutableArray() : null; } 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 e3f7d520d..e7ff1c5d5 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommandDataOption.cs @@ -22,18 +22,16 @@ namespace Discord.WebSocket public IReadOnlyCollection Options { get; private set; } private DiscordSocketClient discord; - private ulong guild; internal SocketSlashCommandDataOption() { } - internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord, ulong guild) + internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord) { this.Name = model.Name; this.Value = model.Value.IsSpecified ? model.Value.Value : null; this.discord = discord; - this.guild = guild; this.Options = model.Options.Any() - ? model.Options.Select(x => new SocketSlashCommandDataOption(x, discord, guild)).ToImmutableArray() + ? model.Options.Select(x => new SocketSlashCommandDataOption(x, discord)).ToImmutableArray() : null; } @@ -49,7 +47,7 @@ namespace Discord.WebSocket { if (option.Value is ulong id) { - var guild = option.discord.GetGuild(option.guild); + var guild = option.discord.GetGuild(id); if (guild == null) return null; @@ -64,7 +62,7 @@ namespace Discord.WebSocket { if (option.Value is ulong id) { - var guild = option.discord.GetGuild(option.guild); + var guild = option.discord.GetGuild(id); if (guild == null) return null; @@ -79,7 +77,7 @@ namespace Discord.WebSocket { if(option.Value is ulong id) { - var guild = option.discord.GetGuild(option.guild); + var guild = option.discord.GetGuild(id); if (guild == null) return null; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 96f0bca10..62f0debef 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -14,21 +14,14 @@ namespace Discord.WebSocket public abstract class SocketInteraction : SocketEntity, IDiscordInteraction { /// - /// The this interaction was used in. + /// The this interaction was used in. /// - public SocketGuild Guild - => Discord.GetGuild(GuildId); + public ISocketMessageChannel Channel { get; private set; } /// - /// The this interaction was used in. + /// The who triggered this interaction. /// - public SocketTextChannel Channel - => Guild.GetTextChannel(ChannelId); - - /// - /// The who triggered this interaction. - /// - public SocketGuildUser User { get; private set; } + public SocketUser User { get; private set; } /// /// The type of this interaction. @@ -58,9 +51,8 @@ namespace Discord.WebSocket public bool IsValidToken => CheckToken(); - private ulong GuildId { get; set; } - private ulong ChannelId { get; set; } - private ulong UserId { get; set; } + private ulong? GuildId { get; set; } + private ulong? ChannelId { get; set; } internal SocketInteraction(DiscordSocketClient client, ulong id) : base(client, id) @@ -83,15 +75,35 @@ namespace Discord.WebSocket ? model.Data.Value : null; - this.GuildId = model.GuildId; - this.ChannelId = model.ChannelId; + this.GuildId = model.GuildId.ToNullable(); + this.ChannelId = model.ChannelId.ToNullable(); this.Token = model.Token; this.Version = model.Version; - this.UserId = model.Member.User.Id; this.Type = model.Type; if (this.User == null) - this.User = SocketGuildUser.Create(this.Guild, Discord.State, model.Member); // Change from getter. + { + if (model.Member.IsSpecified && model.GuildId.IsSpecified) + { + this.User = SocketGuildUser.Create(Discord.State.GetGuild(this.GuildId.Value), Discord.State, model.Member.Value); + } + else + { + this.User = SocketGlobalUser.Create(this.Discord, this.Discord.State, model.User.Value); + } + } + + if (this.Channel == null) + { + if (model.ChannelId.IsSpecified) + { + this.Channel = Discord.State.GetChannel(model.ChannelId.Value) as ISocketMessageChannel; + } + else + { + this.Channel = Discord.State.GetDMChannel(this.User.Id); + } + } } /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 5df2d1acb..e201d3e97 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -162,9 +162,16 @@ namespace Discord.WebSocket if (model.Components.IsSpecified) { - Components = model.Components.Value.Select(x => - (x as Newtonsoft.Json.Linq.JToken).ToObject() - ).ToList(); + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(x => + new ButtonComponent( + x.Style, + x.Label.GetValueOrDefault(), + x.Emote.IsSpecified ? x.Emote.Value.Id.HasValue ? new Emote(x.Emote.Value.Id.Value, x.Emote.Value.Name, x.Emote.Value.Animated.GetValueOrDefault()) : new Emoji(x.Emote.Value.Name) : null, + x.CustomId.GetValueOrDefault(), + x.Url.GetValueOrDefault(), + x.Disabled.GetValueOrDefault()) + ).ToList() + )).ToList(); } else Components = new List(); diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 2c2e7b56b..811277373 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -2,7 +2,7 @@ Discord.Net.Labs - 2.3.1$suffix$ + 2.3.3$suffix$ Discord.Net Labs Discord.Net Contributors quinchs @@ -14,23 +14,23 @@ https://avatars.githubusercontent.com/u/84047264 - - - + + + - - - + + + - - - + + +