| @@ -8,7 +8,7 @@ | |||
| <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks> | |||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
| <PackageId>Discord.Net.Labs.Core</PackageId> | |||
| <Version>2.3.3</Version> | |||
| <Version>2.3.4</Version> | |||
| <Product>Discord.Net.Labs.Core</Product> | |||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
| <PackageIcon>Temporary.png</PackageIcon> | |||
| @@ -7,16 +7,21 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a <see cref="IMessageComponent"/> Row for child components to live in. | |||
| /// </summary> | |||
| public class ActionRowComponent : IMessageComponent | |||
| { | |||
| [JsonProperty("type")] | |||
| /// <inheritdoc/> | |||
| 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(IReadOnlyCollection<IMessageComponent> components) | |||
| internal ActionRowComponent(List<ButtonComponent> components) | |||
| { | |||
| this.Components = components; | |||
| } | |||
| @@ -7,27 +7,45 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a <see cref="IMessageComponent"/> Button. | |||
| /// </summary> | |||
| public class ButtonComponent : IMessageComponent | |||
| { | |||
| [JsonProperty("type")] | |||
| /// <inheritdoc/> | |||
| 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; } | |||
| [JsonProperty("label")] | |||
| /// <summary> | |||
| /// The label of the button, this is the text that is shown. | |||
| /// </summary> | |||
| public string Label { get; } | |||
| [JsonProperty("emoji")] | |||
| /// <summary> | |||
| /// A <see cref="IEmote"/> that will be displayed with this button. | |||
| /// </summary> | |||
| 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; } | |||
| [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; } | |||
| [JsonProperty("disabled")] | |||
| /// <summary> | |||
| /// Whether this button is disabled or not. | |||
| /// </summary> | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,10 +6,19 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a builder for creating a <see cref="MessageComponent"/>. | |||
| /// </summary> | |||
| public class ComponentBuilder | |||
| { | |||
| /// <summary> | |||
| /// The max amount of rows a message can have. | |||
| /// </summary> | |||
| public const int MaxActionRowCount = 5; | |||
| /// <summary> | |||
| /// Gets or sets the Action Rows for this Component Builder. | |||
| /// </summary> | |||
| public List<ActionRowBuilder> ActionRows | |||
| { | |||
| get => _actionRows; | |||
| @@ -25,11 +34,22 @@ namespace Discord | |||
| 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( | |||
| 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); | |||
| } | |||
| /// <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) | |||
| => 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) | |||
| { | |||
| var builtButton = button.Build(); | |||
| @@ -75,6 +106,10 @@ namespace Discord | |||
| 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() | |||
| { | |||
| if (this._actionRows != null) | |||
| @@ -84,10 +119,20 @@ namespace Discord | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Represents a class used to build Action rows. | |||
| /// </summary> | |||
| public class ActionRowBuilder | |||
| { | |||
| /// <summary> | |||
| /// The max amount of child components this row can hold. | |||
| /// </summary> | |||
| 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; | |||
| 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; | |||
| 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) | |||
| this.Components = new List<IMessageComponent>(); | |||
| this.Components = new List<ButtonComponent>(); | |||
| this.Components.Add(component); | |||
| 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() | |||
| => 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 | |||
| { | |||
| /// <summary> | |||
| /// The max length of a <see cref="ButtonComponent.Label"/>. | |||
| /// </summary> | |||
| public const int MaxLabelLength = 80; | |||
| /// <summary> | |||
| /// The max length of a <see cref="ButtonComponent.CustomId"/>. | |||
| /// </summary> | |||
| public const int MaxCustomIdLength = 100; | |||
| /// <summary> | |||
| /// Gets or sets the label of the current button. | |||
| /// </summary> | |||
| public string Label | |||
| { | |||
| get => _label; | |||
| @@ -139,6 +221,9 @@ namespace Discord | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Gets or sets the custom id of the current button. | |||
| /// </summary> | |||
| public string 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; } | |||
| /// <summary> | |||
| /// Gets or sets the <see cref="IEmote"/> of the current button. | |||
| /// </summary> | |||
| public IEmote Emote { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets the url of the current button. | |||
| /// </summary> | |||
| public string Url { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets whether the current button is disabled. | |||
| /// </summary> | |||
| public bool Disabled { get; set; } | |||
| private string _label; | |||
| 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) | |||
| { | |||
| var builder = new ButtonBuilder() | |||
| @@ -170,6 +276,12 @@ namespace Discord | |||
| 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) | |||
| { | |||
| var builder = new ButtonBuilder() | |||
| @@ -180,6 +292,12 @@ namespace Discord | |||
| 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) | |||
| { | |||
| var builder = new ButtonBuilder() | |||
| @@ -190,6 +308,12 @@ namespace Discord | |||
| 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) | |||
| { | |||
| var builder = new ButtonBuilder() | |||
| @@ -200,6 +324,12 @@ namespace Discord | |||
| 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) | |||
| { | |||
| var builder = new ButtonBuilder() | |||
| @@ -210,41 +340,78 @@ namespace Discord | |||
| 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) | |||
| { | |||
| this.Label = label; | |||
| 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) | |||
| { | |||
| this.Style = style; | |||
| 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) | |||
| { | |||
| this.Emote = emote; | |||
| 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) | |||
| { | |||
| this.Url = url; | |||
| 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) | |||
| { | |||
| this.CustomId = id; | |||
| 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) | |||
| { | |||
| this.Disabled = disabled; | |||
| 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() | |||
| { | |||
| 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); | |||
| @@ -6,19 +6,25 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a component object used to send components with messages. | |||
| /// </summary> | |||
| 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) | |||
| { | |||
| this.Components = components; | |||
| } | |||
| /// <summary> | |||
| /// Returns a empty <see cref="MessageComponent"/>. | |||
| /// </summary> | |||
| internal static MessageComponent Empty | |||
| => 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. | |||
| /// </summary> | |||
| 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; } | |||
| [JsonProperty("components")] | |||
| public Optional<IMessageComponent[]> Components { get; set; } | |||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
| public InteractionApplicationCommandCallbackData() { } | |||
| public InteractionApplicationCommandCallbackData(string text) | |||
| @@ -59,6 +59,6 @@ namespace Discord.API | |||
| [JsonProperty("referenced_message")] | |||
| public Optional<Message> ReferencedMessage { get; set; } | |||
| [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; } | |||
| [JsonProperty("components")] | |||
| public Optional<IMessageComponent[]> Components { get; set; } | |||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
| public CreateMessageParams(string content) | |||
| { | |||
| Content = content; | |||
| @@ -31,7 +31,7 @@ namespace Discord.API.Rest | |||
| public Optional<int> Flags { get; set; } | |||
| [JsonProperty("components")] | |||
| public Optional<IMessageComponent[]> Components { get; set; } | |||
| public Optional<API.ActionRowComponent[]> Components { get; set; } | |||
| public CreateWebhookMessageParams(string content) | |||
| { | |||
| @@ -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<string> Content { get; set; } | |||
| [JsonProperty("embed")] | |||
| 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<AllowedMentions> AllowedMentions { 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 UploadFileParams(Stream file) | |||
| @@ -9,9 +9,11 @@ | |||
| <TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
| <PackageIcon>Temporary.png</PackageIcon> | |||
| <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> | |||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
| <AssemblyVersion>2.3.4</AssemblyVersion> | |||
| <FileVersion>2.3.4</FileVersion> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <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) | |||
| { | |||
| 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); | |||
| @@ -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); | |||
| 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); | |||
| return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
| } | |||
| @@ -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<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); | |||
| } | |||
| @@ -129,9 +129,16 @@ namespace Discord.Rest | |||
| 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 | |||
| Components = new List<ActionRowComponent>(); | |||
| @@ -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<object> Data { get; set; } | |||
| [JsonProperty("guild_id")] | |||
| public ulong GuildId { get; set; } | |||
| public Optional<ulong> GuildId { get; set; } | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| public Optional<ulong> ChannelId { get; set; } | |||
| [JsonProperty("member")] | |||
| public GuildMember Member { get; set; } | |||
| public Optional<GuildMember> Member { get; set; } | |||
| [JsonProperty("user")] | |||
| public Optional<User> User { get; set; } | |||
| [JsonProperty("token")] | |||
| public string Token { get; set; } | |||
| [JsonProperty("version")] | |||
| 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' ">netstandard2.0;netstandard2.1</TargetFrameworks> | |||
| <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
| <Version>2.3.2</Version> | |||
| <Version>2.3.4</Version> | |||
| <RepositoryUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</RepositoryUrl> | |||
| <PackageProjectUrl>https://github.com/Discord-Net-Labs/Discord.Net-Labs</PackageProjectUrl> | |||
| <PackageIcon>Temporary.png</PackageIcon> | |||
| @@ -1785,26 +1785,33 @@ namespace Discord.WebSocket | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | |||
| 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 | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| // DM TODO | |||
| } | |||
| } | |||
| break; | |||
| @@ -10,10 +10,21 @@ using Discord.Rest; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents a Websocket-based interaction type for Message Components. | |||
| /// </summary> | |||
| public class SocketMessageComponent : SocketInteraction | |||
| { | |||
| /// <summary> | |||
| /// The data received with this interaction, contains the button that was clicked. | |||
| /// </summary> | |||
| 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) | |||
| : 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); | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Responds to an Interaction. | |||
| /// <para> | |||
| @@ -51,7 +81,6 @@ namespace Discord.WebSocket | |||
| /// </returns> | |||
| /// <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> | |||
| 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) | |||
| { | |||
| @@ -94,7 +123,7 @@ namespace Discord.WebSocket | |||
| ? new API.Embed[] { embed.ToModel() } | |||
| : Optional<API.Embed[]>.Unspecified, | |||
| 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 | |||
| ? new API.Embed[] { embed.ToModel() } | |||
| : 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) | |||
| @@ -7,6 +7,9 @@ using Model = Discord.API.MessageComponentInteractionData; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// Represents the data sent with a <see cref="InteractionType.MessageComponent"/>. | |||
| /// </summary> | |||
| public class SocketMessageComponentData | |||
| { | |||
| /// <summary> | |||
| @@ -21,7 +21,7 @@ namespace Discord.WebSocket | |||
| (model.Data.Value as JToken).ToObject<DataModel>() | |||
| : 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<DataModel>() | |||
| : 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<API.Embed[]>.Unspecified, | |||
| 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 | |||
| ? new API.Embed[] { embed.ToModel() } | |||
| : 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) | |||
| @@ -15,27 +15,24 @@ namespace Discord.WebSocket | |||
| /// </summary> | |||
| public IReadOnlyCollection<SocketSlashCommandDataOption> 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; | |||
| } | |||
| @@ -22,18 +22,16 @@ namespace Discord.WebSocket | |||
| public IReadOnlyCollection<SocketSlashCommandDataOption> 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; | |||
| @@ -14,21 +14,14 @@ namespace Discord.WebSocket | |||
| public abstract class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | |||
| { | |||
| /// <summary> | |||
| /// The <see cref="SocketGuild"/> this interaction was used in. | |||
| /// The <see cref="ISocketMessageChannel"/> this interaction was used in. | |||
| /// </summary> | |||
| public SocketGuild Guild | |||
| => Discord.GetGuild(GuildId); | |||
| public ISocketMessageChannel Channel { get; private set; } | |||
| /// <summary> | |||
| /// The <see cref="SocketTextChannel"/> this interaction was used in. | |||
| /// The <see cref="SocketUser"/> who triggered this interaction. | |||
| /// </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> | |||
| /// 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); | |||
| } | |||
| } | |||
| } | |||
| /// <summary> | |||
| @@ -162,9 +162,16 @@ namespace Discord.WebSocket | |||
| 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 | |||
| Components = new List<ActionRowComponent>(); | |||
| @@ -2,7 +2,7 @@ | |||
| <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> | |||
| <metadata> | |||
| <id>Discord.Net.Labs</id> | |||
| <version>2.3.1$suffix$</version> | |||
| <version>2.3.3$suffix$</version> | |||
| <title>Discord.Net Labs</title> | |||
| <authors>Discord.Net Contributors</authors> | |||
| <owners>quinchs</owners> | |||
| @@ -14,23 +14,23 @@ | |||
| <iconUrl>https://avatars.githubusercontent.com/u/84047264</iconUrl> | |||
| <dependencies> | |||
| <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.Webhook" version="2.3.1$suffix$" /> | |||
| </group> | |||
| <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.Webhook" version="2.3.1$suffix$" /> | |||
| </group> | |||
| <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.Webhook" version="2.3.1$suffix$" /> | |||
| </group> | |||