From 3f86e61047d758ce6e5f54f6b943b82f5aa3a32e Mon Sep 17 00:00:00 2001 From: quin lynch Date: Fri, 2 Jul 2021 17:45:12 -0300 Subject: [PATCH] Added select menus This comit addds support for select menu message components defined at https://discord.com/developers/docs/interactions/message-components#select-menus. Added 2 new converters: InteractionConverter and MessageComponentConverter. These converters are responsible for resolving the API type given some condition. Added new core interface for representing Interaction Data, this is used in part with the InteractionConverter to parse the data to the respective types. Updated the way gateway converting is done with interactions. The Interaction payload was moved from the API.Gateway namespace to API, as the interaction object is not websocket specific. The socket entities no longer try to parse the interaction models data to a JToken to desterilize since the data is now parsed to the correct model by the contract resolver. --- src/Discord.Net.Core/Discord.Net.Core.xml | 347 +++++++++++- .../Interactions/IDiscordInteraction.cs | 2 +- .../Interactions/IDiscordInteractionData.cs | 13 + .../Message Components/ActionRowComponent.cs | 4 +- .../Message Components/ComponentBuilder.cs | 529 +++++++++++++++++- .../Message Components/ComponentType.cs | 7 +- .../Message Components/SelectMenu.cs | 51 ++ .../Message Components/SelectMenuOption.cs | 48 ++ .../API/Common/ActionRowComponent.cs | 18 +- .../ApplicationCommandInteractionData.cs | 2 +- .../API/Common/ButtonComponent.cs | 2 +- .../API/Common/Interaction.cs} | 9 +- .../Common/MessageComponentInteractionData.cs | 5 +- .../API/Common/SelectMenuComponent.cs | 42 ++ .../API/Common/SelectMenuOption.cs | 58 ++ .../Entities/Messages/RestMessage.cs | 54 +- .../Net/Converters/DiscordContractResolver.cs | 4 + .../Net/Converters/InteractionConverter.cs | 67 +++ .../Converters/MessageComponentConverter.cs | 44 ++ .../Discord.Net.WebSocket.xml | 5 + .../DiscordSocketClient.cs | 2 +- .../SocketMessageComponent.cs | 6 +- .../SocketMessageComponentData.cs | 6 + .../Slash Commands/SocketSlashCommand.cs | 4 +- .../Entities/Interaction/SocketInteraction.cs | 4 +- .../Entities/Messages/SocketMessage.cs | 54 +- .../Discord.Net.Webhook.xml | 50 ++ 27 files changed, 1359 insertions(+), 78 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenu.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs rename src/{Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs => Discord.Net.Rest/API/Common/Interaction.cs} (84%) create mode 100644 src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/SelectMenuOption.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs diff --git a/src/Discord.Net.Core/Discord.Net.Core.xml b/src/Discord.Net.Core/Discord.Net.Core.xml index f60639030..a4b16057d 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.xml +++ b/src/Discord.Net.Core/Discord.Net.Core.xml @@ -4132,6 +4132,11 @@ read-only property, always 1. + + + Represents an interface used to specify classes that they are a vaild dataype of a class. + + The response type for an . @@ -4277,6 +4282,16 @@ Represents a builder for creating a . + + + The max length of a . + + + + + The max length of a . + + The max amount of rows a message can have. @@ -4287,6 +4302,36 @@ Gets or sets the Action Rows for this Component Builder. + + + Adds a to the first row, if the first row cannot + accept the component then it will add it to a row that can + + The label of the menu. + The custom id of the menu. + The options of the menu. + The placeholder of the menu. + The min values of the placeholder. + The max values of the placeholder. + The row to add the menu to. + + + + + Adds a to the first row, if the first row cannot + accept the component then it will add it to a row that can + + The menu to add + The current builder. + + + + Adds a to the current builder at the specific row. + + The menu to add. + The row to attempt to add this component on. + The current builder. + Adds a button to the specified row. @@ -4336,14 +4381,14 @@ Gets or sets the components inside this row. - + Adds a list of components to the current row. The list of components to add. The current builder. - + Adds a component at the end of the current row. @@ -4363,16 +4408,6 @@ Represents a class used to build 's. - - - The max length of a . - - - - - The max length of a . - - Gets or sets the label of the current button. @@ -4493,6 +4528,226 @@ A button cannot contain a URL and a CustomId. A button must have an Emote or a label. + + + Represents a class used to build 's. + + + + + The max length of a . + + + + + The maximum number of values for the and properties. + + + + + The maximum number of options a can have. + + + + + Gets or sets the label of the current select menu. + + + + + Gets or sets the custom id of the current select menu. + + + + + Gets or sets the placeholder text of the current select menu. + + + + + Gets or sets the minimum values of the current select menu. + + + + + Gets or sets the maximum values of the current select menu. + + + + + Gets or sets a collection of for this current select menu. + + + + + Creates a new instance of a . + + + + + Creates a new instance of a . + + The custom id of this select menu. + The options for this select menu. + + + + Sets the field label. + + The value to set the field label to. + + The current builder. + + + + + Sets the field CustomId. + + The value to set the field CustomId to. + + The current builder. + + + + + Sets the field placeholder. + + The value to set the field placeholder to. + + The current builder. + + + + + Sets the field minValues. + + The value to set the field minValues to. + + The current builder. + + + + + Sets the field maxValues. + + The value to set the field maxValues to. + + The current builder. + + + + + Sets the field options. + + The value to set the field options to. + + The current builder. + + + + + Builds a + + The newly built + + + + Represents a class used to build 's. + + + + + The maximum length of a . + + + + + Gets or sets the label of the current select menu. + + + + + Gets or sets the custom id of the current select menu. + + + + + Gets or sets this menu options description. + + + + + Gets or sets the emote of this option. + + + + + Gets or sets the whether or not this option will render selected by default. + + + + + Creates a new instance of a . + + + + + Creates a new instance of a . + + The label for this option. + The value of this option. + + + + Sets the field label. + + The value to set the field label to. + + The current builder. + + + + + Sets the field value. + + The value to set the field value to. + + The current builder. + + + + + Sets the field description. + + The value to set the field description to. + + The current builder. + + + + + Sets the field emote. + + The value to set the field emote to. + + The current builder. + + + + + Sets the field default. + + The value to set the field default to. + + The current builder. + + + + + Builds a . + + The newly built . + Represents a type of a component @@ -4508,6 +4763,11 @@ A clickable button + + + A select menu for picking from choices + + The of this Message Component. @@ -4528,6 +4788,69 @@ Returns a empty . + + + Represents a select menu component defined at + + + + + + + + The custom id of this Select menu that will be sent with a . + + + + + The menus options to select from. + + + + + A custom placeholder text if nothing is selected, max 100 characters. + + + + + The minimum number of items that must be chosen; default 1, min 0, max 25 + + + + + The maximum number of items that can be chosen; default 1, max 25 + + + + + Represents a choice for a . + + + + + The user-facing name of the option, max 25 characters. + + + + + The dev-define value of the option, max 100 characters. + + + + + An additional description of the option, max 50 characters. + + + + + A that will be displayed with this menu option. + + + + + Will render this option as selected by default. + + A class used to build slash commands. diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 5c409e837..466bf3e91 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -28,7 +28,7 @@ namespace Discord /// /// Represents the data sent within this interaction. /// - object Data { get; } + IDiscordInteractionData Data { get; } /// /// A continuation token for responding to the interaction. diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs new file mode 100644 index 000000000..7083810b7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteractionData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents an interface used to specify classes that they are a vaild dataype of a class. + /// + public interface IDiscordInteractionData { } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs index c29ef44a2..c5bbc8d08 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs @@ -18,10 +18,10 @@ namespace Discord /// /// The child components in this row. /// - public IReadOnlyCollection Components { get; internal set; } + public IReadOnlyCollection Components { get; internal set; } internal ActionRowComponent() { } - internal ActionRowComponent(List components) + internal ActionRowComponent(List components) { this.Components = components; } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs index e8b0ce19c..afd1e34bd 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs @@ -11,6 +11,16 @@ namespace Discord /// public class ComponentBuilder { + /// + /// The max length of a . + /// + public const int MaxLabelLength = 80; + + /// + /// The max length of a . + /// + public const int MaxCustomIdLength = 100; + /// /// The max amount of rows a message can have. /// @@ -34,6 +44,84 @@ namespace Discord private List _actionRows { get; set; } + /// + /// Adds a to the first row, if the first row cannot + /// accept the component then it will add it to a row that can + /// + /// The label of the menu. + /// The custom id of the menu. + /// The options of the menu. + /// The placeholder of the menu. + /// The min values of the placeholder. + /// The max values of the placeholder. + /// The row to add the menu to. + /// + public ComponentBuilder WithSelectMenu(string label, string customId, List options, + string placeholder = null, int minValues = 1, int maxValues = 1, int row = 0) + { + return WithSelectMenu(new SelectMenuBuilder() + .WithLabel(label) + .WithCustomId(customId) + .WithOptions(options) + .WithPlaceholder(placeholder) + .WithMaxValues(maxValues) + .WithMinValues(minValues), + row); + } + + /// + /// Adds a to the first row, if the first row cannot + /// accept the component then it will add it to a row that can + /// + /// The menu to add + /// The current builder. + public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu) + => WithSelectMenu(menu, 0); + + /// + /// Adds a to the current builder at the specific row. + /// + /// The menu to add. + /// The row to attempt to add this component on. + /// The current builder. + public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row) + { + Preconditions.LessThan(row, 5, nameof(row)); + + var builtMenu = menu.Build(); + + if (_actionRows == null) + { + _actionRows = new List(); + _actionRows.Add(new ActionRowBuilder().WithComponent(builtMenu)); + } + else + { + if (_actionRows.Count == row) + _actionRows.Add(new ActionRowBuilder().WithComponent(builtMenu)); + else + { + ActionRowBuilder actionRow = null; + if (_actionRows.Count < row) + actionRow = _actionRows.ElementAt(row); + else + { + actionRow = new ActionRowBuilder(); + _actionRows.Add(actionRow); + } + + if (actionRow.CanTakeComponent(builtMenu)) + actionRow.WithComponent(builtMenu); + else if (row < 5) + WithSelectMenu(menu, row + 1); + else + throw new ArgumentOutOfRangeException($"There is no more room to add a {nameof(builtMenu)}"); + } + } + + return this; + } + /// /// Adds a button to the specified row. /// @@ -81,6 +169,8 @@ namespace Discord /// The current builder. public ComponentBuilder WithButton(ButtonBuilder button, int row) { + Preconditions.LessThan(row, 5, nameof(row)); + var builtButton = button.Build(); if (_actionRows == null) @@ -94,12 +184,21 @@ namespace Discord _actionRows.Add(new ActionRowBuilder().WithComponent(builtButton)); else { - if (_actionRows.Count > row) - _actionRows[row].WithComponent(builtButton); + ActionRowBuilder actionRow = null; + if(_actionRows.Count < row) + actionRow = _actionRows.ElementAt(row); else { - _actionRows.First().WithComponent(builtButton); + actionRow = new ActionRowBuilder(); + _actionRows.Add(actionRow); } + + if (actionRow.CanTakeComponent(builtButton)) + actionRow.WithComponent(builtButton); + else if (row < 5) + WithButton(button, row + 1); + else + throw new ArgumentOutOfRangeException($"There is no more room to add a {nameof(button)}"); } } @@ -132,7 +231,7 @@ namespace Discord /// /// Gets or sets the components inside this row. /// - public List Components + public List Components { get => _components; set @@ -144,14 +243,14 @@ namespace Discord } } - private List _components { get; set; } + private List _components { get; set; } /// /// Adds a list of components to the current row. /// /// The list of components to add. /// The current builder. - public ActionRowBuilder WithComponents(List components) + public ActionRowBuilder WithComponents(List components) { this.Components = components; return this; @@ -162,10 +261,10 @@ namespace Discord /// /// The component to add. /// The current builder. - public ActionRowBuilder WithComponent(ButtonComponent component) + public ActionRowBuilder WithComponent(IMessageComponent component) { if (this.Components == null) - this.Components = new List(); + this.Components = new List(); this.Components.Add(component); @@ -188,6 +287,21 @@ namespace Discord return new ActionRowComponent(this._components); } + + internal bool CanTakeComponent(IMessageComponent component) + { + switch (component.Type) + { + case ComponentType.ActionRow: + return false; + case ComponentType.Button: + return this.Components.Count < 5; + case ComponentType.SelectMenu: + return this.Components.Count == 0; + default: + return false; + } + } } /// @@ -195,16 +309,6 @@ namespace Discord /// public class ButtonBuilder { - /// - /// The max length of a . - /// - public const int MaxLabelLength = 80; - - /// - /// The max length of a . - /// - public const int MaxCustomIdLength = 100; - /// /// Gets or sets the label of the current button. /// @@ -214,8 +318,8 @@ namespace Discord set { if (value != null) - if (value.Length > MaxLabelLength) - throw new ArgumentException(message: $"Button label must be {MaxLabelLength} characters or less!", paramName: nameof(Label)); + if (value.Length > ComponentBuilder.MaxLabelLength) + throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); _label = value; } @@ -230,8 +334,8 @@ namespace Discord set { if (value != null) - if (value.Length > MaxCustomIdLength) - throw new ArgumentException(message: $"Custom Id must be {MaxCustomIdLength} characters or less!", paramName: nameof(CustomId)); + if (value.Length > ComponentBuilder.MaxCustomIdLength) + throw new ArgumentException(message: $"Custom Id must be {ComponentBuilder.MaxCustomIdLength} characters or less!", paramName: nameof(CustomId)); _customId = value; } } @@ -429,4 +533,385 @@ namespace Discord return new ButtonComponent(this.Style, this.Label, this.Emote, this.CustomId, this.Url, this.Disabled); } } + + /// + /// Represents a class used to build 's. + /// + public class SelectMenuBuilder + { + /// + /// The max length of a . + /// + public const int MaxPlaceholderLength = 100; + + /// + /// The maximum number of values for the and properties. + /// + public const int MaxValuesCount = 25; + + /// + /// The maximum number of options a can have. + /// + public const int MaxOptionCount = 25; + + /// + /// Gets or sets the label of the current select menu. + /// + public string Label + { + get => _label; + set + { + if (value != null) + if (value.Length > ComponentBuilder.MaxLabelLength) + throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + + _label = value; + } + } + + /// + /// Gets or sets the custom id of the current select menu. + /// + public string CustomId + { + get => _customId; + set + { + if (value != null) + if (value.Length > ComponentBuilder.MaxCustomIdLength) + throw new ArgumentException(message: $"Custom Id must be {ComponentBuilder.MaxCustomIdLength} characters or less!", paramName: nameof(CustomId)); + _customId = value; + } + } + + /// + /// Gets or sets the placeholder text of the current select menu. + /// + public string Placeholder + { + get => _placeholder; + set + { + if (value?.Length > MaxPlaceholderLength) + throw new ArgumentException(message: $"Placeholder must be {MaxPlaceholderLength} characters or less!", paramName: nameof(Placeholder)); + + _placeholder = value; + } + } + + /// + /// Gets or sets the minimum values of the current select menu. + /// + public int MinValues + { + get => _minvalues; + set + { + Preconditions.LessThan(value, MaxValuesCount, nameof(MinValues)); + _minvalues = value; + } + } + + /// + /// Gets or sets the maximum values of the current select menu. + /// + public int MaxValues + { + get => _maxvalues; + set + { + Preconditions.LessThan(value, MaxValuesCount, nameof(MaxValues)); + _maxvalues = value; + } + } + + /// + /// Gets or sets a collection of for this current select menu. + /// + public List Options + { + get => _options; + set + { + if (value != null) + Preconditions.LessThan(value.Count, MaxOptionCount, nameof(Options)); + + _options = value; + } + } + + private List _options; + private int _minvalues = 1; + private int _maxvalues = 1; + private string _placeholder; + private string _label; + private string _customId; + + /// + /// Creates a new instance of a . + /// + public SelectMenuBuilder() { } + + /// + /// Creates a new instance of a . + /// + /// The custom id of this select menu. + /// The options for this select menu. + public SelectMenuBuilder(string customId, List options) + { + this.CustomId = customId; + this.Options = options; + } + + /// + /// Sets the field label. + /// + /// The value to set the field label to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithLabel(string label) + { + this.Label = label; + return this; + } + + /// + /// Sets the field CustomId. + /// + /// The value to set the field CustomId to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithCustomId(string customId) + { + this.CustomId = customId; + return this; + } + + /// + /// Sets the field placeholder. + /// + /// The value to set the field placeholder to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithPlaceholder(string placeholder) + { + this.Placeholder = placeholder; + return this; + } + + /// + /// Sets the field minValues. + /// + /// The value to set the field minValues to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithMinValues(int minValues) + { + this.MinValues = minValues; + return this; + } + + /// + /// Sets the field maxValues. + /// + /// The value to set the field maxValues to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithMaxValues(int maxValues) + { + this.MaxValues = maxValues; + return this; + } + + /// + /// Sets the field options. + /// + /// The value to set the field options to. + /// + /// The current builder. + /// + public SelectMenuBuilder WithOptions(List options) + { + this.Options = options; + return this; + } + + /// + /// Builds a + /// + /// The newly built + public SelectMenu Build() + { + var opt = this.Options?.Select(x => x.Build()).ToList(); + + return new SelectMenu(this.CustomId, opt, this.Placeholder, this.MinValues, this.MaxValues); + } + } + + /// + /// Represents a class used to build 's. + /// + public class SelectMenuOptionBuilder + { + /// + /// The maximum length of a . + /// + public const int MaxDescriptionLength = 50; + + /// + /// Gets or sets the label of the current select menu. + /// + public string Label + { + get => _label; + set + { + if (value != null) + if (value.Length > ComponentBuilder.MaxLabelLength) + throw new ArgumentException(message: $"Button label must be {ComponentBuilder.MaxLabelLength} characters or less!", paramName: nameof(Label)); + + _label = value; + } + } + + /// + /// Gets or sets the custom id of the current select menu. + /// + public string Value + { + get => _value; + set + { + if (value != null) + if (value.Length > ComponentBuilder.MaxCustomIdLength) + throw new ArgumentException(message: $"Value must be {ComponentBuilder.MaxCustomIdLength} characters or less!", paramName: nameof(Value)); + _value = value; + } + } + + /// + /// Gets or sets this menu options description. + /// + public string Description + { + get => _description; + set + { + if (value != null) + Preconditions.LessThan(value.Length, MaxDescriptionLength, nameof(Description)); + + _description = value; + } + } + + /// + /// Gets or sets the emote of this option. + /// + public IEmote Emote { get; set; } + + /// + /// Gets or sets the whether or not this option will render selected by default. + /// + public bool? Default { get; set; } + + private string _label; + private string _value; + private string _description; + + /// + /// Creates a new instance of a . + /// + public SelectMenuOptionBuilder() { } + + /// + /// Creates a new instance of a . + /// + /// The label for this option. + /// The value of this option. + public SelectMenuOptionBuilder(string label, string value) + { + this.Label = label; + this.Value = value; + } + + /// + /// Sets the field label. + /// + /// The value to set the field label to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithLabel(string label) + { + this.Label = label; + return this; + } + + /// + /// Sets the field value. + /// + /// The value to set the field value to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithValue(string value) + { + this.Value = value; + return this; + } + + /// + /// Sets the field description. + /// + /// The value to set the field description to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithDescription(string description) + { + this.Description = description; + return this; + } + + /// + /// Sets the field emote. + /// + /// The value to set the field emote to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithEmote(IEmote emote) + { + this.Emote = emote; + return this; + } + + /// + /// Sets the field default. + /// + /// The value to set the field default to. + /// + /// The current builder. + /// + public SelectMenuOptionBuilder WithDefault(bool defaultValue) + { + this.Default = defaultValue; + return this; + } + + /// + /// Builds a . + /// + /// The newly built . + public SelectMenuOption Build() + { + return new SelectMenuOption(this.Label, this.Value, this.Description, this.Emote, this.Default); + } + } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs index 9b75599c9..5fb4fc092 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs @@ -19,6 +19,11 @@ namespace Discord /// /// A clickable button /// - Button = 2 + Button = 2, + + /// + /// A select menu for picking from choices + /// + SelectMenu = 3, } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenu.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenu.cs new file mode 100644 index 000000000..70fd36398 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenu.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a select menu component defined at + /// + public class SelectMenu : IMessageComponent + { + /// + public ComponentType Type => ComponentType.SelectMenu; + + /// + /// The custom id of this Select menu that will be sent with a . + /// + public string CustomId { get; } + + /// + /// The menus options to select from. + /// + public IReadOnlyCollection Options { get; } + + /// + /// A custom placeholder text if nothing is selected, max 100 characters. + /// + public string Placeholder { get; } + + /// + /// The minimum number of items that must be chosen; default 1, min 0, max 25 + /// + public int MinValues { get; } + + /// + /// The maximum number of items that can be chosen; default 1, max 25 + /// + public int MaxValues { get; } + + internal SelectMenu(string customId, List options, string placeholder, int minValues, int maxValues) + { + this.CustomId = customId; + this.Options = options; + this.Placeholder = placeholder; + this.MinValues = minValues; + this.MaxValues = maxValues; + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs new file mode 100644 index 000000000..74da89cae --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a choice for a . + /// + public class SelectMenuOption + { + /// + /// The user-facing name of the option, max 25 characters. + /// + public string Label { get; } + + /// + /// The dev-define value of the option, max 100 characters. + /// + public string Value { get; } + + /// + /// An additional description of the option, max 50 characters. + /// + public string Description { get; } + + /// + /// A that will be displayed with this menu option. + /// + public IEmote Emote { get; } + + /// + /// Will render this option as selected by default. + /// + public bool? Default { get; } + + internal SelectMenuOption(string label, string value, string description, IEmote emote, bool? defaultValue) + { + this.Label = label; + this.Value = value; + this.Description = description; + this.Emote = emote; + this.Default = defaultValue; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs index 7fddac1cf..4de9d6fe1 100644 --- a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; @@ -7,19 +8,30 @@ using System.Threading.Tasks; namespace Discord.API { - internal class ActionRowComponent + internal class ActionRowComponent : IMessageComponent { [JsonProperty("type")] public ComponentType Type { get; set; } [JsonProperty("components")] - public List Components { get; set; } + public IMessageComponent[] Components { get; set; } internal ActionRowComponent() { } internal ActionRowComponent(Discord.ActionRowComponent c) { this.Type = c.Type; - this.Components = c.Components?.Select(x => new ButtonComponent(x)).ToList(); + this.Components = c.Components?.Select(x => + { + switch (x.Type) + { + case ComponentType.Button: + return new ButtonComponent(x as Discord.ButtonComponent); + case ComponentType.SelectMenu: + return new SelectMenuComponent(x as Discord.SelectMenu); + default: return null; + + } + }).ToArray(); } } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs index c72e8a686..b9647ba65 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandInteractionData.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Discord.API { - internal class ApplicationCommandInteractionData + internal class ApplicationCommandInteractionData : IDiscordInteractionData { [JsonProperty("id")] public ulong Id { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs index 775f78101..b9d1147a2 100644 --- a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord.API { - internal class ButtonComponent + internal class ButtonComponent : IMessageComponent { [JsonProperty("type")] public ComponentType Type { get; set; } diff --git a/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs b/src/Discord.Net.Rest/API/Common/Interaction.cs similarity index 84% rename from src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs rename to src/Discord.Net.Rest/API/Common/Interaction.cs index 6e9ebb4fb..ebbc5fc1b 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/InteractionCreated.cs +++ b/src/Discord.Net.Rest/API/Common/Interaction.cs @@ -1,4 +1,3 @@ -using Discord.API; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -6,9 +5,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Discord.API.Gateway +namespace Discord.API { - internal class InteractionCreated + [JsonConverter(typeof(Net.Converters.InteractionConverter))] + internal class Interaction { [JsonProperty("id")] public ulong Id { get; set; } @@ -20,7 +20,7 @@ namespace Discord.API.Gateway public InteractionType Type { get; set; } [JsonProperty("data")] - public Optional Data { get; set; } + public Optional Data { get; set; } [JsonProperty("guild_id")] public Optional GuildId { get; set; } @@ -42,6 +42,5 @@ namespace Discord.API.Gateway [JsonProperty("message")] public Optional Message { get; set; } - } } diff --git a/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs index cdb4e7d5c..5dc81e61e 100644 --- a/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs +++ b/src/Discord.Net.Rest/API/Common/MessageComponentInteractionData.cs @@ -7,12 +7,15 @@ using System.Threading.Tasks; namespace Discord.API { - internal class MessageComponentInteractionData + internal class MessageComponentInteractionData : IDiscordInteractionData { [JsonProperty("custom_id")] public string CustomId { get; set; } [JsonProperty("component_type")] public ComponentType ComponentType { get; set; } + + [JsonProperty("values")] + public Optional Values { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs new file mode 100644 index 000000000..e206ec11a --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class SelectMenuComponent : IMessageComponent + { + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("custom_id")] + public string CustomId { get; set; } + + [JsonProperty("options")] + public SelectMenuOption[] Options { get; set; } + + [JsonProperty("placeholder")] + public Optional Placeholder { get; set; } + + [JsonProperty("min_values")] + public int MinValues { get; set; } + + [JsonProperty("max_values")] + public int MaxValues { get; set; } + + public SelectMenuComponent() { } + + public SelectMenuComponent(Discord.SelectMenu component) + { + this.Type = component.Type; + this.CustomId = component.CustomId; + this.Options = component.Options.Select(x => new SelectMenuOption(x)).ToArray(); + this.Placeholder = component.Placeholder; + this.MinValues = component.MinValues; + this.MaxValues = component.MaxValues; + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs new file mode 100644 index 000000000..5c6f2816b --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SelectMenuOption.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API +{ + internal class SelectMenuOption + { + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("description")] + public Optional Description { get; set; } + + [JsonProperty("emoji")] + public Optional Emoji { get; set; } + + [JsonProperty("default")] + public Optional Default { get; set; } + + public SelectMenuOption() { } + + public SelectMenuOption(Discord.SelectMenuOption option) + { + this.Label = option.Label; + this.Value = option.Value; + this.Description = option.Description; + + if (option.Emote != null) + { + if (option.Emote is Emote e) + { + this.Emoji = new Emoji() + { + Name = e.Name, + Animated = e.Animated, + Id = e.Id, + }; + } + else + { + this.Emoji = new Emoji() + { + Name = option.Emote.Name + }; + } + } + + this.Default = option.Default.HasValue ? option.Default.Value : Optional.Unspecified; + } + } +} diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 9f62c45be..4ce655fcb 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -133,16 +134,49 @@ namespace Discord.Rest if (model.Components.IsSpecified) { - 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(); + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(y => + { + switch (y.Type) + { + case ComponentType.Button: + { + var parsed = (API.ButtonComponent)y; + return new Discord.ButtonComponent( + parsed.Style, + parsed.Label.GetValueOrDefault(), + parsed.Emote.IsSpecified + ? parsed.Emote.Value.Id.HasValue + ? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault()) + : new Emoji(parsed.Emote.Value.Name) + : null, + parsed.CustomId.GetValueOrDefault(), + parsed.Url.GetValueOrDefault(), + parsed.Disabled.GetValueOrDefault()); + } + case ComponentType.SelectMenu: + { + var parsed = (API.SelectMenuComponent)y; + return new SelectMenu( + parsed.CustomId, + parsed.Options.Select(z => new SelectMenuOption( + z.Label, + z.Value, + z.Description.GetValueOrDefault(), + z.Emoji.IsSpecified + ? z.Emoji.Value.Id.HasValue + ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault()) + : new Emoji(z.Emoji.Value.Name) + : null, + z.Default.ToNullable())).ToList(), + parsed.Placeholder.GetValueOrDefault(), + parsed.MinValues, + parsed.MaxValues + ); + } + default: + return null; + } + }).ToList())).ToImmutableArray(); } else Components = new List(); diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index 931c0c4c9..5981a266e 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -81,6 +81,10 @@ namespace Discord.Net.Converters //Special if (type == typeof(API.Image)) return ImageConverter.Instance; + if (typeof(IMessageComponent).IsAssignableFrom(type)) + return MessageComponentConverter.Instance; + if (type == typeof(API.Interaction)) + return InteractionConverter.Instance; //Entities var typeInfo = type.GetTypeInfo(); diff --git a/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs new file mode 100644 index 000000000..cda3b1e24 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/InteractionConverter.cs @@ -0,0 +1,67 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Net.Converters +{ + internal class InteractionConverter : JsonConverter + { + public static InteractionConverter Instance => new InteractionConverter(); + + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanConvert(Type objectType) => true; + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + var obj = JObject.Load(reader); + var interaction = new API.Interaction(); + + + // Remove the data property for manual deserialization + var result = obj.GetValue("data", StringComparison.OrdinalIgnoreCase); + result.Parent.Remove(); + + // Populate the remaining properties. + using (var subReader = obj.CreateReader()) + { + serializer.Populate(subReader, interaction); + } + + // Process the Result property + if (result != null) + { + switch (interaction.Type) + { + case InteractionType.ApplicationCommand: + { + var appCommandData = new API.ApplicationCommandInteractionData(); + serializer.Populate(result.CreateReader(), appCommandData); + interaction.Data = appCommandData; + } + break; + case InteractionType.MessageComponent: + { + var messageComponent = new API.MessageComponentInteractionData(); + serializer.Populate(result.CreateReader(), messageComponent); + interaction.Data = messageComponent; + } + break; + } + } + else + interaction.Data = Optional.Unspecified; + + return interaction; + } + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs new file mode 100644 index 000000000..2203008d6 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Net.Converters +{ + internal class MessageComponentConverter : JsonConverter + { + public static MessageComponentConverter Instance => new MessageComponentConverter(); + + public override bool CanRead => true; + public override bool CanWrite => false; + public override bool CanConvert(Type objectType) => true; + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var jsonObject = JObject.Load(reader); + var messageComponent = default(IMessageComponent); + switch ((ComponentType)jsonObject["type"].Value()) + { + case ComponentType.ActionRow: + messageComponent = new API.ActionRowComponent(); + break; + case ComponentType.Button: + messageComponent = new API.ButtonComponent(); + break; + case ComponentType.SelectMenu: + messageComponent = new API.SelectMenuComponent(); + break; + } + serializer.Populate(jsonObject.CreateReader(), messageComponent); + return messageComponent; + } + } +} diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index e08511d5d..c864a9f6b 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3224,6 +3224,11 @@ The type of the component clicked + + + The value(s) of a interaction response. + + Represends a Websocket-based recieved over the gateway. diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 4fe1cfdfe..9f7a1e524 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1865,7 +1865,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); SocketChannel channel = null; if(data.ChannelId.IsSpecified) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs index beda6eb53..f42fb58ff 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Model = Discord.API.Gateway.InteractionCreated; +using Model = Discord.API.Interaction; using DataModel = Discord.API.MessageComponentInteractionData; using Newtonsoft.Json.Linq; using Discord.Rest; @@ -29,12 +29,10 @@ namespace Discord.WebSocket : base(client, model.Id, channel) { var dataModel = model.Data.IsSpecified ? - (model.Data.Value as JToken).ToObject() + (DataModel)model.Data.Value : null; this.Data = new SocketMessageComponentData(dataModel); - - } new internal static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs index 8477432c7..45e688266 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponentData.cs @@ -22,10 +22,16 @@ namespace Discord.WebSocket /// public ComponentType Type { get; } + /// + /// The value(s) of a interaction response. + /// + public IReadOnlyCollection Values { get; } + internal SocketMessageComponentData(Model model) { this.CustomId = model.CustomId; this.Type = model.ComponentType; + this.Values = model.Values.GetValueOrDefault(); } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs index 1b94f5c83..542e8efc5 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketSlashCommand.cs @@ -4,7 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; using DataModel = Discord.API.ApplicationCommandInteractionData; -using Model = Discord.API.Gateway.InteractionCreated; +using Model = Discord.API.Interaction; namespace Discord.WebSocket { @@ -22,7 +22,7 @@ namespace Discord.WebSocket : base(client, model.Id, channel) { var dataModel = model.Data.IsSpecified ? - (model.Data.Value as JToken).ToObject(client._serializer) + (DataModel)model.Data.Value : null; ulong? guildId = null; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index a31ecc692..0876d6eb6 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Model = Discord.API.Gateway.InteractionCreated; +using Model = Discord.API.Interaction; namespace Discord.WebSocket { @@ -36,7 +36,7 @@ namespace Discord.WebSocket /// /// The data sent with this interaction. /// - public object Data { get; private set; } + public IDiscordInteractionData Data { get; private set; } /// /// The version of this interaction. diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index d0b9443a4..8ec18d49e 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,4 +1,5 @@ using Discord.Rest; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -167,16 +168,49 @@ namespace Discord.WebSocket if (model.Components.IsSpecified) { - 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(); + Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select(y => + { + switch (y.Type) + { + case ComponentType.Button: + { + var parsed = (API.ButtonComponent)y; + return new Discord.ButtonComponent( + parsed.Style, + parsed.Label.GetValueOrDefault(), + parsed.Emote.IsSpecified + ? parsed.Emote.Value.Id.HasValue + ? new Emote(parsed.Emote.Value.Id.Value, parsed.Emote.Value.Name, parsed.Emote.Value.Animated.GetValueOrDefault()) + : new Emoji(parsed.Emote.Value.Name) + : null, + parsed.CustomId.GetValueOrDefault(), + parsed.Url.GetValueOrDefault(), + parsed.Disabled.GetValueOrDefault()); + } + case ComponentType.SelectMenu: + { + var parsed = (API.SelectMenuComponent)y; + return new SelectMenu( + parsed.CustomId, + parsed.Options.Select(z => new SelectMenuOption( + z.Label, + z.Value, + z.Description.GetValueOrDefault(), + z.Emoji.IsSpecified + ? z.Emoji.Value.Id.HasValue + ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault()) + : new Emoji(z.Emoji.Value.Name) + : null, + z.Default.ToNullable())).ToList(), + parsed.Placeholder.GetValueOrDefault(), + parsed.MinValues, + parsed.MaxValues + ); + } + default: + return null; + } + }).ToList())).ToImmutableArray(); } else Components = new List(); diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml index e2c3e69a9..f629c2c29 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.xml +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.xml @@ -35,6 +35,33 @@ Sends a message to the channel for this webhook. Returns the ID of the created message. + + + Modifies a message posted using this webhook. + + + This method can only modify messages that were sent using the same webhook. + + ID of the modified message. + A delegate containing the properties to modify the message with. + The options to be used when sending the request. + + A task that represents the asynchronous modification operation. + + + + + Deletes a message posted using this webhook. + + + This method can only delete messages that were sent using the same webhook. + + ID of the deleted message. + The options to be used when sending the request. + + A task that represents the asynchronous deletion operation. + + Sends a message to the channel for this webhook with an attachment. Returns the ID of the created message. @@ -49,6 +76,29 @@ Deletes this webhook from Discord and disposes the client. + + + Properties that are used to modify an Webhook message with the specified changes. + + + + + Gets or sets the content of the message. + + + This must be less than the constant defined by . + + + + + Gets or sets the embed array that the message should display. + + + + + Gets or sets the allowed mentions of the message. + + Could not find a webhook with the supplied credentials.