| @@ -8,7 +8,7 @@ | |||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <PackageId>Discord.Net.Labs.Core</PackageId> | <PackageId>Discord.Net.Labs.Core</PackageId> | ||||
| <Version>2.3.3</Version> | |||||
| <Version>2.3.4</Version> | |||||
| <Product>Discord.Net.Labs.Core</Product> | <Product>Discord.Net.Labs.Core</Product> | ||||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | ||||
| <PackageIcon>Temporary.png</PackageIcon> | <PackageIcon>Temporary.png</PackageIcon> | ||||
| @@ -7,16 +7,21 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a <see cref="IMessageComponent"/> Row for child components to live in. | |||||
| /// </summary> | |||||
| public class ActionRowComponent : IMessageComponent | public class ActionRowComponent : IMessageComponent | ||||
| { | { | ||||
| [JsonProperty("type")] | |||||
| /// <inheritdoc/> | |||||
| public ComponentType Type { get; } = ComponentType.ActionRow; | public ComponentType Type { get; } = ComponentType.ActionRow; | ||||
| [JsonProperty("components")] | |||||
| public IReadOnlyCollection<IMessageComponent> Components { get; internal set; } | |||||
| /// <summary> | |||||
| /// The child components in this row. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<ButtonComponent> Components { get; internal set; } | |||||
| internal ActionRowComponent() { } | internal ActionRowComponent() { } | ||||
| internal ActionRowComponent(IReadOnlyCollection<IMessageComponent> components) | |||||
| internal ActionRowComponent(List<ButtonComponent> components) | |||||
| { | { | ||||
| this.Components = components; | this.Components = components; | ||||
| } | } | ||||
| @@ -7,27 +7,45 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a <see cref="IMessageComponent"/> Button. | |||||
| /// </summary> | |||||
| public class ButtonComponent : IMessageComponent | public class ButtonComponent : IMessageComponent | ||||
| { | { | ||||
| [JsonProperty("type")] | |||||
| /// <inheritdoc/> | |||||
| public ComponentType Type { get; } = ComponentType.Button; | public ComponentType Type { get; } = ComponentType.Button; | ||||
| [JsonProperty("style")] | |||||
| /// <summary> | |||||
| /// The <see cref="ButtonStyle"/> of this button, example buttons with each style can be found <see href="https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png">Here</see>. | |||||
| /// </summary> | |||||
| public ButtonStyle Style { get; } | public ButtonStyle Style { get; } | ||||
| [JsonProperty("label")] | |||||
| /// <summary> | |||||
| /// The label of the button, this is the text that is shown. | |||||
| /// </summary> | |||||
| public string Label { get; } | public string Label { get; } | ||||
| [JsonProperty("emoji")] | |||||
| /// <summary> | |||||
| /// A <see cref="IEmote"/> that will be displayed with this button. | |||||
| /// </summary> | |||||
| public IEmote Emote { get; } | public IEmote Emote { get; } | ||||
| [JsonProperty("custom_id")] | |||||
| /// <summary> | |||||
| /// A unique id that will be sent with a <see cref="IDiscordInteraction"/>. This is how you know what button was pressed. | |||||
| /// </summary> | |||||
| public string CustomId { get; } | public string CustomId { get; } | ||||
| [JsonProperty("url")] | |||||
| /// <summary> | |||||
| /// A URL for a <see cref="ButtonStyle.Link"/> button. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// You cannot have a button with a <b>URL</b> and a <b>CustomId</b>. | |||||
| /// </remarks> | |||||
| public string Url { get; } | public string Url { get; } | ||||
| [JsonProperty("disabled")] | |||||
| /// <summary> | |||||
| /// Whether this button is disabled or not. | |||||
| /// </summary> | |||||
| public bool Disabled { get; } | public bool Disabled { get; } | ||||
| internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool disabled) | internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool disabled) | ||||
| @@ -39,5 +57,7 @@ namespace Discord | |||||
| this.Url = url; | this.Url = url; | ||||
| this.Disabled = disabled; | this.Disabled = disabled; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -6,10 +6,19 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a builder for creating a <see cref="MessageComponent"/>. | |||||
| /// </summary> | |||||
| public class ComponentBuilder | public class ComponentBuilder | ||||
| { | { | ||||
| /// <summary> | |||||
| /// The max amount of rows a message can have. | |||||
| /// </summary> | |||||
| public const int MaxActionRowCount = 5; | public const int MaxActionRowCount = 5; | ||||
| /// <summary> | |||||
| /// Gets or sets the Action Rows for this Component Builder. | |||||
| /// </summary> | |||||
| public List<ActionRowBuilder> ActionRows | public List<ActionRowBuilder> ActionRows | ||||
| { | { | ||||
| get => _actionRows; | get => _actionRows; | ||||
| @@ -25,11 +34,22 @@ namespace Discord | |||||
| private List<ActionRowBuilder> _actionRows { get; set; } | private List<ActionRowBuilder> _actionRows { get; set; } | ||||
| /// <summary> | |||||
| /// Adds a button to the specified row. | |||||
| /// </summary> | |||||
| /// <param name="label">The label text for the newly added button.</param> | |||||
| /// <param name="style">The style of this newly added button.</param> | |||||
| /// <param name="emote">A <see cref="IEmote"/> to be used with this button.</param> | |||||
| /// <param name="customId">The custom id of the newly added button.</param> | |||||
| /// <param name="url">A URL to be used only if the <see cref="ButtonStyle"/> is a Link.</param> | |||||
| /// <param name="disabled">Whether or not the newly created button is disabled.</param> | |||||
| /// <param name="row">The row the button should be placed on.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ComponentBuilder WithButton( | public ComponentBuilder WithButton( | ||||
| string label, | string label, | ||||
| string customId, | |||||
| ButtonStyle style = ButtonStyle.Primary, | ButtonStyle style = ButtonStyle.Primary, | ||||
| IEmote emote = null, | IEmote emote = null, | ||||
| string customId = null, | |||||
| string url = null, | string url = null, | ||||
| bool disabled = false, | bool disabled = false, | ||||
| int row = 0) | int row = 0) | ||||
| @@ -45,9 +65,20 @@ namespace Discord | |||||
| return this.WithButton(button, row); | return this.WithButton(button, row); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Adds a button to the first row. | |||||
| /// </summary> | |||||
| /// <param name="button">The button to add to the first row.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ComponentBuilder WithButton(ButtonBuilder button) | public ComponentBuilder WithButton(ButtonBuilder button) | ||||
| => this.WithButton(button, 0); | => this.WithButton(button, 0); | ||||
| /// <summary> | |||||
| /// Adds a button to the specified row. | |||||
| /// </summary> | |||||
| /// <param name="button">The button to add.</param> | |||||
| /// <param name="row">The row to add the button.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ComponentBuilder WithButton(ButtonBuilder button, int row) | public ComponentBuilder WithButton(ButtonBuilder button, int row) | ||||
| { | { | ||||
| var builtButton = button.Build(); | var builtButton = button.Build(); | ||||
| @@ -75,6 +106,10 @@ namespace Discord | |||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Builds this builder into a <see cref="MessageComponent"/> used to send your components. | |||||
| /// </summary> | |||||
| /// <returns>A <see cref="MessageComponent"/> that can be sent with <see cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent)"/></returns> | |||||
| public MessageComponent Build() | public MessageComponent Build() | ||||
| { | { | ||||
| if (this._actionRows != null) | if (this._actionRows != null) | ||||
| @@ -84,10 +119,20 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Represents a class used to build Action rows. | |||||
| /// </summary> | |||||
| public class ActionRowBuilder | public class ActionRowBuilder | ||||
| { | { | ||||
| /// <summary> | |||||
| /// The max amount of child components this row can hold. | |||||
| /// </summary> | |||||
| public const int MaxChildCount = 5; | public const int MaxChildCount = 5; | ||||
| public List<IMessageComponent> Components | |||||
| /// <summary> | |||||
| /// Gets or sets the components inside this row. | |||||
| /// </summary> | |||||
| public List<ButtonComponent> Components | |||||
| { | { | ||||
| get => _components; | get => _components; | ||||
| set | set | ||||
| @@ -99,33 +144,70 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| private List<IMessageComponent> _components { get; set; } | |||||
| private List<ButtonComponent> _components { get; set; } | |||||
| public ActionRowBuilder WithComponents(List<IMessageComponent> components) | |||||
| /// <summary> | |||||
| /// Adds a list of components to the current row. | |||||
| /// </summary> | |||||
| /// <param name="components">The list of components to add.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithComponents(List<ButtonComponent> components) | |||||
| { | { | ||||
| this.Components = components; | this.Components = components; | ||||
| return this; | return this; | ||||
| } | } | ||||
| public ActionRowBuilder WithComponent(IMessageComponent component) | |||||
| /// <summary> | |||||
| /// Adds a component at the end of the current row. | |||||
| /// </summary> | |||||
| /// <param name="component">The component to add.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ActionRowBuilder WithComponent(ButtonComponent component) | |||||
| { | { | ||||
| if (this.Components == null) | if (this.Components == null) | ||||
| this.Components = new List<IMessageComponent>(); | |||||
| this.Components = new List<ButtonComponent>(); | |||||
| this.Components.Add(component); | this.Components.Add(component); | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> | |||||
| /// </summary> | |||||
| /// <returns>A <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/></returns> | |||||
| /// <exception cref="ArgumentNullException"><see cref="Components"/> cannot be null.</exception> | |||||
| /// <exception cref="ArgumentException">There must be at least 1 component in a row.</exception> | |||||
| public ActionRowComponent Build() | 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); | |||||
| } | |||||
| } | } | ||||
| /// <summary> | |||||
| /// Represents a class used to build <see cref="ButtonComponent"/>'s. | |||||
| /// </summary> | |||||
| public class ButtonBuilder | public class ButtonBuilder | ||||
| { | { | ||||
| /// <summary> | |||||
| /// The max length of a <see cref="ButtonComponent.Label"/>. | |||||
| /// </summary> | |||||
| public const int MaxLabelLength = 80; | public const int MaxLabelLength = 80; | ||||
| /// <summary> | |||||
| /// The max length of a <see cref="ButtonComponent.CustomId"/>. | |||||
| /// </summary> | |||||
| public const int MaxCustomIdLength = 100; | public const int MaxCustomIdLength = 100; | ||||
| /// <summary> | |||||
| /// Gets or sets the label of the current button. | |||||
| /// </summary> | |||||
| public string Label | public string Label | ||||
| { | { | ||||
| get => _label; | get => _label; | ||||
| @@ -139,6 +221,9 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets or sets the custom id of the current button. | |||||
| /// </summary> | |||||
| public string CustomId | public string CustomId | ||||
| { | { | ||||
| get => _customId; | get => _customId; | ||||
| @@ -151,15 +236,36 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Gets or sets the <see cref="ButtonStyle"/> of the current button. | |||||
| /// </summary> | |||||
| public ButtonStyle Style { get; set; } | public ButtonStyle Style { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the <see cref="IEmote"/> of the current button. | |||||
| /// </summary> | |||||
| public IEmote Emote { get; set; } | public IEmote Emote { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the url of the current button. | |||||
| /// </summary> | |||||
| public string Url { get; set; } | public string Url { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets whether the current button is disabled. | |||||
| /// </summary> | |||||
| public bool Disabled { get; set; } | public bool Disabled { get; set; } | ||||
| private string _label; | private string _label; | ||||
| private string _customId; | private string _customId; | ||||
| /// <summary> | |||||
| /// Creates a button with the <see cref="ButtonStyle.Link"/> style. | |||||
| /// </summary> | |||||
| /// <param name="label">The label to use on the newly created link button.</param> | |||||
| /// <param name="url">The url for this link button to go to.</param> | |||||
| /// <returns>A builder with the newly created button.</returns> | |||||
| public static ButtonBuilder CreateLinkButton(string label, string url) | public static ButtonBuilder CreateLinkButton(string label, string url) | ||||
| { | { | ||||
| var builder = new ButtonBuilder() | var builder = new ButtonBuilder() | ||||
| @@ -170,6 +276,12 @@ namespace Discord | |||||
| return builder; | return builder; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Creates a button with the <see cref="ButtonStyle.Danger"/> style. | |||||
| /// </summary> | |||||
| /// <param name="label">The label for this danger button.</param> | |||||
| /// <param name="customId">The custom id for this danger button.</param> | |||||
| /// <returns>A builder with the newly created button.</returns> | |||||
| public static ButtonBuilder CreateDangerButton(string label, string customId) | public static ButtonBuilder CreateDangerButton(string label, string customId) | ||||
| { | { | ||||
| var builder = new ButtonBuilder() | var builder = new ButtonBuilder() | ||||
| @@ -180,6 +292,12 @@ namespace Discord | |||||
| return builder; | return builder; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Creates a button with the <see cref="ButtonStyle.Primary"/> style. | |||||
| /// </summary> | |||||
| /// <param name="label">The label for this primary button.</param> | |||||
| /// <param name="customId">The custom id for this primary button.</param> | |||||
| /// <returns>A builder with the newly created button.</returns> | |||||
| public static ButtonBuilder CreatePrimaryButton(string label, string customId) | public static ButtonBuilder CreatePrimaryButton(string label, string customId) | ||||
| { | { | ||||
| var builder = new ButtonBuilder() | var builder = new ButtonBuilder() | ||||
| @@ -190,6 +308,12 @@ namespace Discord | |||||
| return builder; | return builder; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Creates a button with the <see cref="ButtonStyle.Secondary"/> style. | |||||
| /// </summary> | |||||
| /// <param name="label">The label for this secondary button.</param> | |||||
| /// <param name="customId">The custom id for this secondary button.</param> | |||||
| /// <returns>A builder with the newly created button.</returns> | |||||
| public static ButtonBuilder CreateSecondaryButton(string label, string customId) | public static ButtonBuilder CreateSecondaryButton(string label, string customId) | ||||
| { | { | ||||
| var builder = new ButtonBuilder() | var builder = new ButtonBuilder() | ||||
| @@ -200,6 +324,12 @@ namespace Discord | |||||
| return builder; | return builder; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Creates a button with the <see cref="ButtonStyle.Success"/> style. | |||||
| /// </summary> | |||||
| /// <param name="label">The label for this success button.</param> | |||||
| /// <param name="customId">The custom id for this success button.</param> | |||||
| /// <returns>A builder with the newly created button.</returns> | |||||
| public static ButtonBuilder CreateSuccessButton(string label, string customId) | public static ButtonBuilder CreateSuccessButton(string label, string customId) | ||||
| { | { | ||||
| var builder = new ButtonBuilder() | var builder = new ButtonBuilder() | ||||
| @@ -210,41 +340,78 @@ namespace Discord | |||||
| return builder; | return builder; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the current buttons label to the specified text. | |||||
| /// </summary> | |||||
| /// <param name="label">The text for the label</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithLabel(string label) | public ButtonBuilder WithLabel(string label) | ||||
| { | { | ||||
| this.Label = label; | this.Label = label; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the current buttons style. | |||||
| /// </summary> | |||||
| /// <param name="style">The style for this builders button.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithStyle(ButtonStyle style) | public ButtonBuilder WithStyle(ButtonStyle style) | ||||
| { | { | ||||
| this.Style = style; | this.Style = style; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the current buttons emote. | |||||
| /// </summary> | |||||
| /// <param name="emote">The emote to use for the current button.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithEmote(IEmote emote) | public ButtonBuilder WithEmote(IEmote emote) | ||||
| { | { | ||||
| this.Emote = emote; | this.Emote = emote; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the current buttons url. | |||||
| /// </summary> | |||||
| /// <param name="url">The url to use for the current button.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithUrl(string url) | public ButtonBuilder WithUrl(string url) | ||||
| { | { | ||||
| this.Url = url; | this.Url = url; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets the custom id of the current button. | |||||
| /// </summary> | |||||
| /// <param name="id">The id to use for the current button.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithCustomId(string id) | public ButtonBuilder WithCustomId(string id) | ||||
| { | { | ||||
| this.CustomId = id; | this.CustomId = id; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Sets whether the current button is disabled. | |||||
| /// </summary> | |||||
| /// <param name="disabled">Whether the current button is disabled or not.</param> | |||||
| /// <returns>The current builder.</returns> | |||||
| public ButtonBuilder WithDisabled(bool disabled) | public ButtonBuilder WithDisabled(bool disabled) | ||||
| { | { | ||||
| this.Disabled = disabled; | this.Disabled = disabled; | ||||
| return this; | return this; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Builds this builder into a <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>. | |||||
| /// </summary> | |||||
| /// <returns>A <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>.</returns> | |||||
| /// <exception cref="InvalidOperationException">A button cannot contain a URL and a CustomId.</exception> | |||||
| /// <exception cref="ArgumentException">A button must have an Emote or a label.</exception> | |||||
| public ButtonComponent Build() | public ButtonComponent Build() | ||||
| { | { | ||||
| if (string.IsNullOrEmpty(this.Label) && this.Emote == null) | if (string.IsNullOrEmpty(this.Label) && this.Emote == null) | ||||
| @@ -255,7 +422,8 @@ namespace Discord | |||||
| if (this.Style == ButtonStyle.Link && !string.IsNullOrEmpty(this.CustomId)) | if (this.Style == ButtonStyle.Link && !string.IsNullOrEmpty(this.CustomId)) | ||||
| this.CustomId = null; | this.CustomId = null; | ||||
| else if (!string.IsNullOrEmpty(this.Url)) | |||||
| else if (this.Style != ButtonStyle.Link && !string.IsNullOrEmpty(this.Url)) // Thanks 𝑴𝒓𝑪𝒂𝒌𝒆𝑺𝒍𝒂𝒚𝒆𝒓 :D | |||||
| this.Url = null; | this.Url = null; | ||||
| return new ButtonComponent(this.Style, this.Label, this.Emote, this.CustomId, this.Url, this.Disabled); | return new ButtonComponent(this.Style, this.Label, this.Emote, this.CustomId, this.Url, this.Disabled); | ||||
| @@ -6,19 +6,25 @@ using System.Threading.Tasks; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a component object used to send components with messages. | |||||
| /// </summary> | |||||
| public class MessageComponent | public class MessageComponent | ||||
| { | { | ||||
| public IReadOnlyCollection<IMessageComponent> Components { get; } | |||||
| /// <summary> | |||||
| /// The components to be used in a message. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<ActionRowComponent> Components { get; } | |||||
| internal MessageComponent(List<ActionRowComponent> components) | internal MessageComponent(List<ActionRowComponent> components) | ||||
| { | { | ||||
| this.Components = components; | this.Components = components; | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns a empty <see cref="MessageComponent"/>. | |||||
| /// </summary> | |||||
| internal static MessageComponent Empty | internal static MessageComponent Empty | ||||
| => new MessageComponent(new List<ActionRowComponent>()); | => new MessageComponent(new List<ActionRowComponent>()); | ||||
| internal IMessageComponent[] ToModel() | |||||
| => this.Components.ToArray(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,5 +21,10 @@ namespace Discord | |||||
| /// Gets or sets the embed the message should display. | /// Gets or sets the embed the message should display. | ||||
| /// </summary> | /// </summary> | ||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| /// <summary> | |||||
| /// Gets or sets the components for this message. | |||||
| /// </summary> | |||||
| public Optional<MessageComponent> Components { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -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<ButtonComponent> Components { get; set; } | |||||
| internal ActionRowComponent() { } | |||||
| internal ActionRowComponent(Discord.ActionRowComponent c) | |||||
| { | |||||
| this.Type = c.Type; | |||||
| this.Components = c.Components?.Select(x => new ButtonComponent(x)).ToList(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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<string> Label { get; set; } | |||||
| [JsonProperty("emoji")] | |||||
| public Optional<Emoji> Emote { get; set; } | |||||
| [JsonProperty("custom_id")] | |||||
| public Optional<string> CustomId { get; set; } | |||||
| [JsonProperty("url")] | |||||
| public Optional<string> Url { get; set; } | |||||
| [JsonProperty("disabled")] | |||||
| public Optional<bool> 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 | |||||
| }; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -26,7 +26,7 @@ namespace Discord.API | |||||
| public Optional<int> Flags { get; set; } | public Optional<int> Flags { get; set; } | ||||
| [JsonProperty("components")] | [JsonProperty("components")] | ||||
| public Optional<IMessageComponent[]> Components { get; set; } | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| public InteractionApplicationCommandCallbackData() { } | public InteractionApplicationCommandCallbackData() { } | ||||
| public InteractionApplicationCommandCallbackData(string text) | public InteractionApplicationCommandCallbackData(string text) | ||||
| @@ -59,6 +59,6 @@ namespace Discord.API | |||||
| [JsonProperty("referenced_message")] | [JsonProperty("referenced_message")] | ||||
| public Optional<Message> ReferencedMessage { get; set; } | public Optional<Message> ReferencedMessage { get; set; } | ||||
| [JsonProperty("components")] | [JsonProperty("components")] | ||||
| public Optional<IMessageComponent[]> Components { get; set; } | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -25,7 +25,7 @@ namespace Discord.API.Rest | |||||
| public Optional<MessageReference> MessageReference { get; set; } | public Optional<MessageReference> MessageReference { get; set; } | ||||
| [JsonProperty("components")] | [JsonProperty("components")] | ||||
| public Optional<IMessageComponent[]> Components { get; set; } | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| public CreateMessageParams(string content) | public CreateMessageParams(string content) | ||||
| { | { | ||||
| Content = content; | Content = content; | ||||
| @@ -31,7 +31,7 @@ namespace Discord.API.Rest | |||||
| public Optional<int> Flags { get; set; } | public Optional<int> Flags { get; set; } | ||||
| [JsonProperty("components")] | [JsonProperty("components")] | ||||
| public Optional<IMessageComponent[]> Components { get; set; } | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| public CreateWebhookMessageParams(string content) | public CreateWebhookMessageParams(string content) | ||||
| { | { | ||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| @@ -10,5 +10,7 @@ namespace Discord.API.Rest | |||||
| public Optional<string> Content { get; set; } | public Optional<string> Content { get; set; } | ||||
| [JsonProperty("embed")] | [JsonProperty("embed")] | ||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| [JsonProperty("components")] | |||||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ namespace Discord.API.Rest | |||||
| public Optional<Embed> Embed { get; set; } | public Optional<Embed> Embed { get; set; } | ||||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | public Optional<AllowedMentions> AllowedMentions { get; set; } | ||||
| public Optional<MessageReference> MessageReference { get; set; } | public Optional<MessageReference> MessageReference { get; set; } | ||||
| public Optional<IMessageComponent[]> MessageComponent { get; set; } | |||||
| public Optional<ActionRowComponent[]> MessageComponent { get; set; } | |||||
| public bool IsSpoiler { get; set; } = false; | public bool IsSpoiler { get; set; } = false; | ||||
| public UploadFileParams(Stream file) | public UploadFileParams(Stream file) | ||||
| @@ -9,9 +9,11 @@ | |||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <PackageIcon>Temporary.png</PackageIcon> | <PackageIcon>Temporary.png</PackageIcon> | ||||
| <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | ||||
| <Version>2.3.2</Version> | |||||
| <Version>2.3.5</Version> | |||||
| <PackageId>Discord.Net.Labs.Rest</PackageId> | <PackageId>Discord.Net.Labs.Rest</PackageId> | ||||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | ||||
| <AssemblyVersion>2.3.4</AssemblyVersion> | |||||
| <FileVersion>2.3.4</FileVersion> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| @@ -944,7 +944,7 @@ namespace Discord.API | |||||
| public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) | public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) | ||||
| { | { | ||||
| if(response.Data.IsSpecified && response.Data.Value.Content.IsSpecified) | 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); | options = RequestOptions.CreateOrClone(options); | ||||
| @@ -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<API.ActionRowComponent[]>.Unspecified }; | |||||
| var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | 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<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, MessageComponent = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified, IsSpoiler = isSpoiler }; | |||||
| var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed?.ToModel() ?? Optional<API.Embed>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, MessageComponent = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, IsSpoiler = isSpoiler }; | |||||
| var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); | ||||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | return RestUserMessage.Create(client, channel, client.CurrentUser, model); | ||||
| } | } | ||||
| @@ -41,7 +41,8 @@ namespace Discord.Rest | |||||
| var apiArgs = new API.Rest.ModifyMessageParams | var apiArgs = new API.Rest.ModifyMessageParams | ||||
| { | { | ||||
| Content = args.Content, | Content = args.Content, | ||||
| Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>() | |||||
| Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>(), | |||||
| Components = args?.Components.GetValueOrDefault()?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||||
| }; | }; | ||||
| return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -129,9 +129,16 @@ namespace Discord.Rest | |||||
| if (model.Components.IsSpecified) | if (model.Components.IsSpecified) | ||||
| { | { | ||||
| Components = model.Components.Value.Select(x => | |||||
| (x as Newtonsoft.Json.Linq.JToken).ToObject<ActionRowComponent>() | |||||
| ).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 | else | ||||
| Components = new List<ActionRowComponent>(); | Components = new List<ActionRowComponent>(); | ||||
| @@ -13,6 +13,9 @@ namespace Discord.API.Gateway | |||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| [JsonProperty("application_id")] | |||||
| public ulong ApplicationId { get; set; } | |||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public InteractionType Type { get; set; } | public InteractionType Type { get; set; } | ||||
| @@ -20,18 +23,25 @@ namespace Discord.API.Gateway | |||||
| public Optional<object> Data { get; set; } | public Optional<object> Data { get; set; } | ||||
| [JsonProperty("guild_id")] | [JsonProperty("guild_id")] | ||||
| public ulong GuildId { get; set; } | |||||
| public Optional<ulong> GuildId { get; set; } | |||||
| [JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
| public ulong ChannelId { get; set; } | |||||
| public Optional<ulong> ChannelId { get; set; } | |||||
| [JsonProperty("member")] | [JsonProperty("member")] | ||||
| public GuildMember Member { get; set; } | |||||
| public Optional<GuildMember> Member { get; set; } | |||||
| [JsonProperty("user")] | |||||
| public Optional<User> User { get; set; } | |||||
| [JsonProperty("token")] | [JsonProperty("token")] | ||||
| public string Token { get; set; } | public string Token { get; set; } | ||||
| [JsonProperty("version")] | [JsonProperty("version")] | ||||
| public int Version { get; set; } | public int Version { get; set; } | ||||
| [JsonProperty("message")] | |||||
| public Optional<Message> Message { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,7 +8,7 @@ | |||||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | ||||
| <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
| <Version>2.3.2</Version> | |||||
| <Version>2.3.4</Version> | |||||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | ||||
| <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | ||||
| <PackageIcon>Temporary.png</PackageIcon> | <PackageIcon>Temporary.png</PackageIcon> | ||||
| @@ -1785,26 +1785,33 @@ namespace Discord.WebSocket | |||||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | ||||
| var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer); | var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_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 | else | ||||
| { | { | ||||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||||
| return; | |||||
| // DM TODO | |||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -10,10 +10,21 @@ using Discord.Rest; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a Websocket-based interaction type for Message Components. | |||||
| /// </summary> | |||||
| public class SocketMessageComponent : SocketInteraction | public class SocketMessageComponent : SocketInteraction | ||||
| { | { | ||||
| /// <summary> | |||||
| /// The data received with this interaction, contains the button that was clicked. | |||||
| /// </summary> | |||||
| new public SocketMessageComponentData Data { get; } | new public SocketMessageComponentData Data { get; } | ||||
| /// <summary> | |||||
| /// The message that contained the trigger for this interaction. | |||||
| /// </summary> | |||||
| public SocketMessage Message { get; private set; } | |||||
| internal SocketMessageComponent(DiscordSocketClient client, Model model) | internal SocketMessageComponent(DiscordSocketClient client, Model model) | ||||
| : base(client, model.Id) | : base(client, model.Id) | ||||
| { | { | ||||
| @@ -22,6 +33,8 @@ namespace Discord.WebSocket | |||||
| : null; | : null; | ||||
| this.Data = new SocketMessageComponentData(dataModel); | this.Data = new SocketMessageComponentData(dataModel); | ||||
| } | } | ||||
| new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model) | new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model) | ||||
| @@ -31,6 +44,23 @@ namespace Discord.WebSocket | |||||
| return entity; | 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); | |||||
| } | |||||
| } | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Responds to an Interaction. | /// Responds to an Interaction. | ||||
| /// <para> | /// <para> | ||||
| @@ -51,7 +81,6 @@ namespace Discord.WebSocket | |||||
| /// </returns> | /// </returns> | ||||
| /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | /// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | ||||
| /// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception> | /// <exception cref="InvalidOperationException">The parameters provided were invalid or the token was invalid.</exception> | ||||
| public override async Task<RestUserMessage> RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType type = InteractionResponseType.ChannelMessageWithSource, | public override async Task<RestUserMessage> 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) | bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null) | ||||
| { | { | ||||
| @@ -94,7 +123,7 @@ namespace Discord.WebSocket | |||||
| ? new API.Embed[] { embed.ToModel() } | ? new API.Embed[] { embed.ToModel() } | ||||
| : Optional<API.Embed[]>.Unspecified, | : Optional<API.Embed[]>.Unspecified, | ||||
| TTS = isTTS, | TTS = isTTS, | ||||
| Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -134,7 +163,7 @@ namespace Discord.WebSocket | |||||
| Embeds = embed != null | Embeds = embed != null | ||||
| ? new API.Embed[] { embed.ToModel() } | ? new API.Embed[] { embed.ToModel() } | ||||
| : Optional<API.Embed[]>.Unspecified, | : Optional<API.Embed[]>.Unspecified, | ||||
| Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||||
| }; | }; | ||||
| if (ephemeral) | if (ephemeral) | ||||
| @@ -7,6 +7,9 @@ using Model = Discord.API.MessageComponentInteractionData; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents the data sent with a <see cref="InteractionType.MessageComponent"/>. | |||||
| /// </summary> | |||||
| public class SocketMessageComponentData | public class SocketMessageComponentData | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| @@ -21,7 +21,7 @@ namespace Discord.WebSocket | |||||
| (model.Data.Value as JToken).ToObject<DataModel>() | (model.Data.Value as JToken).ToObject<DataModel>() | ||||
| : null; | : null; | ||||
| Data = SocketSlashCommandData.Create(client, dataModel, model.GuildId); | |||||
| Data = SocketSlashCommandData.Create(client, dataModel, model.Id); | |||||
| } | } | ||||
| new internal static SocketInteraction Create(DiscordSocketClient client, Model model) | new internal static SocketInteraction Create(DiscordSocketClient client, Model model) | ||||
| @@ -37,7 +37,7 @@ namespace Discord.WebSocket | |||||
| (model.Data.Value as JToken).ToObject<DataModel>() | (model.Data.Value as JToken).ToObject<DataModel>() | ||||
| : null; | : null; | ||||
| this.Data.Update(data, model.GuildId); | |||||
| this.Data.Update(data); | |||||
| base.Update(model); | base.Update(model); | ||||
| } | } | ||||
| @@ -107,7 +107,7 @@ namespace Discord.WebSocket | |||||
| ? new API.Embed[] { embed.ToModel() } | ? new API.Embed[] { embed.ToModel() } | ||||
| : Optional<API.Embed[]>.Unspecified, | : Optional<API.Embed[]>.Unspecified, | ||||
| TTS = isTTS, | TTS = isTTS, | ||||
| Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -146,7 +146,7 @@ namespace Discord.WebSocket | |||||
| Embeds = embed != null | Embeds = embed != null | ||||
| ? new API.Embed[] { embed.ToModel() } | ? new API.Embed[] { embed.ToModel() } | ||||
| : Optional<API.Embed[]>.Unspecified, | : Optional<API.Embed[]>.Unspecified, | ||||
| Components = component?.ToModel() ?? Optional<IMessageComponent[]>.Unspecified | |||||
| Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified | |||||
| }; | }; | ||||
| if (ephemeral) | if (ephemeral) | ||||
| @@ -15,27 +15,24 @@ namespace Discord.WebSocket | |||||
| /// </summary> | /// </summary> | ||||
| public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | ||||
| private ulong guildId; | |||||
| internal SocketSlashCommandData(DiscordSocketClient client, ulong id) | internal SocketSlashCommandData(DiscordSocketClient client, ulong id) | ||||
| : base(client, 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); | var entity = new SocketSlashCommandData(client, model.Id); | ||||
| entity.Update(model, guildId); | |||||
| entity.Update(model); | |||||
| return entity; | return entity; | ||||
| } | } | ||||
| internal void Update(Model model, ulong guildId) | |||||
| internal void Update(Model model) | |||||
| { | { | ||||
| this.Name = model.Name; | this.Name = model.Name; | ||||
| this.guildId = guildId; | |||||
| this.Options = model.Options.Any() | 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; | : null; | ||||
| } | } | ||||
| @@ -22,18 +22,16 @@ namespace Discord.WebSocket | |||||
| public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; private set; } | ||||
| private DiscordSocketClient discord; | private DiscordSocketClient discord; | ||||
| private ulong guild; | |||||
| internal SocketSlashCommandDataOption() { } | internal SocketSlashCommandDataOption() { } | ||||
| internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord, ulong guild) | |||||
| internal SocketSlashCommandDataOption(Model model, DiscordSocketClient discord) | |||||
| { | { | ||||
| this.Name = model.Name; | this.Name = model.Name; | ||||
| this.Value = model.Value.IsSpecified ? model.Value.Value : null; | this.Value = model.Value.IsSpecified ? model.Value.Value : null; | ||||
| this.discord = discord; | this.discord = discord; | ||||
| this.guild = guild; | |||||
| this.Options = model.Options.Any() | 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; | : null; | ||||
| } | } | ||||
| @@ -49,7 +47,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (option.Value is ulong id) | if (option.Value is ulong id) | ||||
| { | { | ||||
| var guild = option.discord.GetGuild(option.guild); | |||||
| var guild = option.discord.GetGuild(id); | |||||
| if (guild == null) | if (guild == null) | ||||
| return null; | return null; | ||||
| @@ -64,7 +62,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if (option.Value is ulong id) | if (option.Value is ulong id) | ||||
| { | { | ||||
| var guild = option.discord.GetGuild(option.guild); | |||||
| var guild = option.discord.GetGuild(id); | |||||
| if (guild == null) | if (guild == null) | ||||
| return null; | return null; | ||||
| @@ -79,7 +77,7 @@ namespace Discord.WebSocket | |||||
| { | { | ||||
| if(option.Value is ulong id) | if(option.Value is ulong id) | ||||
| { | { | ||||
| var guild = option.discord.GetGuild(option.guild); | |||||
| var guild = option.discord.GetGuild(id); | |||||
| if (guild == null) | if (guild == null) | ||||
| return null; | return null; | ||||
| @@ -14,21 +14,14 @@ namespace Discord.WebSocket | |||||
| public abstract class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | public abstract class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// The <see cref="SocketGuild"/> this interaction was used in. | |||||
| /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | |||||
| /// </summary> | /// </summary> | ||||
| public SocketGuild Guild | |||||
| => Discord.GetGuild(GuildId); | |||||
| public ISocketMessageChannel Channel { get; private set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// The <see cref="SocketTextChannel"/> this interaction was used in. | |||||
| /// The <see cref="SocketUser"/> who triggered this interaction. | |||||
| /// </summary> | /// </summary> | ||||
| public SocketTextChannel Channel | |||||
| => Guild.GetTextChannel(ChannelId); | |||||
| /// <summary> | |||||
| /// The <see cref="SocketGuildUser"/> who triggered this interaction. | |||||
| /// </summary> | |||||
| public SocketGuildUser User { get; private set; } | |||||
| public SocketUser User { get; private set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// The type of this interaction. | /// The type of this interaction. | ||||
| @@ -58,9 +51,8 @@ namespace Discord.WebSocket | |||||
| public bool IsValidToken | public bool IsValidToken | ||||
| => CheckToken(); | => 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) | internal SocketInteraction(DiscordSocketClient client, ulong id) | ||||
| : base(client, id) | : base(client, id) | ||||
| @@ -83,15 +75,35 @@ namespace Discord.WebSocket | |||||
| ? model.Data.Value | ? model.Data.Value | ||||
| : null; | : null; | ||||
| this.GuildId = model.GuildId; | |||||
| this.ChannelId = model.ChannelId; | |||||
| this.GuildId = model.GuildId.ToNullable(); | |||||
| this.ChannelId = model.ChannelId.ToNullable(); | |||||
| this.Token = model.Token; | this.Token = model.Token; | ||||
| this.Version = model.Version; | this.Version = model.Version; | ||||
| this.UserId = model.Member.User.Id; | |||||
| this.Type = model.Type; | this.Type = model.Type; | ||||
| if (this.User == null) | 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); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -162,9 +162,16 @@ namespace Discord.WebSocket | |||||
| if (model.Components.IsSpecified) | if (model.Components.IsSpecified) | ||||
| { | { | ||||
| Components = model.Components.Value.Select(x => | |||||
| (x as Newtonsoft.Json.Linq.JToken).ToObject<ActionRowComponent>() | |||||
| ).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 | else | ||||
| Components = new List<ActionRowComponent>(); | Components = new List<ActionRowComponent>(); | ||||
| @@ -2,7 +2,7 @@ | |||||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | ||||
| <metadata> | <metadata> | ||||
| <id>Discord.Net.Labs</id> | <id>Discord.Net.Labs</id> | ||||
| <version>2.3.1$suffix$</version> | |||||
| <version>2.3.3$suffix$</version> | |||||
| <title>Discord.Net Labs</title> | <title>Discord.Net Labs</title> | ||||
| <authors>Discord.Net Contributors</authors> | <authors>Discord.Net Contributors</authors> | ||||
| <owners>quinchs</owners> | <owners>quinchs</owners> | ||||
| @@ -14,23 +14,23 @@ | |||||
| <iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl> | <iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl> | ||||
| <dependencies> | <dependencies> | ||||
| <group targetFramework="net461"> | <group targetFramework="net461"> | ||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | ||||
| <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | ||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.0"> | <group targetFramework="netstandard2.0"> | ||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | ||||
| <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | ||||
| </group> | </group> | ||||
| <group targetFramework="netstandard2.1"> | <group targetFramework="netstandard2.1"> | ||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.1$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Core" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Rest" version="2.3.5$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.WebSocket" version="2.3.4$suffix$" /> | |||||
| <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Commands" version="2.3.1$suffix$" /> | ||||
| <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | <dependency id="Discord.Net.Labs.Webhook" version="2.3.1$suffix$" /> | ||||
| </group> | </group> | ||||