From 3c9be4a7aeba36c88d51b9c209d4728ac4b0fc8a Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sun, 15 Dec 2024 19:19:36 +0300 Subject: [PATCH 01/15] oh well. at least it runs --- .../MessageComponents/ActionRowComponent.cs | 36 +++++++++---------- .../Builders/ButtonBuilder.cs | 8 +++-- .../Builders/ComponentBuilder.cs | 2 +- .../Builders/SelectMenuBuilder.cs | 8 +++-- .../Builders/TextInputBuilder.cs | 7 ++-- .../MessageComponents/ButtonComponent.cs | 8 +++-- .../MessageComponents/ComponentType.cs | 8 +++++ .../IInteractableComponent.cs | 9 +++++ .../MessageComponents/IMessageComponent.cs | 22 +++++------- .../MessageComponents/SelectMenuComponent.cs | 12 ++++--- .../MessageComponents/TextInputComponent.cs | 10 ++++-- .../Entities/Interactions/Modals/Modal.cs | 11 +----- .../Interactions/Modals/ModalBuilder.cs | 8 ++--- .../API/Common/ActionRowComponent.cs | 6 +++- .../API/Common/ButtonComponent.cs | 11 ++++-- .../API/Common/SelectMenuComponent.cs | 8 ++++- .../API/Common/TextInputComponent.cs | 9 ++++- .../API/Common/UnfurledMediaItem.cs | 9 +++++ .../RestMessageComponentData.cs | 2 +- .../Interactions/Modals/RestModalData.cs | 5 +-- .../Entities/Messages/RestMessage.cs | 13 ++++--- .../SocketMessageComponentData.cs | 4 +-- .../Interaction/Modals/SocketModalData.cs | 5 +-- .../Entities/Messages/SocketMessage.cs | 4 ++- 24 files changed, 143 insertions(+), 82 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs index 202a5687ff..bca11c4824 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs @@ -1,27 +1,27 @@ using System.Collections.Generic; -namespace Discord +namespace Discord; + +/// <summary> +/// Represents a <see cref="IMessageComponent"/> Row for child components to live in. +/// </summary> +public class ActionRowComponent : IMessageComponent { - /// <summary> - /// Represents a <see cref="IMessageComponent"/> Row for child components to live in. - /// </summary> - public class ActionRowComponent : IMessageComponent - { - /// <inheritdoc/> - public ComponentType Type => ComponentType.ActionRow; + /// <inheritdoc/> + public ComponentType Type => ComponentType.ActionRow; - /// <summary> - /// Gets the child components in this row. - /// </summary> - public IReadOnlyCollection<IMessageComponent> Components { get; internal set; } + /// <inheritdoc/> + public int? Id { get; internal set; } - internal ActionRowComponent() { } + /// <summary> + /// Gets the child components in this row. + /// </summary> + public IReadOnlyCollection<IMessageComponent> Components { get; internal set; } - internal ActionRowComponent(List<IMessageComponent> components) - { - Components = components; - } + internal ActionRowComponent() { } - string IMessageComponent.CustomId => null; + internal ActionRowComponent(List<IMessageComponent> components) + { + Components = components; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs index 4522eb0629..058f4e473c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs @@ -74,6 +74,8 @@ public string CustomId /// </remarks> public ulong? SkuId { get; set; } + public int? Id { get; set; } + private string _label; private string _customId; @@ -92,7 +94,7 @@ public ButtonBuilder() { } /// <param name="emote">The emote of this button.</param> /// <param name="isDisabled">Disabled this button or not.</param> /// <param name="skuId">The sku id of this button.</param> - public ButtonBuilder(string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, string url = null, IEmote emote = null, bool isDisabled = false, ulong? skuId = null) + public ButtonBuilder(string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, string url = null, IEmote emote = null, bool isDisabled = false, ulong? skuId = null, int? id = null) { CustomId = customId; Style = style; @@ -101,6 +103,7 @@ public ButtonBuilder(string label = null, string customId = null, ButtonStyle st IsDisabled = isDisabled; Emote = emote; SkuId = skuId; + Id = id; } /// <summary> @@ -115,6 +118,7 @@ public ButtonBuilder(ButtonComponent button) IsDisabled = button.IsDisabled; Emote = button.Emote; SkuId = button.SkuId; + Id = button.Id; } /// <summary> @@ -316,6 +320,6 @@ public ButtonComponent Build() break; } - return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled, SkuId); + return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled, SkuId, Id); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs index eeb7db8596..b4c60ba9f3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs @@ -98,7 +98,7 @@ public ComponentBuilder RemoveComponentsOfType(ComponentType t) /// <returns>The current builder.</returns> public ComponentBuilder RemoveComponent(string customId) { - this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c.CustomId == customId)); + this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c is IInteractableComponent ic && ic.CustomId == customId)); return this; } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs index 000a1c6203..41c49e93f0 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs @@ -136,6 +136,8 @@ public List<SelectMenuDefaultValue> DefaultValues } } + public int? Id { get; set; } + private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>(); private int _minValues = 1; private int _maxValues = 1; @@ -163,6 +165,7 @@ public SelectMenuBuilder(SelectMenuComponent selectMenu) .Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)) .ToList(); DefaultValues = selectMenu.DefaultValues?.ToList(); + Id = selectMenu.Id; } /// <summary> @@ -177,7 +180,7 @@ public SelectMenuBuilder(SelectMenuComponent selectMenu) /// <param name="type">The <see cref="ComponentType"/> of this select menu.</param> /// <param name="channelTypes">The types of channels this menu can select (only valid on <see cref="ComponentType.ChannelSelect"/>s)</param> public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, int maxValues = 1, int minValues = 1, - bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null, List<SelectMenuDefaultValue> defaultValues = null) + bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null, List<SelectMenuDefaultValue> defaultValues = null, int? id = null) { CustomId = customId; Options = options; @@ -188,6 +191,7 @@ public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options Type = type; ChannelTypes = channelTypes ?? new(); DefaultValues = defaultValues ?? new(); + Id = id; } /// <summary> @@ -401,6 +405,6 @@ public SelectMenuComponent Build() { var options = Options?.Select(x => x.Build()).ToList(); - return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, ChannelTypes, DefaultValues); + return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, Id, ChannelTypes, DefaultValues); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs index b675980bf6..2efe92b63d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs @@ -99,6 +99,8 @@ public int? MaxLength /// </summary> public bool? Required { get; set; } + public int? Id { get; set; } + /// <summary> /// Gets or sets the default value of the text input. /// </summary> @@ -140,7 +142,7 @@ public string Value /// <param name="maxLength">The text input's maximum length.</param> /// <param name="required">The text input's required value.</param> public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null, - int? minLength = null, int? maxLength = null, bool? required = null, string value = null) + int? minLength = null, int? maxLength = null, bool? required = null, string value = null, int? id = null) { Label = label; Style = style; @@ -150,6 +152,7 @@ public TextInputBuilder(string label, string customId, TextInputStyle style = Te MaxLength = maxLength; Required = required; Value = value; + Id = id; } /// <summary> @@ -257,6 +260,6 @@ public TextInputComponent Build() if (Style is TextInputStyle.Short && Value?.Any(x => x == '\n') is true) throw new ArgumentException($"Value must not contain new line characters when style is {TextInputStyle.Short}.", nameof(Value)); - return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value); + return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value, Id); } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs index c387f9ad53..5a850e2015 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs @@ -3,11 +3,14 @@ namespace Discord; /// <summary> /// Represents a <see cref="IMessageComponent"/> Button. /// </summary> -public class ButtonComponent : IMessageComponent +public class ButtonComponent : IInteractableComponent { /// <inheritdoc/> public ComponentType Type => ComponentType.Button; + /// <inheritdoc/> + public int? Id { get; } + /// <summary> /// Gets 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> @@ -56,9 +59,10 @@ public class ButtonComponent : IMessageComponent public ButtonBuilder ToBuilder() => new (Label, CustomId, Style, Url, Emote, IsDisabled); - internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled, ulong? skuId) + internal ButtonComponent(ButtonStyle style, string label, IEmote emote, string customId, string url, bool isDisabled, ulong? skuId, int? id) { Style = style; + Id = id; Label = label; Emote = emote; CustomId = customId; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs index 0ad3f741a1..3e80deada9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs @@ -44,5 +44,13 @@ public enum ComponentType /// A select menu for picking from channels. /// </summary> ChannelSelect = 8, + + TextDisplay = 10, + + MediaGallery = 12, + + File = 13, + + Separator = 14 } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs new file mode 100644 index 0000000000..61c2265889 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs @@ -0,0 +1,9 @@ +namespace Discord; + +public interface IInteractableComponent : IMessageComponent +{ + /// <summary> + /// Gets the custom id of the component if possible; otherwise <see langword="null"/>. + /// </summary> + string CustomId { get; } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs index 9366a44d69..6bc1c07d0d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs @@ -1,18 +1,14 @@ -namespace Discord +namespace Discord; + +/// <summary> +/// Represents a message component on a message. +/// </summary> +public interface IMessageComponent { /// <summary> - /// Represents a message component on a message. + /// Gets the <see cref="ComponentType"/> of this Message Component. /// </summary> - public interface IMessageComponent - { - /// <summary> - /// Gets the <see cref="ComponentType"/> of this Message Component. - /// </summary> - ComponentType Type { get; } + ComponentType Type { get; } - /// <summary> - /// Gets the custom id of the component if possible; otherwise <see langword="null"/>. - /// </summary> - string CustomId { get; } - } + int? Id { get; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs index 39631deea5..7d1a2558e3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs @@ -7,11 +7,14 @@ namespace Discord /// <summary> /// Represents a select menu component defined at <see href="https://discord.com/developers/docs/interactions/message-components#select-menu-object"/> /// </summary> - public class SelectMenuComponent : IMessageComponent + public class SelectMenuComponent : IInteractableComponent { /// <inheritdoc/> public ComponentType Type { get; } + /// <inheritdoc/> + public int? Id { get; } + /// <inheritdoc/> public string CustomId { get; } @@ -67,7 +70,7 @@ public SelectMenuBuilder ToBuilder() DefaultValues.ToList()); internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues, - bool disabled, ComponentType type, IEnumerable<ChannelType> channelTypes = null, IEnumerable<SelectMenuDefaultValue> defaultValues = null) + bool disabled, ComponentType type, int? id, IEnumerable<ChannelType> channelTypes = null, IEnumerable<SelectMenuDefaultValue> defaultValues = null) { CustomId = customId; Options = options; @@ -76,8 +79,9 @@ internal SelectMenuComponent(string customId, List<SelectMenuOption> options, st MaxValues = maxValues; IsDisabled = disabled; Type = type; - ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>(); - DefaultValues = defaultValues?.ToArray() ?? Array.Empty<SelectMenuDefaultValue>(); + Id = id; + ChannelTypes = channelTypes?.ToArray() ?? []; + DefaultValues = defaultValues?.ToArray() ?? []; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs index e2da1132c8..c462d0c45c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs @@ -1,9 +1,11 @@ +using Newtonsoft.Json; + namespace Discord { /// <summary> /// Represents a <see cref="IMessageComponent"/> text input. /// </summary> - public class TextInputComponent : IMessageComponent + public class TextInputComponent : IInteractableComponent { /// <inheritdoc/> public ComponentType Type => ComponentType.TextInput; @@ -11,6 +13,9 @@ public class TextInputComponent : IMessageComponent /// <inheritdoc/> public string CustomId { get; } + /// <inheritdoc/> + public int? Id { get; } + /// <summary> /// Gets the label of the component; this is the text shown above it. /// </summary> @@ -47,7 +52,7 @@ public class TextInputComponent : IMessageComponent public string Value { get; } internal TextInputComponent(string customId, string label, string placeholder, int? minLength, int? maxLength, - TextInputStyle style, bool? required, string value) + TextInputStyle style, bool? required, string value, int? id) { CustomId = customId; Label = label; @@ -57,6 +62,7 @@ internal TextInputComponent(string customId, string label, string placeholder, i Style = style; Required = required; Value = value; + Id = id; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs index a435d33ef1..6062db0cc0 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs @@ -1,19 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// <summary> /// Represents a modal interaction. /// </summary> - public class Modal : IMessageComponent + public class Modal { - /// <inheritdoc/> - public ComponentType Type => throw new NotSupportedException("Modals do not have a component type."); - /// <summary> /// Gets the title of the modal. /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs index c534e9f385..98a37e2a8e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs @@ -114,18 +114,18 @@ public ModalBuilder AddComponents(List<IMessageComponent> components, int row) /// Gets a <typeparamref name="TMessageComponent"/> by the specified <paramref name="customId"/>. /// </summary> /// <typeparam name="TMessageComponent">The type of the component to get.</typeparam> - /// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to get.</param> + /// <param name="customId">The <see cref="IInteractableComponent.CustomId"/> of the component to get.</param> /// <returns> /// The component of type <typeparamref name="TMessageComponent"/> that was found, <see langword="null"/> otherwise. /// </returns> public TMessageComponent GetComponent<TMessageComponent>(string customId) - where TMessageComponent : class, IMessageComponent + where TMessageComponent : class, IInteractableComponent { Preconditions.NotNull(customId, nameof(customId)); return Components.ActionRows ?.SelectMany(r => r.Components.OfType<TMessageComponent>()) - .FirstOrDefault(c => c?.CustomId == customId); + .FirstOrDefault(c => c is IInteractableComponent ic && ic?.CustomId == customId); } /// <summary> @@ -185,7 +185,7 @@ public ModalBuilder RemoveComponent(string customId) { Preconditions.NotNull(customId, nameof(customId)); - Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c.CustomId == customId)); + Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c is IInteractableComponent ic && ic.CustomId == customId)); return this; } diff --git a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs index e97ca71d65..4ee6311ee3 100644 --- a/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ActionRowComponent.cs @@ -8,6 +8,9 @@ internal class ActionRowComponent : IMessageComponent [JsonProperty("type")] public ComponentType Type { get; set; } + [JsonProperty("id")] + public Optional<int> Id { get; set; } + [JsonProperty("components")] public IMessageComponent[] Components { get; set; } @@ -29,9 +32,10 @@ internal ActionRowComponent(Discord.ActionRowComponent c) _ => null }; }).ToArray(); + Id = c.Id ?? Optional<int>.Unspecified; } [JsonIgnore] - string IMessageComponent.CustomId => null; + int? IMessageComponent.Id => Id.ToNullable(); } } diff --git a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs index f820fc026d..84f6e0c825 100644 --- a/src/Discord.Net.Rest/API/Common/ButtonComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ButtonComponent.cs @@ -2,11 +2,14 @@ namespace Discord.API { - internal class ButtonComponent : IMessageComponent + internal class ButtonComponent : IInteractableComponent { [JsonProperty("type")] public ComponentType Type { get; set; } + [JsonProperty("id")] + public Optional<int> Id { get; set; } + [JsonProperty("style")] public ButtonStyle Style { get; set; } @@ -39,6 +42,7 @@ public ButtonComponent(Discord.ButtonComponent c) Url = c.Url; Disabled = c.IsDisabled; SkuId = c.SkuId ?? Optional<ulong>.Unspecified; + Id = c.Id ?? Optional<int>.Unspecified; if (c.Emote != null) { @@ -62,6 +66,9 @@ public ButtonComponent(Discord.ButtonComponent c) } [JsonIgnore] - string IMessageComponent.CustomId => CustomId.GetValueOrDefault(); + string IInteractableComponent.CustomId => CustomId.GetValueOrDefault(); + + [JsonIgnore] + int? IMessageComponent.Id => Id.ToNullable(); } } diff --git a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs index c7a69568cd..f605d75b84 100644 --- a/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs +++ b/src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs @@ -3,11 +3,14 @@ namespace Discord.API { - internal class SelectMenuComponent : IMessageComponent + internal class SelectMenuComponent : IInteractableComponent { [JsonProperty("type")] public ComponentType Type { get; set; } + [JsonProperty("id")] + public Optional<int> Id { get; set; } + [JsonProperty("custom_id")] public string CustomId { get; set; } @@ -52,5 +55,8 @@ public SelectMenuComponent(Discord.SelectMenuComponent component) ChannelTypes = component.ChannelTypes.ToArray(); DefaultValues = component.DefaultValues.Select(x => new SelectMenuDefaultValue {Id = x.Id, Type = x.Type}).ToArray(); } + + [JsonIgnore] + int? IMessageComponent.Id => Id.ToNullable(); } } diff --git a/src/Discord.Net.Rest/API/Common/TextInputComponent.cs b/src/Discord.Net.Rest/API/Common/TextInputComponent.cs index a475345fcf..458cdaca56 100644 --- a/src/Discord.Net.Rest/API/Common/TextInputComponent.cs +++ b/src/Discord.Net.Rest/API/Common/TextInputComponent.cs @@ -2,11 +2,14 @@ namespace Discord.API { - internal class TextInputComponent : IMessageComponent + internal class TextInputComponent : IInteractableComponent { [JsonProperty("type")] public ComponentType Type { get; set; } + [JsonProperty("id")] + public Optional<int> Id { get; set; } + [JsonProperty("style")] public TextInputStyle Style { get; set; } @@ -44,6 +47,10 @@ public TextInputComponent(Discord.TextInputComponent component) MaxLength = component.MaxLength ?? Optional<int>.Unspecified; Required = component.Required ?? Optional<bool>.Unspecified; Value = component.Value ?? Optional<string>.Unspecified; + Id = component.Id ?? Optional<int>.Unspecified; } + + [JsonIgnore] + int? IMessageComponent.Id => Id.ToNullable(); } } diff --git a/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs b/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs new file mode 100644 index 0000000000..60635c41a7 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +public class UnfurledMediaItem +{ + [JsonProperty("url")] + public string Url { get; set; } +} diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs index bc44d0df55..f72eceecb4 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponentData.cs @@ -93,7 +93,7 @@ internal RestMessageComponentData(Model model, BaseDiscordClient discord, IGuild } } - internal RestMessageComponentData(IMessageComponent component, BaseDiscordClient discord, IGuild guild) + internal RestMessageComponentData(IInteractableComponent component, BaseDiscordClient discord, IGuild guild) { CustomId = component.CustomId; Type = component.Type; diff --git a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModalData.cs b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModalData.cs index c1f329e7d3..1831d4b51a 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModalData.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModalData.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Linq; -using DataModel = Discord.API.MessageComponentInteractionData; -using InterationModel = Discord.API.Interaction; using Model = Discord.API.ModalInteractionData; namespace Discord.Rest @@ -26,7 +23,7 @@ internal RestModalData(Model model, BaseDiscordClient discord, IGuild guild) { CustomId = model.CustomId; Components = model.Components - .SelectMany(x => x.Components) + .SelectMany(x => x.Components.OfType<IInteractableComponent>()) .Select(x => new RestMessageComponentData(x, discord, guild)) .ToArray(); } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 132f1c97d7..d4fddd038e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -191,7 +191,8 @@ internal virtual void Update(Model model) parsed.CustomId.GetValueOrDefault(), parsed.Url.GetValueOrDefault(), parsed.Disabled.GetValueOrDefault(), - parsed.SkuId.ToNullable()); + parsed.SkuId.ToNullable(), + parsed.Id.ToNullable()); } case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect: { @@ -213,6 +214,7 @@ internal virtual void Update(Model model) parsed.MaxValues, parsed.Disabled, parsed.Type, + parsed.Id.ToNullable(), parsed.ChannelTypes.GetValueOrDefault(), parsed.DefaultValues.IsSpecified ? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type)) @@ -236,15 +238,16 @@ internal virtual void Update(Model model) if (value.Length > 0) { var reactions = ImmutableArray.CreateBuilder<RestReaction>(value.Length); - for (int i = 0; i < value.Length; i++) - reactions.Add(RestReaction.Create(value[i])); + foreach (var t in value) + reactions.Add(RestReaction.Create(t)); + _reactions = reactions.ToImmutable(); } else - _reactions = ImmutableArray.Create<RestReaction>(); + _reactions = []; } else - _reactions = ImmutableArray.Create<RestReaction>(); + _reactions = []; if (model.Interaction.IsSpecified) { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs index b193b77f38..718cc3b130 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs @@ -90,13 +90,13 @@ internal SocketMessageComponentData(Model model, DiscordSocketClient discord, Cl } } - internal SocketMessageComponentData(IMessageComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser) + internal SocketMessageComponentData(IInteractableComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser) { CustomId = component.CustomId; Type = component.Type; Value = component.Type == ComponentType.TextInput - ? (component as API.TextInputComponent).Value.Value + ? ((TextInputComponent)component).Value : null; if (component is API.SelectMenuComponent select) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs index 52c7615b2d..c20562ecc7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Linq; -using DataModel = Discord.API.MessageComponentInteractionData; -using InterationModel = Discord.API.Interaction; using Model = Discord.API.ModalInteractionData; namespace Discord.WebSocket @@ -26,7 +23,7 @@ internal SocketModalData(Model model, DiscordSocketClient discord, ClientState s { CustomId = model.CustomId; Components = model.Components - .SelectMany(x => x.Components) + .SelectMany(x => x.Components.OfType<IInteractableComponent>()) .Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser)) .ToArray(); } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index c778137e01..878940f7cf 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -224,7 +224,8 @@ internal virtual void Update(ClientState state, Model model) parsed.CustomId.GetValueOrDefault(), parsed.Url.GetValueOrDefault(), parsed.Disabled.GetValueOrDefault(), - parsed.SkuId.ToNullable()); + parsed.SkuId.ToNullable(), + parsed.Id.ToNullable()); } case ComponentType.SelectMenu: { @@ -246,6 +247,7 @@ internal virtual void Update(ClientState state, Model model) parsed.MaxValues, parsed.Disabled, parsed.Type, + parsed.Id.ToNullable(), parsed.ChannelTypes.GetValueOrDefault(), parsed.DefaultValues.IsSpecified ? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type)) From 2f501adb33b2c41d0ce94b04445da03fe502bdd3 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sun, 15 Dec 2024 21:11:45 +0300 Subject: [PATCH 02/15] well that's some breakings --- .../Interactions/IDiscordInteraction.cs | 53 ++++++---- .../Builders/ComponentBuilder.cs | 10 +- .../Builders/TextDisplayComponentBuilder.cs | 18 ++++ .../MessageComponents/MessageComponent.cs | 35 ++++--- .../MessageComponents/SeparatorSpacingSize.cs | 8 ++ .../MessageComponents/TextDisplayComponent.cs | 16 +++ .../Entities/Messages/MessageFlags.cs | 2 + .../InteractionModuleBase.cs | 60 ++++++------ .../RestInteractionModuleBase.cs | 4 +- .../API/Common/ForumThreadMessage.cs | 2 +- .../API/Common/InteractionCallbackData.cs | 2 +- .../API/Common/TextDisplayComponent.cs | 26 +++++ .../API/Rest/CreateMessageParams.cs | 2 +- .../API/Rest/CreateMultipartPostAsync.cs | 2 +- .../API/Rest/CreatePostParams.cs | 34 +++---- .../API/Rest/CreateWebhookMessageParams.cs | 2 +- .../Rest/ModifyInteractionResponseParams.cs | 2 +- .../API/Rest/ModifyMessageParams.cs | 32 +++--- .../API/Rest/ModifyWebhookMessageParams.cs | 27 ++--- .../API/Rest/UploadFileParams.cs | 2 +- .../API/Rest/UploadInteractionFileParams.cs | 6 +- .../API/Rest/UploadWebhookFileParams.cs | 2 +- .../Entities/Channels/ChannelHelper.cs | 4 +- .../Entities/Channels/ThreadHelper.cs | 4 +- .../CommandBase/RestCommandBase.cs | 84 +++++++++------- .../Interactions/InteractionHelper.cs | 10 +- .../MessageComponents/RestMessageComponent.cs | 74 +++++++------- .../Entities/Interactions/Modals/RestModal.cs | 96 ++++++++++-------- .../Entities/Interactions/RestInteraction.cs | 45 +++++---- .../Interactions/RestPingInteraction.cs | 12 +-- .../RestAutocompleteInteraction.cs | 12 +-- .../Entities/Messages/MessageHelper.cs | 12 +-- .../Entities/Messages/RestMessage.cs | 71 ++------------ .../Entities/Messages/RestUserMessage.cs | 20 ++-- .../Extensions/MessageComponentExtension.cs | 98 +++++++++++++++++++ .../Converters/MessageComponentConverter.cs | 3 + .../SocketMessageComponent.cs | 85 +++++++++------- .../Interaction/Modals/SocketModal.cs | 78 +++++++++------ .../SocketAutocompleteInteraction.cs | 8 +- .../SocketBaseCommand/SocketCommandBase.cs | 61 +++++++----- .../Entities/Interaction/SocketInteraction.cs | 56 +++++------ .../Entities/Messages/SocketMessage.cs | 68 ++----------- .../WebhookClientHelper.cs | 10 +- 43 files changed, 705 insertions(+), 553 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/TextDisplayComponent.cs create mode 100644 src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 5b04de590b..d7a530bba8 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -127,7 +127,7 @@ public interface IDiscordInteraction : ISnowflakeEntity /// A task that represents an asynchronous send operation for delivering the message. /// </returns> Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, - MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Responds to this interaction with a file attachment. @@ -149,7 +149,8 @@ Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, /// </returns> #if NETCOREAPP3_0_OR_GREATER async Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(fileStream, fileName)) { @@ -158,7 +159,8 @@ async Task RespondWithFileAsync(Stream fileStream, string fileName, string text } #else Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null + , MessageFlags flags = MessageFlags.None); #endif /// <summary> /// Responds to this interaction with a file attachment. @@ -180,7 +182,8 @@ Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null /// </returns> #if NETCOREAPP3_0_OR_GREATER async Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(filePath, fileName)) { @@ -189,7 +192,8 @@ async Task RespondWithFileAsync(string filePath, string fileName = null, string } #else Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None); #endif /// <summary> /// Responds to this interaction with a file attachment. @@ -210,11 +214,13 @@ Task RespondWithFileAsync(string filePath, string fileName = null, string text = /// </returns> #if NETCOREAPP3_0_OR_GREATER Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => RespondWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None) + => RespondWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); #else Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None); #endif /// <summary> /// Responds to this interaction with a collection of file attachments. @@ -234,7 +240,9 @@ Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] /// contains the sent message. /// </returns> Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None); + /// <summary> /// Sends a followup message for this interaction. /// </summary> @@ -252,7 +260,9 @@ Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text /// contains the sent message. /// </returns> Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None); + /// <summary> /// Sends a followup message for this interaction. /// </summary> @@ -273,17 +283,19 @@ Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool /// </returns> #if NETCOREAPP3_0_OR_GREATER async Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(fileStream, fileName)) { - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } #else Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None); #endif + /// <summary> /// Sends a followup message for this interaction. /// </summary> @@ -304,16 +316,17 @@ Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, str /// </returns> #if NETCOREAPP3_0_OR_GREATER async Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(filePath, fileName)) { - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } #else Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); #endif /// <summary> /// Sends a followup message for this interaction. @@ -334,11 +347,11 @@ Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null /// </returns> #if NETCOREAPP3_0_OR_GREATER Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); #else Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); #endif /// <summary> /// Sends a followup message for this interaction. @@ -358,7 +371,7 @@ Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text /// contains the sent message. /// </returns> Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Gets the original response for this interaction. /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs index b4c60ba9f3..ce11bdee9d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs @@ -67,13 +67,18 @@ internal void AddComponent(IMessageComponent component, int row) { switch (component) { + case TextDisplayComponent textDisplay: + break; + case ButtonComponent button: WithButton(button.Label, button.CustomId, button.Style, button.Emote, button.Url, button.IsDisabled, row); break; + case ActionRowComponent actionRow: foreach (var cmp in actionRow.Components) AddComponent(cmp, row); break; + case SelectMenuComponent menu: WithSelectMenu(menu.CustomId, menu.Options?.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)).ToList(), menu.Placeholder, menu.MinValues, menu.MaxValues, menu.IsDisabled, row); break; @@ -247,10 +252,7 @@ public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) if (_actionRows == null) { - _actionRows = new List<ActionRowBuilder> - { - new ActionRowBuilder().AddComponent(builtButton) - }; + _actionRows = [new ActionRowBuilder().AddComponent(builtButton)]; } else { diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs new file mode 100644 index 0000000000..28d6fe41bf --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs @@ -0,0 +1,18 @@ +namespace Discord; + +public class TextDisplayComponentBuilder +{ + public int? Id { get; set; } + + private string _content; + public string Content + { + get => _content; + set => _content = value; + } + + public TextDisplayComponent Build() + { + return new(Id, _content); + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs index 7205886816..18946a7380 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs @@ -1,26 +1,25 @@ using System.Collections.Generic; -namespace Discord +namespace Discord; + +/// <summary> +/// Represents a component object used to send components with messages. +/// </summary> +public class MessageComponent { /// <summary> - /// Represents a component object used to send components with messages. + /// Gets the components to be used in a message. /// </summary> - public class MessageComponent - { - /// <summary> - /// Gets the components to be used in a message. - /// </summary> - public IReadOnlyCollection<ActionRowComponent> Components { get; } + public IReadOnlyCollection<IMessageComponent> Components { get; } - internal MessageComponent(List<ActionRowComponent> components) - { - Components = components; - } - - /// <summary> - /// Returns a empty <see cref="MessageComponent"/>. - /// </summary> - internal static MessageComponent Empty - => new MessageComponent(new List<ActionRowComponent>()); + internal MessageComponent(List<IMessageComponent> components) + { + Components = components; } + + /// <summary> + /// Returns a empty <see cref="MessageComponent"/>. + /// </summary> + internal static MessageComponent Empty + => new([]); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs new file mode 100644 index 0000000000..405b08c58d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs @@ -0,0 +1,8 @@ +namespace Discord; + +public enum SeparatorSpacingSize +{ + Small = 1, + + Large = 2 +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs new file mode 100644 index 0000000000..1829804f59 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs @@ -0,0 +1,16 @@ +namespace Discord; + +public class TextDisplayComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.TextDisplay; + + public int? Id { get; } + + public string Content { get; } + + internal TextDisplayComponent(int? id, string content) + { + Id = id; + Content = content; + } +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs index 10f1aeba16..51f7d89fc9 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs @@ -57,5 +57,7 @@ public enum MessageFlags /// This message is a voice message. /// </summary> VoiceMessage = 1 << 13, + + UiKitComponents = 1 << 15, } } diff --git a/src/Discord.Net.Interactions/InteractionModuleBase.cs b/src/Discord.Net.Interactions/InteractionModuleBase.cs index 62e5c3ab03..5b9b90f35a 100644 --- a/src/Discord.Net.Interactions/InteractionModuleBase.cs +++ b/src/Discord.Net.Interactions/InteractionModuleBase.cs @@ -44,55 +44,55 @@ internal void SetContext(IInteractionContext context) protected virtual Task DeferAsync(bool ephemeral = false, RequestOptions options = null) => Context.Interaction.DeferAsync(ephemeral, options); - /// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.RespondAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null) - => Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.RespondWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.RespondWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.RespondWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.RespondWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.RespondWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.RespondWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.FollowupAsync(string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task<IUserMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null) - => Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(Stream, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task<IUserMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(string, string, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task<IUserMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.FollowupWithFileAsync(FileAttachment, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task<IUserMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); - /// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties)"/> + /// <inheritdoc cref="IDiscordInteraction.FollowupWithFilesAsync(IEnumerable{FileAttachment}, string, Embed[], bool, bool, AllowedMentions, MessageComponent, Embed, RequestOptions, PollProperties, MessageFlags)"/> protected virtual Task<IUserMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => Context.Interaction.FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); /// <inheritdoc cref="IMessageChannel.SendMessageAsync(string, bool, Embed, RequestOptions, AllowedMentions, MessageReference, MessageComponent, ISticker[], Embed[], MessageFlags, PollProperties)"/> protected virtual Task<IUserMessage> ReplyAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, diff --git a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs index 1778b7efec..cdcc4c89d6 100644 --- a/src/Discord.Net.Interactions/RestInteractionModuleBase.cs +++ b/src/Discord.Net.Interactions/RestInteractionModuleBase.cs @@ -43,8 +43,8 @@ protected override Task DeferAsync(bool ephemeral = false, RequestOptions option /// A Task representing the operation of creating the interaction response. /// </returns> /// <exception cref="InvalidOperationException">Thrown if the interaction isn't a type of <see cref="RestInteraction"/>.</exception> - protected override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null) - => HandleInteractionAsync(x => x.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll)); + protected override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent components = null, Embed embed = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => HandleInteractionAsync(x => x.Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags)); /// <summary> /// Responds to the interaction with a modal. diff --git a/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs index c64920e33a..8874324677 100644 --- a/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs +++ b/src/Discord.Net.Rest/API/Common/ForumThreadMessage.cs @@ -17,7 +17,7 @@ internal class ForumThreadMessage public Optional<AllowedMentions> AllowedMentions { get; set; } [JsonProperty("components")] - public Optional<API.ActionRowComponent[]> Components { get; set; } + public Optional<IMessageComponent[]> Components { get; set; } [JsonProperty("sticker_ids")] public Optional<ulong[]> Stickers { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs index 0b3302dde0..2596c490da 100644 --- a/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs +++ b/src/Discord.Net.Rest/API/Common/InteractionCallbackData.cs @@ -21,7 +21,7 @@ internal class InteractionCallbackData public Optional<MessageFlags> Flags { get; set; } [JsonProperty("components")] - public Optional<ActionRowComponent[]> Components { get; set; } + public Optional<IMessageComponent[]> Components { get; set; } [JsonProperty("choices")] public Optional<ApplicationCommandOptionChoice[]> Choices { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/TextDisplayComponent.cs b/src/Discord.Net.Rest/API/Common/TextDisplayComponent.cs new file mode 100644 index 0000000000..04d66e06c9 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/TextDisplayComponent.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class TextDisplayComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } + + public TextDisplayComponent() { } + + public TextDisplayComponent(Discord.TextDisplayComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + Content = component.Content; + } + + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index e22f8fbd47..42174ab944 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -24,7 +24,7 @@ internal class CreateMessageParams public Optional<MessageReference> MessageReference { get; set; } [JsonProperty("components")] - public Optional<API.ActionRowComponent[]> Components { get; set; } + public Optional<IMessageComponent[]> Components { get; set; } [JsonProperty("sticker_ids")] public Optional<ulong[]> Stickers { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs index 85cffe13f4..710e14ac42 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMultipartPostAsync.cs @@ -24,7 +24,7 @@ internal class CreateMultipartPostAsync public Optional<string> Content { get; set; } public Optional<Embed[]> Embeds { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; } - public Optional<ActionRowComponent[]> MessageComponent { get; set; } + public Optional<IMessageComponent[]> MessageComponent { get; set; } public Optional<MessageFlags?> Flags { get; set; } public Optional<ulong[]> Stickers { get; set; } public Optional<ulong[]> TagIds { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs b/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs index d74678f631..f568cb9dde 100644 --- a/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreatePostParams.cs @@ -1,28 +1,22 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace Discord.API.Rest +namespace Discord.API.Rest; + +internal class CreatePostParams { - internal class CreatePostParams - { - // thread - [JsonProperty("name")] - public string Title { get; set; } + // thread + [JsonProperty("name")] + public string Title { get; set; } - [JsonProperty("auto_archive_duration")] - public ThreadArchiveDuration ArchiveDuration { get; set; } + [JsonProperty("auto_archive_duration")] + public ThreadArchiveDuration ArchiveDuration { get; set; } - [JsonProperty("rate_limit_per_user")] - public Optional<int?> Slowmode { get; set; } + [JsonProperty("rate_limit_per_user")] + public Optional<int?> Slowmode { get; set; } - [JsonProperty("message")] - public ForumThreadMessage Message { get; set; } + [JsonProperty("message")] + public ForumThreadMessage Message { get; set; } - [JsonProperty("applied_tags")] - public Optional<ulong[]> Tags { get; set; } - } + [JsonProperty("applied_tags")] + public Optional<ulong[]> Tags { get; set; } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 52dd1806dc..0258425c7b 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -37,7 +37,7 @@ internal class CreateWebhookMessageParams public Optional<MessageFlags> Flags { get; set; } [JsonProperty("components")] - public Optional<API.ActionRowComponent[]> Components { get; set; } + public Optional<IMessageComponent[]> Components { get; set; } [JsonProperty("file")] public Optional<MultipartFile> File { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs index a2c7cbee6b..2afc464132 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyInteractionResponseParams.cs @@ -14,7 +14,7 @@ internal class ModifyInteractionResponseParams public Optional<AllowedMentions> AllowedMentions { get; set; } [JsonProperty("components")] - public Optional<ActionRowComponent[]> Components { get; set; } + public Optional<IMessageComponent[]> Components { get; set; } [JsonProperty("flags")] public Optional<MessageFlags?> Flags { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index 3dba45a5b7..3bee06750d 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -1,19 +1,21 @@ using Newtonsoft.Json; -namespace Discord.API.Rest +namespace Discord.API.Rest; + +internal class ModifyMessageParams { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class ModifyMessageParams - { - [JsonProperty("content")] - public Optional<string> Content { get; set; } - [JsonProperty("embeds")] - public Optional<API.Embed[]> Embeds { get; set; } - [JsonProperty("components")] - public Optional<API.ActionRowComponent[]> Components { get; set; } - [JsonProperty("flags")] - public Optional<MessageFlags?> Flags { get; set; } - [JsonProperty("allowed_mentions")] - public Optional<AllowedMentions> AllowedMentions { get; set; } - } + [JsonProperty("content")] + public Optional<string> Content { get; set; } + + [JsonProperty("embeds")] + public Optional<Embed[]> Embeds { get; set; } + + [JsonProperty("components")] + public Optional<IMessageComponent[]> Components { get; set; } + + [JsonProperty("flags")] + public Optional<MessageFlags?> Flags { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional<AllowedMentions> AllowedMentions { get; set; } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs index e73efaf36a..db80eba75a 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookMessageParams.cs @@ -1,17 +1,18 @@ using Newtonsoft.Json; -namespace Discord.API.Rest +namespace Discord.API.Rest; + +internal class ModifyWebhookMessageParams { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] - internal class ModifyWebhookMessageParams - { - [JsonProperty("content")] - public Optional<string> Content { get; set; } - [JsonProperty("embeds")] - public Optional<Embed[]> Embeds { get; set; } - [JsonProperty("allowed_mentions")] - public Optional<AllowedMentions> AllowedMentions { get; set; } - [JsonProperty("components")] - public Optional<API.ActionRowComponent[]> Components { get; set; } - } + [JsonProperty("content")] + public Optional<string> Content { get; set; } + + [JsonProperty("embeds")] + public Optional<Embed[]> Embeds { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional<AllowedMentions> AllowedMentions { get; set; } + + [JsonProperty("components")] + public Optional<IMessageComponent[]> Components { get; set; } } diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index c1e4309eed..5a10353428 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -21,7 +21,7 @@ internal class UploadFileParams public Optional<Embed[]> Embeds { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; } public Optional<MessageReference> MessageReference { get; set; } - public Optional<ActionRowComponent[]> MessageComponent { get; set; } + public Optional<IMessageComponent[]> MessageComponent { get; set; } public Optional<MessageFlags?> Flags { get; set; } public Optional<ulong[]> Stickers { get; set; } public Optional<CreatePollParams> Poll { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs index dcdc7e65a8..26697ecbe9 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs @@ -21,7 +21,7 @@ internal class UploadInteractionFileParams public Optional<bool> IsTTS { get; set; } public Optional<Embed[]> Embeds { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; } - public Optional<ActionRowComponent[]> MessageComponents { get; set; } + public Optional<IMessageComponent[]> MessageComponents { get; set; } public Optional<MessageFlags> Flags { get; set; } public Optional<CreatePollParams> Poll { get; set; } @@ -66,9 +66,9 @@ public IReadOnlyDictionary<string, object> ToDictionary() if (Poll.IsSpecified) data["poll"] = Poll.Value; - List<object> attachments = new(); + List<object> attachments = []; - for (int n = 0; n != Files.Length; n++) + for (var n = 0; n != Files.Length; n++) { var attachment = Files[n]; diff --git a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs index f33eecfb9d..6c404475cc 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs @@ -22,7 +22,7 @@ internal class UploadWebhookFileParams public Optional<string> AvatarUrl { get; set; } public Optional<Embed[]> Embeds { get; set; } public Optional<AllowedMentions> AllowedMentions { get; set; } - public Optional<ActionRowComponent[]> MessageComponents { get; set; } + public Optional<IMessageComponent[]> MessageComponents { get; set; } public Optional<MessageFlags> Flags { get; set; } public Optional<string> ThreadName { get; set; } public Optional<ulong[]> AppliedTags { get; set; } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 3fd65d02d6..0c8e2395bb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -320,7 +320,7 @@ public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel chann Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel(), MessageReference = messageReference?.ToModel(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified @@ -449,7 +449,7 @@ public static async Task<RestUserMessage> SendFilesAsync(IMessageChannel channel Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, MessageReference = messageReference?.ToModel() ?? Optional<API.MessageReference>.Unspecified, - MessageComponent = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponent = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified diff --git a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs index a133211d8e..29be16cb47 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ThreadHelper.cs @@ -163,7 +163,7 @@ public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channe Content = text, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, Flags = flags, - Components = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, + Components = components?.Components?.Any() ?? false ? components.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, }, Tags = tagIds @@ -224,7 +224,7 @@ public static async Task<RestThreadChannel> CreatePostAsync(IForumChannel channe Content = text, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, Flags = flags, - MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponent = components?.Components?.Any() ?? false ? components.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified, Slowmode = slowmode, Stickers = stickers?.Any() ?? false ? stickers.Select(x => x.Id).ToArray() : Optional<ulong[]>.Unspecified, Title = title, diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs index dae9b890d7..ef16c0f55c 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs @@ -1,12 +1,11 @@ using Discord.API.Rest; -using Discord.Net.Rest; + using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; -using DataModel = Discord.API.ApplicationCommandInteractionData; + using Model = Discord.API.Interaction; namespace Discord.Rest @@ -81,7 +80,8 @@ public override string Respond( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -89,7 +89,7 @@ public override string Respond( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -123,8 +123,12 @@ public override string Respond( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } }; @@ -152,12 +156,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -172,13 +177,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } @@ -194,7 +201,8 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -203,7 +211,7 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); using (var file = new FileAttachment(fileStream, fileName)) - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } /// <inheritdoc/> @@ -218,7 +226,8 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); @@ -226,7 +235,7 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); using (var file = new FileAttachment(File.OpenRead(filePath), fileName)) - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } /// <inheritdoc/> @@ -240,9 +249,10 @@ public override Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { - return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); } /// <inheritdoc/> @@ -256,12 +266,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -289,23 +300,22 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } - } - - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; + }; var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) - { - Flags = flags, - Content = text, - IsTTS = isTTS, - Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, - AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified - }; + { + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, + Content = text, + IsTTS = isTTS, + Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, + AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified + }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index 6afbf74e98..ce07a2940b 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -430,8 +430,8 @@ public static Task DeleteUnknownApplicationCommandAsync(BaseDiscordClient client Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional<API.AllowedMentions>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, }; return client.ApiClient.ModifyInteractionFollowupMessageAsync(apiArgs, message.Id, message.Token, options); @@ -478,8 +478,7 @@ public static Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFoll Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags }; @@ -495,8 +494,7 @@ public static Task DeleteFollowupMessageAsync(BaseDiscordClient client, RestFoll Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, MessageComponents = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified }; return client.ApiClient.ModifyInteractionResponseAsync(apiArgs, token, options); diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs index 811b45605e..8748ef4a37 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs @@ -77,7 +77,8 @@ public override string Respond( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -85,7 +86,7 @@ public override string Respond( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -119,14 +120,16 @@ public override string Respond( AllowedMentions = allowedMentions?.ToModel(), Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, } }; - if (ephemeral) - response.Data.Value.Flags = MessageFlags.Ephemeral; - lock (_lock) { if (HasResponded) @@ -208,8 +211,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified } }; @@ -227,8 +230,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, MessageComponents = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified }; @@ -256,12 +259,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -276,14 +280,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : MessageFlags.None, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } @@ -299,7 +304,8 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -308,7 +314,7 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); using (var file = new FileAttachment(fileStream, fileName)) - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } /// <inheritdoc/> @@ -323,7 +329,8 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { Preconditions.NotNullOrEmpty(filePath, nameof(filePath), "Path must exist"); @@ -331,7 +338,7 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( Preconditions.NotNullOrEmpty(fileName, nameof(fileName), "File Name must not be empty or null"); using (var file = new FileAttachment(File.OpenRead(filePath), fileName)) - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } /// <inheritdoc/> @@ -345,9 +352,10 @@ public override Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { - return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); } /// <inheritdoc/> @@ -361,12 +369,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -396,19 +405,18 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; - var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = flags, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); diff --git a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs index edeeea0b90..e7d08e4192 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; @@ -137,12 +138,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent component = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -157,13 +159,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ??Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified + Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } @@ -194,12 +198,13 @@ public override Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent component = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -216,14 +221,16 @@ public override Task<RestFollowupMessage> FollowupWithFileAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified, Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } @@ -254,12 +261,13 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent component = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -279,14 +287,16 @@ public override async Task<RestFollowupMessage> FollowupWithFileAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, File = fileStream != null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified, - Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified + Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return await InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); } @@ -315,7 +325,8 @@ public override string Respond( MessageComponent component = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -323,7 +334,7 @@ public override string Respond( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -357,8 +368,12 @@ public override string Respond( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + Components = component?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified } }; @@ -390,12 +405,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -425,19 +441,18 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; - var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = flags, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<API.Rest.CreatePollParams>.Unspecified }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); @@ -454,9 +469,10 @@ public override Task<RestFollowupMessage> FollowupWithFileAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { - return FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + return FollowupWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); } /// <inheritdoc/> @@ -541,8 +557,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified } }; @@ -551,7 +567,7 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt } else { - var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>(); + var attachments = args.Attachments.Value?.ToArray() ?? []; var response = new API.Rest.UploadInteractionFileParams(attachments) { @@ -560,8 +576,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, MessageComponents = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified }; diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs index 551eb9abb8..d8713ccccb 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -337,7 +337,7 @@ public async Task<RestInteractionMessage> ModifyOriginalResponseAsync(Action<Mes /// <inheritdoc/> public abstract string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -356,7 +356,7 @@ public abstract string Respond(string text = null, Embed[] embeds = null, bool i /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -377,7 +377,7 @@ public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embe /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -398,7 +398,7 @@ public abstract Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStrea /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -418,7 +418,7 @@ public abstract Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -438,7 +438,7 @@ public abstract Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment a /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <inheritdoc/> public Task DeleteOriginalResponseAsync(RequestOptions options = null) @@ -457,7 +457,7 @@ public Task RespondWithPremiumRequiredAsync(RequestOptions options = null) /// <inheritdoc/> Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, - MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) + MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => Task.FromResult(Respond(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll)); /// <inheritdoc/> Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) @@ -465,45 +465,48 @@ Task IDiscordInteraction.DeferAsync(bool ephemeral, RequestOptions options) /// <inheritdoc/> Task IDiscordInteraction.RespondWithModalAsync(Modal modal, RequestOptions options) => Task.FromResult(RespondWithModal(modal, options)); + /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, - MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.GetOriginalResponseAsync(RequestOptions options) => await GetOriginalResponseAsync(options).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action<MessageProperties> func, RequestOptions options) => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); + /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, - AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, - AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(filePath, text, fileName, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, - bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS, - bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); + /// <inheritdoc/> Task IDiscordInteraction.RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS, bool ephemeral, - AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files."); + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files."); #if NETCOREAPP3_0_OR_GREATER != true /// <inheritdoc/> Task IDiscordInteraction.RespondWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, - AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files."); + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files."); /// <inheritdoc/> Task IDiscordInteraction.RespondWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, - bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files."); + bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files."); /// <inheritdoc/> Task IDiscordInteraction.RespondWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, - AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) => throw new NotSupportedException("REST-Based interactions don't support files."); + AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) => throw new NotSupportedException("REST-Based interactions don't support files."); #endif #endregion } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs index 82b44c25ba..5b93127503 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestPingInteraction.cs @@ -37,11 +37,11 @@ public string AcknowledgePing() public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException(); public override string RespondWithModal(Modal modal, RequestOptions options = null) => throw new NotSupportedException(); - public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); - public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); - public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); - public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); - public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); - public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) => throw new NotSupportedException(); + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); + public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); + public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); + public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); + public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); + public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs index dcad1dbd43..6c60990b70 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/SlashCommands/RestAutocompleteInteraction.cs @@ -100,17 +100,17 @@ public string Respond(RequestOptions options = null, params AutocompleteResult[] => Respond(result, options); public override string Defer(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override string Respond(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); public override string RespondWithModal(Modal modal, RequestOptions options = null) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index ac1eeb1132..f7b26d0923 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -55,16 +55,16 @@ public static Task<Model> ModifyAsync(ulong channelId, ulong msgId, BaseDiscordC Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + if (allowedMentions is { AllowedTypes: not null }) { if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + allowedMentions.UserIds is { Count: > 0 }) { throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); } if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + allowedMentions.RoleIds is { Count: > 0 }) { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } @@ -93,13 +93,13 @@ public static Task<Model> ModifyAsync(ulong channelId, ulong msgId, BaseDiscordC Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), - Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() : Optional<API.ActionRowComponent[]>.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified, }; return client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options); } else { - var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>(); + var attachments = args.Attachments.Value?.ToArray() ?? []; var apiArgs = new UploadFileParams(attachments) { @@ -107,7 +107,7 @@ public static Task<Model> ModifyAsync(ulong channelId, ulong msgId, BaseDiscordC Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(), AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), - MessageComponent = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() : Optional<API.ActionRowComponent[]>.Unspecified + MessageComponent = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] : Optional<IMessageComponent[]>.Unspecified }; return client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options); diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index d4fddd038e..c418c4855f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -95,12 +95,12 @@ public abstract class RestMessage : RestEntity<ulong>, IMessage, IUpdateable /// <inheritdoc /> public PurchaseNotification PurchaseNotification { get; private set; } - + /// <inheritdoc /> public MessageCallData? CallData { get; private set; } /// <inheritdoc cref="IMessage.Components"/> - public IReadOnlyCollection<ActionRowComponent> Components { get; private set; } + public IReadOnlyCollection<IMessageComponent> Components { get; private set; } /// <summary> /// Gets a collection of the mentioned users in the message. /// </summary> @@ -150,7 +150,7 @@ internal virtual void Update(Model model) if (model.Activity.IsSpecified) { // create a new Activity from the API model - Activity = new MessageActivity() + Activity = new MessageActivity { Type = model.Activity.Value.Type.Value, PartyId = model.Activity.Value.PartyId.GetValueOrDefault() @@ -170,64 +170,9 @@ internal virtual void Update(Model model) }; } - if (model.Components.IsSpecified) - { - Components = model.Components.Value.Where(x => x.Type is ComponentType.ActionRow) - .Select(x => new ActionRowComponent(((API.ActionRowComponent)x).Components.Select<IMessageComponent, IMessageComponent>(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(), - parsed.SkuId.ToNullable(), - parsed.Id.ToNullable()); - } - case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect: - { - var parsed = (API.SelectMenuComponent)y; - return new SelectMenuComponent( - 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, - parsed.Disabled, - parsed.Type, - parsed.Id.ToNullable(), - parsed.ChannelTypes.GetValueOrDefault(), - parsed.DefaultValues.IsSpecified - ? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type)) - : Array.Empty<SelectMenuDefaultValue>() - ); - } - default: - return null; - } - }).ToList())).ToImmutableArray(); - } - else - Components = new List<ActionRowComponent>(); + Components = model.Components.IsSpecified + ? model.Components.Value.Select(x => x.ToEntity()).ToImmutableArray() + : []; if (model.Flags.IsSpecified) Flags = model.Flags.Value; @@ -292,11 +237,11 @@ internal virtual void Update(Model model) ? new GuildProductPurchase(model.PurchaseNotification.Value.ProductPurchase.Value.ListingId, model.PurchaseNotification.Value.ProductPurchase.Value.ProductName) : null); } - + if (model.Call.IsSpecified) CallData = new MessageCallData(model.Call.Value.Participants, model.Call.Value.EndedTimestamp.ToNullable()); } - + /// <inheritdoc /> public async Task UpdateAsync(RequestOptions options = null) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 4f80fa4385..fb5a8985d7 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -84,7 +84,7 @@ internal override void Update(Model model) if (model.MentionEveryone.IsSpecified) _isMentioningEveryone = model.MentionEveryone.Value; if (model.RoleMentions.IsSpecified) - _roleMentionIds = model.RoleMentions.Value.ToImmutableArray(); + _roleMentionIds = [..model.RoleMentions.Value]; if (model.Attachments.IsSpecified) { @@ -92,12 +92,13 @@ internal override void Update(Model model) if (value.Length > 0) { var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length); - for (int i = 0; i < value.Length; i++) - attachments.Add(Attachment.Create(value[i], Discord)); + foreach (var t in value) + attachments.Add(Attachment.Create(t, Discord)); + _attachments = attachments.ToImmutable(); } else - _attachments = ImmutableArray.Create<Attachment>(); + _attachments = []; } if (model.Embeds.IsSpecified) @@ -106,12 +107,13 @@ internal override void Update(Model model) if (value.Length > 0) { var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length); - for (int i = 0; i < value.Length; i++) - embeds.Add(value[i].ToEntity()); + foreach (var t in value) + embeds.Add(t.ToEntity()); + _embeds = embeds.ToImmutable(); } else - _embeds = ImmutableArray.Create<Embed>(); + _embeds = []; } var guildId = (Channel as IGuildChannel)?.GuildId; @@ -123,7 +125,7 @@ internal override void Update(Model model) model.Content = text; } - if (model.ReferencedMessage.IsSpecified && model.ReferencedMessage.Value != null) + if (model.ReferencedMessage is { IsSpecified: true, Value: not null }) { var refMsg = model.ReferencedMessage.Value; IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable()); @@ -141,7 +143,7 @@ internal override void Update(Model model) _stickers = stickers.ToImmutable(); } else - _stickers = ImmutableArray.Create<StickerItem>(); + _stickers = []; } if (model.Resolved.IsSpecified) diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs new file mode 100644 index 0000000000..a36ba6e20c --- /dev/null +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -0,0 +1,98 @@ +using System.Collections.Immutable; +using System.Linq; + +namespace Discord.Rest; + +internal static class MessageComponentExtension +{ + internal static IMessageComponent ToModel(this IMessageComponent component) + { + switch (component) + { + case ButtonComponent btn: + return new API.ButtonComponent(btn); + + case SelectMenuComponent select: + return new API.SelectMenuComponent(select); + + case TextInputComponent textInput: + return new API.TextInputComponent(textInput); + + case TextDisplayComponent textDisplay: + return new API.TextDisplayComponent(textDisplay); + } + + return null; + } + + internal static IMessageComponent ToEntity(this IMessageComponent component) + { + switch (component.Type) + { + case ComponentType.ActionRow: + { + var parsed = (API.ActionRowComponent)component; + return new ActionRowComponent() + { + Id = component.Id, + Components = parsed.Components.Select(x => x.ToEntity()).ToImmutableArray() + }; + } + + case ComponentType.Button: + { + var parsed = (API.ButtonComponent)component; + return new 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(), + parsed.SkuId.ToNullable(), + parsed.Id.ToNullable()); + } + + case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect: + { + var parsed = (API.SelectMenuComponent)component; + return new SelectMenuComponent( + 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, + parsed.Disabled, + parsed.Type, + parsed.Id.ToNullable(), + parsed.ChannelTypes.GetValueOrDefault(), + parsed.DefaultValues.IsSpecified + ? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type)) + : [] + ); + } + + case ComponentType.TextDisplay: + { + var parsed = (API.TextDisplayComponent)component; + return new TextDisplayComponent(parsed.Id.ToNullable(), parsed.Content); + } + + default: + return null; + } + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs index 7888219bcb..d88c5a7ad5 100644 --- a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -39,6 +39,9 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist case ComponentType.TextInput: messageComponent = new API.TextInputComponent(); break; + case ComponentType.TextDisplay: + messageComponent = new API.TextDisplayComponent(); + break; } serializer.Populate(jsonObject.CreateReader(), messageComponent); return messageComponent; diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs index f24a4be6fd..16bc37722f 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs @@ -82,7 +82,8 @@ public override async Task RespondWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -90,7 +91,7 @@ public override async Task RespondWithFilesAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -122,8 +123,12 @@ public override async Task RespondWithFilesAsync( AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -149,7 +154,8 @@ public override async Task RespondAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -157,7 +163,7 @@ public override async Task RespondAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -167,16 +173,16 @@ public override async Task RespondAsync( Preconditions.ValidatePoll(poll); // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + if (allowedMentions is { AllowedTypes: not null }) { if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + allowedMentions.UserIds is { Count: > 0 }) { throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); } if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + allowedMentions.RoleIds is { Count: > 0 }) { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } @@ -191,8 +197,12 @@ public override async Task RespondAsync( AllowedMentions = allowedMentions?.ToModel(), Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } }; @@ -250,13 +260,13 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt { var allowedMentions = args.AllowedMentions.Value; if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) - && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + && allowedMentions.UserIds is { Count: > 0 }) { throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions)); } if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) - && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + && allowedMentions.RoleIds is { Count: > 0 }) { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions)); } @@ -273,8 +283,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified } }; @@ -283,7 +293,7 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt } else { - var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>(); + var attachments = args.Attachments.Value?.ToArray() ?? []; var response = new API.Rest.UploadInteractionFileParams(attachments) { @@ -292,8 +302,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, MessageComponents = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified }; @@ -321,12 +331,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -341,13 +352,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } @@ -362,12 +375,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -385,31 +399,30 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) { if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + allowedMentions.UserIds is { Count: > 0 }) { throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); } if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + allowedMentions.RoleIds is { Count: > 0 }) { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } } - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; - var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = flags, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs index 7b1b0a6502..90c85a260e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs @@ -79,7 +79,8 @@ public override async Task RespondWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -87,7 +88,7 @@ public override async Task RespondWithFilesAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -119,8 +120,12 @@ public override async Task RespondWithFilesAsync( AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -146,7 +151,8 @@ public override async Task RespondAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -154,7 +160,7 @@ public override async Task RespondAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -164,16 +170,16 @@ public override async Task RespondAsync( Preconditions.ValidatePoll(poll); // check that user flag and user Id list are exclusive, same with role flag and role Id list - if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) + if (allowedMentions is { AllowedTypes: not null }) { if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && - allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) + allowedMentions.UserIds is { Count: > 0 }) { throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); } if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && - allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) + allowedMentions.RoleIds is { Count: > 0 }) { throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } @@ -188,8 +194,12 @@ public override async Task RespondAsync( AllowedMentions = allowedMentions?.ToModel(), Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } }; @@ -270,9 +280,11 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, Components = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, - Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, + Flags = args.Flags.IsSpecified + ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified + : Optional<MessageFlags>.Unspecified } }; @@ -289,8 +301,8 @@ public async Task UpdateAsync(Action<MessageProperties> func, RequestOptions opt AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = apiEmbeds?.ToArray() ?? Optional<API.Embed[]>.Unspecified, MessageComponents = args.Components.IsSpecified - ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty<API.ActionRowComponent>() - : Optional<API.ActionRowComponent[]>.Unspecified, + ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() ?? [] + : Optional<IMessageComponent[]>.Unspecified, Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional<MessageFlags>.Unspecified : Optional<MessageFlags>.Unspecified }; @@ -318,12 +330,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -338,14 +351,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } @@ -360,12 +374,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -395,19 +410,18 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; - var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = flags, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs index 7e6b484ab9..c4521513ff 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SlashCommands/SocketAutocompleteInteraction.cs @@ -91,15 +91,15 @@ public async Task RespondAsync(IEnumerable<AutocompleteResult> result, RequestOp /// </returns> public Task RespondAsync(RequestOptions options = null, params AutocompleteResult[] result) => RespondAsync(result, options); - public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); public override Task DeferAsync(bool ephemeral = false, RequestOptions options = null) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); - public override Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + public override Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) => throw new NotSupportedException("Autocomplete interactions don't support this method!"); /// <inheritdoc/> diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 3b6da47742..2d8dcf0509 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -77,7 +77,8 @@ public override async Task RespondAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -85,7 +86,7 @@ public override async Task RespondAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -119,8 +120,12 @@ public override async Task RespondAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } }; @@ -183,7 +188,8 @@ public override async Task RespondWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); @@ -191,7 +197,7 @@ public override async Task RespondWithFilesAsync( if (!InteractionHelper.CanSendResponse(this) && Discord.ResponseInternalTimeCheck) throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -223,8 +229,12 @@ public override async Task RespondWithFilesAsync( AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, - Flags = ephemeral ? MessageFlags.Ephemeral : Optional<MessageFlags>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -250,12 +260,13 @@ public override Task<RestFollowupMessage> FollowupAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -270,13 +281,15 @@ public override Task<RestFollowupMessage> FollowupAsync( AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), - Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, - Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified + Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, + Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, }; - if (ephemeral) - args.Flags = MessageFlags.Ephemeral; - return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } @@ -291,12 +304,13 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( MessageComponent components = null, Embed embed = null, RequestOptions options = null, - PollProperties poll = null) + PollProperties poll = null, + MessageFlags flags = MessageFlags.None) { if (!IsValidToken) throw new InvalidOperationException("Interaction token is no longer valid"); - embeds ??= Array.Empty<Embed>(); + embeds ??= []; if (embed != null) embeds = new[] { embed }.Concat(embeds).ToArray(); @@ -326,19 +340,18 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } - var flags = MessageFlags.None; - - if (ephemeral) - flags |= MessageFlags.Ephemeral; - var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = flags, + Flags = ephemeral + ? flags | MessageFlags.Ephemeral + : flags == MessageFlags.None + ? Optional<MessageFlags>.Unspecified + : flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; return InteractionHelper.SendFollowupAsync(Discord, args, Token, Channel, options); diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 24aecb47f1..5b3c9bc054 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -211,7 +211,7 @@ ChannelType.Media or /// <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 abstract Task RespondAsync(string text = null, Embed[] embeds = null, bool isTTS = false, - bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + bool ephemeral = false, AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Responds to this interaction with a file attachment. @@ -232,11 +232,11 @@ public abstract Task RespondAsync(string text = null, Embed[] embeds = null, boo /// contains the sent message. /// </returns> public async Task RespondWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(fileStream, fileName)) { - await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } @@ -259,11 +259,11 @@ public async Task RespondWithFileAsync(Stream fileStream, string fileName, strin /// contains the sent message. /// </returns> public async Task RespondWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(filePath, fileName)) { - await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + await RespondWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } @@ -285,8 +285,8 @@ public async Task RespondWithFileAsync(string filePath, string fileName = null, /// contains the sent message. /// </returns> public Task RespondWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => RespondWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => RespondWithFilesAsync([attachment], text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); /// <summary> /// Responds to this interaction with a collection of file attachments. @@ -306,7 +306,7 @@ public Task RespondWithFileAsync(FileAttachment attachment, string text = null, /// contains the sent message. /// </returns> public abstract Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -324,7 +324,7 @@ public abstract Task RespondWithFilesAsync(IEnumerable<FileAttachment> attachmen /// The sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Sends a followup message for this interaction. @@ -344,11 +344,11 @@ public abstract Task<RestFollowupMessage> FollowupAsync(string text = null, Embe /// The sent message. /// </returns> public async Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, string fileName, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(fileStream, fileName)) { - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } @@ -370,11 +370,11 @@ public async Task<RestFollowupMessage> FollowupWithFileAsync(Stream fileStream, /// The sent message. /// </returns> public async Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, string fileName = null, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) { using (var file = new FileAttachment(filePath, fileName)) { - return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + return await FollowupWithFileAsync(file, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); } } @@ -396,8 +396,8 @@ public async Task<RestFollowupMessage> FollowupWithFileAsync(string filePath, st /// contains the sent message. /// </returns> public Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null) - => FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None) + => FollowupWithFilesAsync(new FileAttachment[] { attachment }, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags); /// <summary> /// Sends a followup message for this interaction. @@ -417,7 +417,7 @@ public Task<RestFollowupMessage> FollowupWithFileAsync(FileAttachment attachment /// contains the sent message. /// </returns> public abstract Task<RestFollowupMessage> FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, - AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null); + AllowedMentions allowedMentions = null, MessageComponent components = null, Embed embed = null, RequestOptions options = null, PollProperties poll = null, MessageFlags flags = MessageFlags.None); /// <summary> /// Gets the original response for this interaction. @@ -504,26 +504,26 @@ async Task<IUserMessage> IDiscordInteraction.ModifyOriginalResponseAsync(Action< => await ModifyOriginalResponseAsync(func, options).ConfigureAwait(false); /// <inheritdoc/> async Task IDiscordInteraction.RespondAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, - Embed embed, RequestOptions options, PollProperties poll) - => await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await RespondAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupAsync(string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, - MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupAsync(text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> async Task<IUserMessage> IDiscordInteraction.FollowupWithFilesAsync(IEnumerable<FileAttachment> attachments, string text, Embed[] embeds, bool isTTS, - bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFilesAsync(attachments, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); #if NETCOREAPP3_0_OR_GREATER != true /// <inheritdoc/> - async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(Stream fileStream, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(fileStream, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> - async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(string filePath, string fileName, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(filePath, fileName, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); /// <inheritdoc/> - async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll) - => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll).ConfigureAwait(false); + async Task<IUserMessage> IDiscordInteraction.FollowupWithFileAsync(FileAttachment attachment, string text, Embed[] embeds, bool isTTS, bool ephemeral, AllowedMentions allowedMentions, MessageComponent components, Embed embed, RequestOptions options, PollProperties poll, MessageFlags flags) + => await FollowupWithFileAsync(attachment, text, embeds, isTTS, ephemeral, allowedMentions, components, embed, options, poll, flags).ConfigureAwait(false); #endif #endregion } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 878940f7cf..552baf5aff 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,10 +1,13 @@ using Discord.Rest; + using Newtonsoft.Json.Linq; + using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; + using Model = Discord.API.Message; namespace Discord.WebSocket @@ -65,7 +68,7 @@ public abstract class SocketMessage : SocketEntity<ulong>, IMessage public MessageReference Reference { get; private set; } /// <inheritdoc/> - public IReadOnlyCollection<ActionRowComponent> Components { get; private set; } + public IReadOnlyCollection<IMessageComponent> Components { get; private set; } /// <summary> /// Gets the interaction this message is a response to. @@ -203,64 +206,9 @@ internal virtual void Update(ClientState state, Model model) }; } - if (model.Components.IsSpecified) - { - Components = model.Components.Value.Where(x => x.Type is ComponentType.ActionRow) - .Select(x => new ActionRowComponent(((API.ActionRowComponent)x).Components.Select<IMessageComponent, IMessageComponent>(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(), - parsed.SkuId.ToNullable(), - parsed.Id.ToNullable()); - } - case ComponentType.SelectMenu: - { - var parsed = (API.SelectMenuComponent)y; - return new SelectMenuComponent( - 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, - parsed.Disabled, - parsed.Type, - parsed.Id.ToNullable(), - parsed.ChannelTypes.GetValueOrDefault(), - parsed.DefaultValues.IsSpecified - ? parsed.DefaultValues.Value.Select(x => new SelectMenuDefaultValue(x.Id, x.Type)) - : Array.Empty<SelectMenuDefaultValue>() - ); - } - default: - return null; - } - }).ToList())).ToImmutableArray(); - } - else - Components = new List<ActionRowComponent>(); + Components = model.Components.IsSpecified + ? model.Components.Value.Select(x => x.ToEntity()).ToImmutableArray() + : []; if (model.UserMentions.IsSpecified) { @@ -317,7 +265,7 @@ internal virtual void Update(ClientState state, Model model) ? new GuildProductPurchase(model.PurchaseNotification.Value.ProductPurchase.Value.ListingId, model.PurchaseNotification.Value.ProductPurchase.Value.ProductName) : null); } - + if (model.Call.IsSpecified) CallData = new MessageCallData(model.Call.Value.Participants, model.Call.Value.EndedTimestamp.ToNullable()); } diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index 47db9a21bb..d6b62f8f2b 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -48,7 +48,7 @@ public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, if (allowedMentions != null) args.AllowedMentions = allowedMentions.ToModel(); if (components != null) - args.Components = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray(); + args.Components = components?.Components.Select(x => x.ToModel()).ToArray(); if (threadName is not null) args.ThreadName = threadName; if (appliedTags != null) @@ -108,14 +108,14 @@ public static Task ModifyMessageAsync(DiscordWebhookClient client, ulong message AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), - Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified, }; return client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId); } else { - var attachments = args.Attachments.Value?.ToArray() ?? Array.Empty<FileAttachment>(); + var attachments = args.Attachments.Value?.ToArray() ?? []; var apiArgs = new UploadWebhookFileParams(attachments) { @@ -127,7 +127,7 @@ public static Task ModifyMessageAsync(DiscordWebhookClient client, ulong message AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), - MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => x.ToModel()).ToArray() : Optional<IMessageComponent[]>.Unspecified, }; return client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId); @@ -204,7 +204,7 @@ public static async Task<ulong> SendFilesAsync(DiscordWebhookClient client, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, AllowedMentions = allowedMentions?.ToModel() ?? Optional<API.AllowedMentions>.Unspecified, - MessageComponents = components?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, + MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Flags = flags, ThreadName = threadName, AppliedTags = appliedTags, From 5af8c5f101b6cd42d0965e45da70224837735c9a Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sat, 1 Feb 2025 20:05:53 +0300 Subject: [PATCH 03/15] all the models (i think) --- .../MessageComponents/ComponentType.cs | 6 ++++- .../Entities/Messages/MessageFlags.cs | 5 +++- .../API/Common/ContainerComponent.cs | 25 +++++++++++++++++++ .../API/Common/FileComponent.cs | 21 ++++++++++++++++ .../API/Common/MediaGalleryComponent.cs | 19 ++++++++++++++ .../API/Common/MediaGalleryItem.cs | 15 +++++++++++ .../API/Common/SectionComponent.cs | 22 ++++++++++++++++ .../API/Common/SeparatorComponent.cs | 21 ++++++++++++++++ .../API/Common/ThumbnailComponent.cs | 22 ++++++++++++++++ 9 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Rest/API/Common/ContainerComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/FileComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/MediaGalleryItem.cs create mode 100644 src/Discord.Net.Rest/API/Common/SectionComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/SeparatorComponent.cs create mode 100644 src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs index 3e80deada9..c0d5e196b9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs @@ -45,12 +45,16 @@ public enum ComponentType /// </summary> ChannelSelect = 8, + Section = 9, + TextDisplay = 10, MediaGallery = 12, File = 13, - Separator = 14 + Separator = 14, + + Container = 17, } } diff --git a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs index 51f7d89fc9..6219e8fc9c 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageFlags.cs @@ -58,6 +58,9 @@ public enum MessageFlags /// </summary> VoiceMessage = 1 << 13, - UiKitComponents = 1 << 15, + /// <summary> + /// This message is using v2 components. + /// </summary> + ComponentsV2 = 1 << 15, } } diff --git a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs new file mode 100644 index 0000000000..645f03f1cf --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class ContainerComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("accent_color")] + public Optional<int> AccentColor { get; set; } + + [JsonProperty("spoiler")] + public Optional<bool> IsSpoiler { get; set; } + + [JsonProperty("components")] + public IMessageComponent[] Components { get; set; } + + public ContainerComponent() { } + + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Common/FileComponent.cs b/src/Discord.Net.Rest/API/Common/FileComponent.cs new file mode 100644 index 0000000000..8f95dba4b4 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/FileComponent.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class FileComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("file")] + public UnfurledMediaItem File { get; set; } + + [JsonProperty("spoiler")] + public Optional<bool> IsSpoiler { get; set; } + + public FileComponent() { } + + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs b/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs new file mode 100644 index 0000000000..f8a0546361 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class MediaGalleryComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("items")] + public MediaGalleryItem[] Items { get; set; } + + public MediaGalleryComponent() { } + + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Common/MediaGalleryItem.cs b/src/Discord.Net.Rest/API/Common/MediaGalleryItem.cs new file mode 100644 index 0000000000..dda02dc5b7 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MediaGalleryItem.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class MediaGalleryItem +{ + [JsonProperty("media")] + public UnfurledMediaItem Media { get; set; } + + [JsonProperty("description")] + public Optional<string> Description { get; set; } + + [JsonProperty("spoiler")] + public Optional<bool> IsSpoiler { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Common/SectionComponent.cs b/src/Discord.Net.Rest/API/Common/SectionComponent.cs new file mode 100644 index 0000000000..0674bd17eb --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SectionComponent.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class SectionComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("components")] + public IMessageComponent[] Components { get; set; } + + [JsonProperty("accessory")] + public IMessageComponent Accessory { get; set; } + + public SectionComponent() { } + + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs b/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs new file mode 100644 index 0000000000..d83f123eb6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class SeparatorComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("divider")] + public Optional<bool> IsDivider { get; set; } + + [JsonProperty("spacing")] + public Optional<SeparatorSpacingSize> Spacing { get; set; } + + public SeparatorComponent() { } + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs new file mode 100644 index 0000000000..854e302be9 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class ThumbnailComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional<int> Id { get; set; } + + [JsonProperty("description")] + public Optional<string> Description { get; set; } + + [JsonProperty("spoiler")] + public Optional<bool> IsSpoiler { get; set; } + + public ThumbnailComponent() { } + + int? IMessageComponent.Id => Id.ToNullable(); +} From ba9dcdf0a117e14f271b16b17dda347dc97952f6 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sun, 2 Feb 2025 18:39:03 +0300 Subject: [PATCH 04/15] barebones builder impl --- .../MessageComponents/ActionRowComponent.cs | 2 +- .../Builders/ActionRowBuilder.cs | 29 +++--- .../Builders/ButtonBuilder.cs | 6 +- .../Builders/ComponentBuilder.cs | 39 ++++---- .../Builders/ComponentBuilderV2.cs | 85 ++++++++++++++++++ .../Builders/ContainerComponentBuilder.cs | 61 +++++++++++++ .../Builders/FileComponentBuilder.cs | 37 ++++++++ .../Builders/IInteractableComponentBuilder.cs | 6 ++ .../Builders/IMessageComponentBuilder.cs | 10 +++ .../Builders/MediaGalleryBuilder.cs | 39 ++++++++ .../Builders/MediaGalleryItemProperties.cs | 19 ++++ .../Builders/SectionBuilder.cs | 53 +++++++++++ .../Builders/SelectMenuBuilder.cs | 4 +- .../Builders/SeparatorBuilder.cs | 37 ++++++++ .../Builders/TextDisplayBuilder.cs | 33 +++++++ .../Builders/TextDisplayComponentBuilder.cs | 18 ---- .../Builders/TextInputBuilder.cs | 6 +- .../Builders/ThumbnailBuilder.cs | 45 ++++++++++ .../Builders/UnfurledMediaItemProperties.cs | 12 +++ .../MessageComponents/ComponentType.cs | 2 + .../MessageComponents/ContainerComponent.cs | 24 +++++ .../MessageComponents/FileComponent.cs | 19 ++++ .../MediaGalleryComponent.cs | 18 ++++ .../MessageComponents/MediaGalleryItem.cs | 17 ++++ .../MessageComponents/SectionComponent.cs | 21 +++++ .../MessageComponents/SeparatorComponent.cs | 19 ++++ .../MessageComponents/TextDisplayComponent.cs | 2 +- .../MessageComponents/ThumbnailComponent.cs | 22 +++++ .../MessageComponents/UnfurledMediaItem.cs | 11 +++ .../Interactions/Modals/ModalBuilder.cs | 31 +++---- .../API/Common/ContainerComponent.cs | 11 +++ .../API/Common/FileComponent.cs | 9 ++ .../API/Common/MediaGalleryComponent.cs | 14 +++ .../API/Common/SectionComponent.cs | 10 +++ .../API/Common/SeparatorComponent.cs | 9 ++ .../API/Common/ThumbnailComponent.cs | 12 +++ .../Extensions/MessageComponentExtension.cs | 88 ++++++++++++++++++- .../Converters/MessageComponentConverter.cs | 21 ++++- 38 files changed, 821 insertions(+), 80 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs delete mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs index bca11c4824..855b61ee29 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs @@ -20,7 +20,7 @@ public class ActionRowComponent : IMessageComponent internal ActionRowComponent() { } - internal ActionRowComponent(List<IMessageComponent> components) + internal ActionRowComponent(IReadOnlyCollection<IMessageComponent> components) { Components = components; } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs index dbbe4e9956..3004c8e825 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs @@ -8,8 +8,12 @@ namespace Discord; /// <summary> /// Represents a class used to build Action rows. /// </summary> -public class ActionRowBuilder +public class ActionRowBuilder : IMessageComponentBuilder { + public ComponentType Type => ComponentType.ActionRow; + + public int? Id { get; set; } + /// <summary> /// The max amount of child components this row can hold. /// </summary> @@ -20,7 +24,7 @@ public class ActionRowBuilder /// </summary> /// <exception cref="ArgumentNullException" accessor="set"><see cref="Components"/> cannot be null.</exception> /// <exception cref="ArgumentException" accessor="set"><see cref="Components"/> count exceeds <see cref="MaxChildCount"/>.</exception> - public List<IMessageComponent> Components + public List<IMessageComponentBuilder> Components { get => _components; set @@ -37,7 +41,7 @@ public List<IMessageComponent> Components } } - private List<IMessageComponent> _components = new List<IMessageComponent>(); + private List<IMessageComponentBuilder> _components = new (); /// <summary> /// Adds a list of components to the current row. @@ -45,7 +49,7 @@ public List<IMessageComponent> Components /// <param name="components">The list of components to add.</param> /// <inheritdoc cref="Components"/> /// <returns>The current builder.</returns> - public ActionRowBuilder WithComponents(List<IMessageComponent> components) + public ActionRowBuilder WithComponents(List<IMessageComponentBuilder> components) { Components = components; return this; @@ -57,7 +61,7 @@ public ActionRowBuilder WithComponents(List<IMessageComponent> components) /// <param name="component">The component to add.</param> /// <exception cref="InvalidOperationException">Components count reached <see cref="MaxChildCount"/></exception> /// <returns>The current builder.</returns> - public ActionRowBuilder AddComponent(IMessageComponent component) + public ActionRowBuilder AddComponent(IMessageComponentBuilder component) { if (Components.Count >= MaxChildCount) throw new InvalidOperationException($"Components count reached {MaxChildCount}"); @@ -103,13 +107,11 @@ public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu) { if (menu.Options is not null && menu.Options.Distinct().Count() != menu.Options.Count) throw new InvalidOperationException("Please make sure that there is no duplicates values."); - - var builtMenu = menu.Build(); - + if (Components.Count != 0) throw new InvalidOperationException($"A Select Menu cannot exist in a pre-occupied ActionRow."); - AddComponent(builtMenu); + AddComponent(menu); return this; } @@ -152,15 +154,13 @@ public ActionRowBuilder WithButton( /// <returns>The current builder.</returns> public ActionRowBuilder WithButton(ButtonBuilder button) { - var builtButton = button.Build(); - if (Components.Count >= 5) throw new InvalidOperationException($"Components count reached {MaxChildCount}"); if (Components.Any(x => x.Type.IsSelectType())) throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu"); - AddComponent(builtButton); + AddComponent(button); return this; } @@ -171,10 +171,11 @@ public ActionRowBuilder WithButton(ButtonBuilder button) /// <returns>A <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/></returns> public ActionRowComponent Build() { - return new ActionRowComponent(_components); + return new ActionRowComponent(_components.Select(x => x.Build()).ToList()); } + IMessageComponent IMessageComponentBuilder.Build() => Build(); - internal bool CanTakeComponent(IMessageComponent component) + internal bool CanTakeComponent(IMessageComponentBuilder component) { switch (component.Type) { diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs index 058f4e473c..e6c56afe99 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs @@ -7,8 +7,10 @@ namespace Discord; /// <summary> /// Represents a class used to build <see cref="ButtonComponent"/>'s. /// </summary> -public class ButtonBuilder +public class ButtonBuilder : IInteractableComponentBuilder { + public ComponentType Type => ComponentType.Button; + /// <summary> /// The max length of a <see cref="ButtonComponent.Label"/>. /// </summary> @@ -322,4 +324,6 @@ public ButtonComponent Build() return new ButtonComponent(Style, Label, Emote, CustomId, Url, IsDisabled, SkuId, Id); } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs index ce11bdee9d..5193c45852 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilder.cs @@ -67,18 +67,13 @@ internal void AddComponent(IMessageComponent component, int row) { switch (component) { - case TextDisplayComponent textDisplay: - break; - case ButtonComponent button: WithButton(button.Label, button.CustomId, button.Style, button.Emote, button.Url, button.IsDisabled, row); break; - case ActionRowComponent actionRow: foreach (var cmp in actionRow.Components) AddComponent(cmp, row); break; - case SelectMenuComponent menu: WithSelectMenu(menu.CustomId, menu.Options?.Select(x => new SelectMenuOptionBuilder(x.Label, x.Value, x.Description, x.Emote, x.IsDefault)).ToList(), menu.Placeholder, menu.MinValues, menu.MaxValues, menu.IsDisabled, row); break; @@ -103,7 +98,7 @@ public ComponentBuilder RemoveComponentsOfType(ComponentType t) /// <returns>The current builder.</returns> public ComponentBuilder RemoveComponent(string customId) { - this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c is IInteractableComponent ic && ic.CustomId == customId)); + this.ActionRows.ForEach(ar => ar.Components.RemoveAll(c => c is IInteractableComponent i && i.CustomId == customId)); return this; } @@ -163,20 +158,15 @@ public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); if (menu.Options is not null && menu.Options.Distinct().Count() != menu.Options.Count) throw new InvalidOperationException("Please make sure that there is no duplicates values."); - - var builtMenu = menu.Build(); - + if (_actionRows == null) { - _actionRows = new List<ActionRowBuilder> - { - new ActionRowBuilder().AddComponent(builtMenu) - }; + _actionRows = [new ActionRowBuilder().AddComponent(menu)]; } else { if (_actionRows.Count == row) - _actionRows.Add(new ActionRowBuilder().AddComponent(builtMenu)); + _actionRows.Add(new ActionRowBuilder().AddComponent(menu)); else { ActionRowBuilder actionRow; @@ -188,12 +178,12 @@ public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0) _actionRows.Add(actionRow); } - if (actionRow.CanTakeComponent(builtMenu)) - actionRow.AddComponent(builtMenu); + if (actionRow.CanTakeComponent(menu)) + actionRow.AddComponent(menu); else if (row < MaxActionRowCount) WithSelectMenu(menu, row + 1); else - throw new InvalidOperationException($"There is no more row to add a {nameof(builtMenu)}"); + throw new InvalidOperationException($"There is no more row to add a {nameof(menu)}"); } } @@ -248,16 +238,17 @@ public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) { Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); - var builtButton = button.Build(); - if (_actionRows == null) { - _actionRows = [new ActionRowBuilder().AddComponent(builtButton)]; + _actionRows = new List<ActionRowBuilder> + { + new ActionRowBuilder().AddComponent(button) + }; } else { if (_actionRows.Count == row) - _actionRows.Add(new ActionRowBuilder().AddComponent(builtButton)); + _actionRows.Add(new ActionRowBuilder().AddComponent(button)); else { ActionRowBuilder actionRow; @@ -269,8 +260,8 @@ public ComponentBuilder WithButton(ButtonBuilder button, int row = 0) _actionRows.Add(actionRow); } - if (actionRow.CanTakeComponent(builtButton)) - actionRow.AddComponent(builtButton); + if (actionRow.CanTakeComponent(button)) + actionRow.AddComponent(button); else if (row < MaxActionRowCount) WithButton(button, row + 1); else @@ -328,7 +319,7 @@ public MessageComponent Build() _actionRows.RemoveAt(i); return _actionRows != null - ? new MessageComponent(_actionRows.Select(x => x.Build()).ToList()) + ? new MessageComponent(_actionRows.Select(x => x.Build()).OfType<IMessageComponent>().ToList()) : MessageComponent.Empty; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs new file mode 100644 index 0000000000..18ec131d83 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Discord; + +public class ComponentBuilderV2 +{ + public ComponentBuilderV2() {} + + private List<IMessageComponentBuilder> _components = new(); + + public List<IMessageComponentBuilder> Components + { + get => _components; + set + { + _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); + } + } + + public ComponentBuilderV2 AddComponent(IMessageComponentBuilder component) + { + Components.Add(component); + return this; + } + + public ComponentBuilderV2 AddComponent(IMessageComponent component) + { + return this; + } + + public ComponentBuilderV2 WithComponents(List<IMessageComponentBuilder> components) + { + Components = components; + return this; + } + + public ComponentBuilderV2 WithActionRow(ActionRowBuilder actionRow) + { + Components.Add(actionRow); + return this; + } + + public ComponentBuilderV2 WithTextDisplay(TextDisplayBuilder textDisplayComponent) + { + Components.Add(textDisplayComponent); + return this; + } + + public ComponentBuilderV2 WithSection(SectionBuilder sectionComponent) + { + Components.Add(sectionComponent); + return this; + } + + public ComponentBuilderV2 WithMediaGallery(MediaGalleryBuilder mediaGallery) + { + Components.Add(mediaGallery); + return this; + } + + public ComponentBuilderV2 WithSeparator(SeparatorBuilder separator) + { + Components.Add(separator); + return this; + } + + public ComponentBuilderV2 WithFile(FileComponentBuilder file) + { + Components.Add(file); + return this; + } + + public ComponentBuilderV2 WithContainer(ContainerComponentBuilder container) + { + Components.Add(container); + return this; + } + + public MessageComponent Build() + { + return new MessageComponent(Components.Select(x => x.Build()).ToList()); + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs new file mode 100644 index 0000000000..5eebad2c63 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Discord; + +public class ContainerComponentBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.Container; + + public int? Id { get; set; } + + private List<IMessageComponentBuilder> _components = new(); + + public List<IMessageComponentBuilder> Components + { + get => _components; + set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); + } + + public int? AccentColor { get; set; } + + public bool? IsSpoiler { get; set; } + + public ContainerComponentBuilder WithId(int id) + { + Id = id; + return this; + } + + public ContainerComponentBuilder WithAccentColor(int accentColor) + { + AccentColor = accentColor; + return this; + } + + public ContainerComponentBuilder WithSpoiler(bool isSpoiler) + { + IsSpoiler = isSpoiler; + return this; + } + + public ContainerComponentBuilder AddComponent(IMessageComponentBuilder component) + { + Components.Add(component); + return this; + } + + public ContainerComponentBuilder WithComponents(List<IMessageComponentBuilder> components) + { + Components = components; + return this; + } + + public ContainerComponent Build() + { + return new(Components.ConvertAll(x => x.Build()).ToImmutableArray(), AccentColor, IsSpoiler, Id); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs new file mode 100644 index 0000000000..a40b7e4888 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs @@ -0,0 +1,37 @@ +namespace Discord; + +public class FileComponentBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.File; + + public int? Id { get; set; } + + public UnfurledMediaItemProperties File { get; set; } + + public bool? IsSpoiler { get; set; } + + public FileComponentBuilder WithFile(UnfurledMediaItemProperties file) + { + File = file; + return this; + } + + public FileComponentBuilder WithIsSpoiler(bool? isSpoiler) + { + IsSpoiler = isSpoiler; + return this; + } + + public FileComponentBuilder WithId(int id) + { + Id = id; + return this; + } + + public FileComponent Build() + { + return new(new UnfurledMediaItem(File.Url), IsSpoiler, Id); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs new file mode 100644 index 0000000000..703d9950b7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs @@ -0,0 +1,6 @@ +namespace Discord; + +public interface IInteractableComponentBuilder : IMessageComponentBuilder +{ + string CustomId { get; set; } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs new file mode 100644 index 0000000000..cb5ab6e511 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs @@ -0,0 +1,10 @@ +namespace Discord; + +public interface IMessageComponentBuilder +{ + ComponentType Type { get; } + + int? Id { get; set; } + + IMessageComponent Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs new file mode 100644 index 0000000000..563d5396e9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Discord; + +public class MediaGalleryBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.MediaGallery; + + public int? Id { get; set; } + + private List<MediaGalleryItemProperties> _items = new(); + + public List<MediaGalleryItemProperties> Items + { + get => _items; + set => _items = value; + } + + public MediaGalleryBuilder AddItem(MediaGalleryItemProperties item) + { + _items.Add(item); + return this; + } + + public MediaGalleryBuilder WithId(int id) + { + Id = id; + return this; + } + + public MediaGalleryComponent Build() + { + return new(_items.Select(x => new MediaGalleryItem(new UnfurledMediaItem(x.Media.Url), x.Description, x.IsSpoiler)).ToImmutableArray(), Id); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs new file mode 100644 index 0000000000..9bbc8e02c4 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs @@ -0,0 +1,19 @@ +namespace Discord; + +public struct MediaGalleryItemProperties +{ + public UnfurledMediaItemProperties Media { get; set; } + + public string Description { get; set; } + + public bool IsSpoiler { get; set; } + + public MediaGalleryItemProperties() { } + + public MediaGalleryItemProperties(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false) + { + Media = media; + Description = description; + IsSpoiler = isSpoiler; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs new file mode 100644 index 0000000000..dcc08ffd02 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Discord; + +public class SectionBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.Section; + + public int? Id { get; set; } + + private List<IMessageComponentBuilder> _components = new(); + public List<IMessageComponentBuilder> Components + { + get => _components; + set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); + } + + public IMessageComponentBuilder Accessory { get; set; } + + public SectionBuilder WithId(int id) + { + Id = id; + return this; + } + + public SectionBuilder WithAccessory(IMessageComponentBuilder accessory) + { + Accessory = accessory; + return this; + } + + public SectionBuilder AddComponent(IMessageComponentBuilder component) + { + Components.Add(component); + return this; + } + + public SectionBuilder WithComponents(List<IMessageComponentBuilder> components) + { + Components = components; + return this; + } + + public SectionComponent Build() + { + return new(Id, Components.Select(x => x.Build()).ToImmutableArray(), Accessory?.Build()); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs index 41c49e93f0..7c1c2b2702 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs @@ -8,7 +8,7 @@ namespace Discord; /// <summary> /// Represents a class used to build <see cref="SelectMenuComponent"/>'s. /// </summary> -public class SelectMenuBuilder +public class SelectMenuBuilder : IInteractableComponentBuilder { /// <summary> /// The max length of a <see cref="SelectMenuComponent.Placeholder"/>. @@ -407,4 +407,6 @@ public SelectMenuComponent Build() return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, Id, ChannelTypes, DefaultValues); } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs new file mode 100644 index 0000000000..ba62fe2a0a --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs @@ -0,0 +1,37 @@ +namespace Discord; + +public class SeparatorBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.Separator; + + public bool? IsDivider { get; set; } + + public SeparatorSpacingSize? Spacing { get; set; } + + public int? Id { get; set; } + + public SeparatorBuilder WithIsDivider(bool? isDivider) + { + IsDivider = isDivider; + return this; + } + + public SeparatorBuilder WithSpacing(SeparatorSpacingSize? spacing) + { + Spacing = spacing; + return this; + } + + public SeparatorBuilder WithId(int id) + { + Id = id; + return this; + } + + public SeparatorComponent Build() + { + return new(IsDivider, Spacing, Id); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs new file mode 100644 index 0000000000..42e331ceb9 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs @@ -0,0 +1,33 @@ +namespace Discord; + +public class TextDisplayBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.ActionRow; + + public int? Id { get; set; } + + private string _content; + public string Content + { + get => _content; + set => _content = value; + } + public TextDisplayBuilder WithContent(string content) + { + Content = content; + return this; + } + + public TextDisplayBuilder WithId(int id) + { + Id = id; + return this; + } + + public TextDisplayComponent Build() + { + return new(_content, Id); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs deleted file mode 100644 index 28d6fe41bf..0000000000 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayComponentBuilder.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Discord; - -public class TextDisplayComponentBuilder -{ - public int? Id { get; set; } - - private string _content; - public string Content - { - get => _content; - set => _content = value; - } - - public TextDisplayComponent Build() - { - return new(Id, _content); - } -} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs index 2efe92b63d..dcdfc2cdda 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextInputBuilder.cs @@ -6,8 +6,10 @@ namespace Discord; /// <summary> /// Represents a builder for creating a <see cref="TextInputComponent"/>. /// </summary> -public class TextInputBuilder +public class TextInputBuilder : IInteractableComponentBuilder { + public ComponentType Type => ComponentType.TextInput; + /// <summary> /// The max length of a <see cref="TextInputComponent.Placeholder"/>. /// </summary> @@ -262,4 +264,6 @@ public TextInputComponent Build() return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value, Id); } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs new file mode 100644 index 0000000000..3d6b3d127d --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs @@ -0,0 +1,45 @@ +namespace Discord; + +public class ThumbnailComponentBuilder : IMessageComponentBuilder +{ + public ComponentType Type => ComponentType.Thumbnail; + + public int? Id { get; set; } + + public UnfurledMediaItemProperties Media { get; set; } + + public string Description { get; set; } + + public bool IsSpoiler { get; set; } = false; + + public ThumbnailComponentBuilder WithMedia(UnfurledMediaItemProperties media) + { + Media = media; + return this; + } + + public ThumbnailComponentBuilder WithDescription(string description) + { + Description = description; + return this; + } + + public ThumbnailComponentBuilder WithId(int id) + { + Id = id; + return this; + } + + public ThumbnailComponentBuilder WithSpoiler(bool isSpoiler) + { + IsSpoiler = isSpoiler; + return this; + } + + public ThumbnailComponent Build() + { + return new(Id, new UnfurledMediaItem(Media.Url), Description, IsSpoiler); + } + + IMessageComponent IMessageComponentBuilder.Build() => Build(); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs new file mode 100644 index 0000000000..e8babb78da --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs @@ -0,0 +1,12 @@ +namespace Discord; + +public struct UnfurledMediaItemProperties +{ + public string Url { get; set; } + + public UnfurledMediaItemProperties() {} + public UnfurledMediaItemProperties(string url) + { + Url = url; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs index c0d5e196b9..5d5cce5a77 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs @@ -49,6 +49,8 @@ public enum ComponentType TextDisplay = 10, + Thumbnail = 11, + MediaGallery = 12, File = 13, diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs new file mode 100644 index 0000000000..30fa37d39f --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Discord; + +public class ContainerComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.Container; + + public int? Id { get; } + + public IReadOnlyCollection<IMessageComponent> Components { get; } + + public int? AccentColor { get; } + + public bool? IsSpoiler { get; } + + internal ContainerComponent(IReadOnlyCollection<IMessageComponent> components, int? accentColor, bool? isSpoiler, int? id = null) + { + Components = components; + AccentColor = accentColor; + IsSpoiler = isSpoiler; + Id = id; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs new file mode 100644 index 0000000000..40b89ba6e7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs @@ -0,0 +1,19 @@ +namespace Discord; + +public class FileComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.File; + + public UnfurledMediaItem File { get; } + + public int? Id { get; } + + public bool? IsSpoiler { get; } + + internal FileComponent(UnfurledMediaItem file, bool? isSpoiler, int? id = null) + { + File = file; + IsSpoiler = isSpoiler; + Id = id; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs new file mode 100644 index 0000000000..d08ecaa545 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Discord; + +public class MediaGalleryComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.MediaGallery; + + public int? Id { get; } + + public IReadOnlyCollection<MediaGalleryItem> Items { get; } + + internal MediaGalleryComponent(IReadOnlyCollection<MediaGalleryItem> items, int? id) + { + Items = items; + Id = id; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs new file mode 100644 index 0000000000..db1b4c0d17 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs @@ -0,0 +1,17 @@ +namespace Discord; + +public readonly struct MediaGalleryItem +{ + public UnfurledMediaItem Media { get; } + + public string Description { get; } + + public bool IsSpoiler { get; } + + internal MediaGalleryItem(UnfurledMediaItem media, string description, bool? isSpoiler) + { + Media = media; + Description = description; + IsSpoiler = isSpoiler ?? false; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs new file mode 100644 index 0000000000..90a7e12df7 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Discord; + +public class SectionComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.Section; + + public int? Id { get; } + + public IReadOnlyCollection<IMessageComponent> Components { get; } + + public IMessageComponent Accessory { get; } + + internal SectionComponent(int? id, IReadOnlyCollection<IMessageComponent> components, IMessageComponent accessory) + { + Id = id; + Components = components; + Accessory = accessory; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs new file mode 100644 index 0000000000..b722683a3a --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs @@ -0,0 +1,19 @@ +namespace Discord; + +public class SeparatorComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.Separator; + + public int? Id { get; } + + public bool? IsDivider { get; } + + public SeparatorSpacingSize? Spacing { get; } + + internal SeparatorComponent(bool? isDivider, SeparatorSpacingSize? spacing, int? id = null) + { + IsDivider = isDivider; + Spacing = spacing; + Id = id; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs index 1829804f59..e96bac84e9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs @@ -8,7 +8,7 @@ public class TextDisplayComponent : IMessageComponent public string Content { get; } - internal TextDisplayComponent(int? id, string content) + internal TextDisplayComponent(string content, int? id = null) { Id = id; Content = content; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs new file mode 100644 index 0000000000..fbbaf34ecd --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs @@ -0,0 +1,22 @@ +namespace Discord; + +public class ThumbnailComponent : IMessageComponent +{ + public ComponentType Type => ComponentType.Thumbnail; + + public int? Id { get; } + + public UnfurledMediaItem Media { get; } + + public string Description { get; } + + public bool IsSpoiler { get; } + + internal ThumbnailComponent(int? id, UnfurledMediaItem media, string description, bool? isSpoiler) + { + Id = id; + Media = media; + Description = description; + IsSpoiler = isSpoiler ?? false; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs new file mode 100644 index 0000000000..d9cb7b9592 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs @@ -0,0 +1,11 @@ +namespace Discord; + +public readonly struct UnfurledMediaItem +{ + public string Url { get; } + + internal UnfurledMediaItem(string url) + { + Url = url; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs index 98a37e2a8e..6e04e95d9e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs @@ -111,21 +111,20 @@ public ModalBuilder AddComponents(List<IMessageComponent> components, int row) } /// <summary> - /// Gets a <typeparamref name="TMessageComponent"/> by the specified <paramref name="customId"/>. + /// Gets a <typeparamref name="TMessageComponentBuilder"/> by the specified <paramref name="customId"/>. /// </summary> - /// <typeparam name="TMessageComponent">The type of the component to get.</typeparam> - /// <param name="customId">The <see cref="IInteractableComponent.CustomId"/> of the component to get.</param> + /// <typeparam name="TMessageComponentBuilder">The type of the component to get.</typeparam> + /// <param name="customId">The <see cref="IInteractableComponentBuilder.CustomId"/> of the component to get.</param> /// <returns> - /// The component of type <typeparamref name="TMessageComponent"/> that was found, <see langword="null"/> otherwise. + /// The component of type <typeparamref name="TMessageComponentBuilder"/> that was found, <see langword="null"/> otherwise. /// </returns> - public TMessageComponent GetComponent<TMessageComponent>(string customId) - where TMessageComponent : class, IInteractableComponent + public TMessageComponentBuilder GetComponent<TMessageComponentBuilder>(string customId) + where TMessageComponentBuilder : class, IInteractableComponentBuilder { Preconditions.NotNull(customId, nameof(customId)); - return Components.ActionRows - ?.SelectMany(r => r.Components.OfType<TMessageComponent>()) - .FirstOrDefault(c => c is IInteractableComponent ic && ic?.CustomId == customId); + return Components.ActionRows?.SelectMany(r => r.Components.OfType<TMessageComponentBuilder>()) + .FirstOrDefault(c => c.CustomId == customId); } /// <summary> @@ -141,7 +140,7 @@ public ModalBuilder UpdateTextInput(string customId, Action<TextInputBuilder> up { Preconditions.NotNull(customId, nameof(customId)); - var component = GetComponent<TextInputComponent>(customId) ?? throw new ArgumentException($"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.", nameof(customId)); + var component = GetComponent<TextInputBuilder>(customId) ?? throw new ArgumentException($"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.", nameof(customId)); var row = Components.ActionRows.First(r => r.Components.Contains(component)); var builder = new TextInputBuilder @@ -159,7 +158,7 @@ public ModalBuilder UpdateTextInput(string customId, Action<TextInputBuilder> up updateTextInput(builder); row.Components.Remove(component); - row.AddComponent(builder.Build()); + row.AddComponent(builder); return this; } @@ -314,19 +313,17 @@ public ModalComponentBuilder WithTextInput(TextInputBuilder text, int row = 0) { Preconditions.LessThan(row, MaxActionRowCount, nameof(row)); - var builtButton = text.Build(); - if (_actionRows == null) { _actionRows = new List<ActionRowBuilder> { - new ActionRowBuilder().AddComponent(builtButton) + new ActionRowBuilder().AddComponent(text) }; } else { if (_actionRows.Count == row) - _actionRows.Add(new ActionRowBuilder().AddComponent(builtButton)); + _actionRows.Add(new ActionRowBuilder().AddComponent(text)); else { ActionRowBuilder actionRow; @@ -338,8 +335,8 @@ public ModalComponentBuilder WithTextInput(TextInputBuilder text, int row = 0) _actionRows.Add(actionRow); } - if (actionRow.CanTakeComponent(builtButton)) - actionRow.AddComponent(builtButton); + if (actionRow.CanTakeComponent(text)) + actionRow.AddComponent(text); else if (row < MaxActionRowCount) WithTextInput(text, row + 1); else diff --git a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs index 645f03f1cf..6681c82b33 100644 --- a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs @@ -1,4 +1,6 @@ +using Discord.Rest; using Newtonsoft.Json; +using System.Linq; namespace Discord.API; @@ -21,5 +23,14 @@ internal class ContainerComponent : IMessageComponent public ContainerComponent() { } + public ContainerComponent(Discord.ContainerComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + AccentColor = component.AccentColor ?? Optional<int>.Unspecified; + IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified; + Components = component.Components.Select(x => x.ToModel()).ToArray(); + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/API/Common/FileComponent.cs b/src/Discord.Net.Rest/API/Common/FileComponent.cs index 8f95dba4b4..81f189ae6b 100644 --- a/src/Discord.Net.Rest/API/Common/FileComponent.cs +++ b/src/Discord.Net.Rest/API/Common/FileComponent.cs @@ -1,3 +1,4 @@ +using Discord.Rest; using Newtonsoft.Json; namespace Discord.API; @@ -17,5 +18,13 @@ internal class FileComponent : IMessageComponent public FileComponent() { } + public FileComponent(Discord.FileComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + File = component.File.ToModel(); + IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified; + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs b/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs index f8a0546361..cc26cbd155 100644 --- a/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs +++ b/src/Discord.Net.Rest/API/Common/MediaGalleryComponent.cs @@ -1,4 +1,6 @@ +using Discord.Rest; using Newtonsoft.Json; +using System.Linq; namespace Discord.API; @@ -15,5 +17,17 @@ internal class MediaGalleryComponent : IMessageComponent public MediaGalleryComponent() { } + public MediaGalleryComponent(Discord.MediaGalleryComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + Items = component.Items.Select(x => new MediaGalleryItem + { + Description = x.Description, + IsSpoiler = x.IsSpoiler, + Media = x.Media.ToModel() + }).ToArray(); + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/API/Common/SectionComponent.cs b/src/Discord.Net.Rest/API/Common/SectionComponent.cs index 0674bd17eb..76ea893d8c 100644 --- a/src/Discord.Net.Rest/API/Common/SectionComponent.cs +++ b/src/Discord.Net.Rest/API/Common/SectionComponent.cs @@ -1,4 +1,6 @@ +using Discord.Rest; using Newtonsoft.Json; +using System.Linq; namespace Discord.API; @@ -18,5 +20,13 @@ internal class SectionComponent : IMessageComponent public SectionComponent() { } + public SectionComponent(Discord.SectionComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + Components = component.Components.Select(x => x.ToModel()).ToArray(); + Accessory = component.Accessory.ToModel(); + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs b/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs index d83f123eb6..f01affac2f 100644 --- a/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs +++ b/src/Discord.Net.Rest/API/Common/SeparatorComponent.cs @@ -17,5 +17,14 @@ internal class SeparatorComponent : IMessageComponent public Optional<SeparatorSpacingSize> Spacing { get; set; } public SeparatorComponent() { } + + public SeparatorComponent(Discord.SeparatorComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + IsDivider = component.IsDivider ?? Optional<bool>.Unspecified; + Spacing = component.Spacing ?? Optional<SeparatorSpacingSize>.Unspecified; + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs index 854e302be9..8ee7314fcb 100644 --- a/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs @@ -10,6 +10,9 @@ internal class ThumbnailComponent : IMessageComponent [JsonProperty("id")] public Optional<int> Id { get; set; } + [JsonProperty("media")] + public UnfurledMediaItem Media { get; set; } + [JsonProperty("description")] public Optional<string> Description { get; set; } @@ -18,5 +21,14 @@ internal class ThumbnailComponent : IMessageComponent public ThumbnailComponent() { } + public ThumbnailComponent(Discord.ThumbnailComponent component) + { + Type = component.Type; + Id = component.Id ?? Optional<int>.Unspecified; + Media = new UnfurledMediaItem { Url = component.Media.Url }; + Description = component.Description; + IsSpoiler = component.IsSpoiler; + } + int? IMessageComponent.Id => Id.ToNullable(); } diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs index a36ba6e20c..19d986eaa5 100644 --- a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -20,6 +20,24 @@ internal static IMessageComponent ToModel(this IMessageComponent component) case TextDisplayComponent textDisplay: return new API.TextDisplayComponent(textDisplay); + + case SectionComponent section: + return new API.SectionComponent(section); + + case ThumbnailComponent thumbnail: + return new API.ThumbnailComponent(thumbnail); + + case MediaGalleryComponent mediaGallery: + return new API.MediaGalleryComponent(mediaGallery); + + case SeparatorComponent separator: + return new API.SeparatorComponent(separator); + + case FileComponent file: + return new API.FileComponent(file); + + case ContainerComponent container: + return new API.ContainerComponent(container); } return null; @@ -85,14 +103,82 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) ); } + case ComponentType.TextInput: + { + var parsed = (API.TextInputComponent)component; + return new TextInputComponent(parsed.CustomId, + parsed.Label, + parsed.Placeholder.GetValueOrDefault(null), + parsed.MinLength.ToNullable(), + parsed.MaxLength.ToNullable(), + parsed.Style, + parsed.Required.ToNullable(), + parsed.Value.GetValueOrDefault(null), + parsed.Id.ToNullable()); + } + case ComponentType.TextDisplay: { var parsed = (API.TextDisplayComponent)component; - return new TextDisplayComponent(parsed.Id.ToNullable(), parsed.Content); + return new TextDisplayComponent(parsed.Content, parsed.Id.ToNullable()); + } + + case ComponentType.Section: + { + var parsed = (API.SectionComponent)component; + return new SectionComponent(parsed.Id.ToNullable(), parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(), parsed.Accessory.ToModel()); + } + + case ComponentType.Thumbnail: + { + var parsed = (API.ThumbnailComponent)component; + return new ThumbnailComponent(parsed.Id.ToNullable(), parsed.Media.ToEntity(), parsed.Description.GetValueOrDefault(null), parsed.IsSpoiler.ToNullable()); + } + + case ComponentType.MediaGallery: + { + var parsed = (API.MediaGalleryComponent)component; + + return new MediaGalleryComponent( + parsed.Items.Select(x => new MediaGalleryItem(x.Media.ToEntity(), x.Description.GetValueOrDefault(null), x.IsSpoiler.GetValueOrDefault(false))).ToList(), + parsed.Id.ToNullable()); + } + + case ComponentType.Separator: + { + var parsed = (API.SeparatorComponent)component; + return new SeparatorComponent(parsed.IsDivider.ToNullable(), parsed.Spacing.ToNullable(), parsed.Id.ToNullable()); + } + + case ComponentType.File: + { + var parsed = (API.FileComponent)component; + return new FileComponent(parsed.File.ToEntity(), parsed.IsSpoiler.ToNullable(), parsed.Id.ToNullable()); + } + + case ComponentType.Container: + { + var parsed = (API.ContainerComponent)component; + return new ContainerComponent(parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(), + parsed.AccentColor.ToNullable(), + parsed.IsSpoiler.ToNullable(), + parsed.Id.ToNullable()); } default: return null; } } + + internal static UnfurledMediaItem ToEntity(this API.UnfurledMediaItem mediaItem) + { + return new UnfurledMediaItem(mediaItem.Url); + } + internal static API.UnfurledMediaItem ToModel(this UnfurledMediaItem mediaItem) + { + return new API.UnfurledMediaItem + { + Url = mediaItem.Url + }; + } } diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs index d88c5a7ad5..0f229f33ef 100644 --- a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -1,12 +1,13 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.ComponentModel; namespace Discord.Net.Converters { internal class MessageComponentConverter : JsonConverter { - public static MessageComponentConverter Instance => new MessageComponentConverter(); + public static MessageComponentConverter Instance => new (); public override bool CanRead => true; public override bool CanWrite => false; @@ -42,6 +43,24 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist case ComponentType.TextDisplay: messageComponent = new API.TextDisplayComponent(); break; + case ComponentType.Thumbnail: + messageComponent = new API.ThumbnailComponent(); + break; + case ComponentType.Section: + messageComponent = new API.SectionComponent(); + break; + case ComponentType.MediaGallery: + messageComponent = new API.MediaGalleryComponent(); + break; + case ComponentType.Separator: + messageComponent = new API.SeparatorComponent(); + break; + case ComponentType.File: + messageComponent = new API.FileComponent(); + break; + case ComponentType.Container: + messageComponent = new API.ContainerComponent(); + break; } serializer.Populate(jsonObject.CreateReader(), messageComponent); return messageComponent; From 93b8f6d16c37ec14d68d4d1e4022586fb9e6390d Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Tue, 4 Feb 2025 21:38:19 +0300 Subject: [PATCH 05/15] commit --- .../MessageComponents/Builders/ComponentBuilderV2.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs index 18ec131d83..3c6dfc9c88 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -78,6 +78,17 @@ public ComponentBuilderV2 WithContainer(ContainerComponentBuilder container) return this; } + public ComponentBuilderV2 WithButton(ButtonBuilder button) + { + Components.Add(button); + return this; + } + + public ComponentBuilderV2 WithSelectMenu(SelectMenuBuilder selectMenu) + { + Components.Add(selectMenu); + return this; + } public MessageComponent Build() { return new MessageComponent(Components.Select(x => x.Build()).ToList()); From f92349698298ce3b63fd48ab5253f7789b6391b7 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Tue, 4 Feb 2025 23:23:22 +0300 Subject: [PATCH 06/15] initial take on `WithX` component builder design --- .../Builders/ActionRowBuilder.cs | 29 ++- .../Builders/ButtonBuilder.cs | 6 + .../Builders/ComponentBuilderV2.cs | 67 ++----- .../Builders/ComponentContainerExtensions.cs | 170 ++++++++++++++++++ .../Builders/ContainerComponentBuilder.cs | 30 +++- .../Builders/FileComponentBuilder.cs | 2 +- .../Builders/IComponentContainer.cs | 14 ++ .../IInteractableComponentContainer.cs | 6 + .../Builders/IStaticComponentContainer.cs | 6 + .../Builders/MediaGalleryBuilder.cs | 21 ++- .../Builders/SectionBuilder.cs | 30 ++-- .../Builders/SelectMenuBuilder.cs | 6 + .../Builders/SeparatorBuilder.cs | 2 +- .../Builders/TextDisplayBuilder.cs | 2 +- .../MessageComponents/ContainerComponent.cs | 4 +- .../API/Common/ContainerComponent.cs | 4 +- 16 files changed, 319 insertions(+), 80 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs index 3004c8e825..f1780f9bd9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs @@ -8,7 +8,7 @@ namespace Discord; /// <summary> /// Represents a class used to build Action rows. /// </summary> -public class ActionRowBuilder : IMessageComponentBuilder +public class ActionRowBuilder : IMessageComponentBuilder, IInteractableComponentContainer { public ComponentType Type => ComponentType.ActionRow; @@ -41,6 +41,20 @@ public List<IMessageComponentBuilder> Components } } + + public ActionRowBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + { + foreach (var component in components) + AddComponent(component); + return this; + } + + public ActionRowBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) + { + Components = components.ToList(); + return this; + } + private List<IMessageComponentBuilder> _components = new (); /// <summary> @@ -165,6 +179,12 @@ public ActionRowBuilder WithButton(ButtonBuilder button) return this; } + public ActionRowBuilder WithId(int? id) + { + Id = id; + return this; + } + /// <summary> /// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> /// </summary> @@ -196,4 +216,11 @@ internal bool CanTakeComponent(IMessageComponentBuilder component) return false; } } + + + IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); + + IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + + IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs index e6c56afe99..e16a2258e6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs @@ -262,6 +262,12 @@ public ButtonBuilder WithSkuId(ulong? skuId) return this; } + public ButtonBuilder WithId(int? id) + { + Id = id; + return this; + } + /// <summary> /// Builds this builder into a <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>. /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs index 3c6dfc9c88..9bc2905cca 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -4,7 +4,7 @@ namespace Discord; -public class ComponentBuilderV2 +public class ComponentBuilderV2 : IStaticComponentContainer { public ComponentBuilderV2() {} @@ -25,72 +25,25 @@ public ComponentBuilderV2 AddComponent(IMessageComponentBuilder component) return this; } - public ComponentBuilderV2 AddComponent(IMessageComponent component) + public ComponentBuilderV2 AddComponents(params IEnumerable<IMessageComponentBuilder> components) { + foreach (var component in components) + Components.Add(component); return this; } - public ComponentBuilderV2 WithComponents(List<IMessageComponentBuilder> components) + public ComponentBuilderV2 WithComponents(IEnumerable<IMessageComponentBuilder> components) { - Components = components; + Components = components.ToList(); return this; } - public ComponentBuilderV2 WithActionRow(ActionRowBuilder actionRow) - { - Components.Add(actionRow); - return this; - } - - public ComponentBuilderV2 WithTextDisplay(TextDisplayBuilder textDisplayComponent) - { - Components.Add(textDisplayComponent); - return this; - } - - public ComponentBuilderV2 WithSection(SectionBuilder sectionComponent) - { - Components.Add(sectionComponent); - return this; - } - - public ComponentBuilderV2 WithMediaGallery(MediaGalleryBuilder mediaGallery) - { - Components.Add(mediaGallery); - return this; - } - - public ComponentBuilderV2 WithSeparator(SeparatorBuilder separator) - { - Components.Add(separator); - return this; - } - - public ComponentBuilderV2 WithFile(FileComponentBuilder file) - { - Components.Add(file); - return this; - } - - public ComponentBuilderV2 WithContainer(ContainerComponentBuilder container) - { - Components.Add(container); - return this; - } - - public ComponentBuilderV2 WithButton(ButtonBuilder button) - { - Components.Add(button); - return this; - } - - public ComponentBuilderV2 WithSelectMenu(SelectMenuBuilder selectMenu) - { - Components.Add(selectMenu); - return this; - } public MessageComponent Build() { return new MessageComponent(Components.Select(x => x.Build()).ToList()); } + + IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); + IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs new file mode 100644 index 0000000000..44216b7de3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Discord; + +public static class ComponentContainerExtensions +{ + public static IStaticComponentContainer WithTextDisplay(this IStaticComponentContainer container, TextDisplayBuilder textDisplay) + { + container.AddComponent(textDisplay); + return container; + } + public static IStaticComponentContainer WithTextDisplay(this IStaticComponentContainer container, + string content, + int? id = null) + => container.WithTextDisplay(new TextDisplayBuilder() + .WithContent(content) + .WithId(id)); + + public static IStaticComponentContainer WithSection(this IStaticComponentContainer container, SectionBuilder section) + { + container.AddComponent(section); + return container; + } + + public static IStaticComponentContainer WithSection(this IStaticComponentContainer container, + IEnumerable<TextDisplayBuilder> components, + IMessageComponentBuilder accessory, + bool isSpoiler = false, + int? id = null) + => container.WithSection(new SectionBuilder() + .WithComponents(components) + .WithAccessory(accessory) + .WithId(id)); + + public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, MediaGalleryBuilder mediaGallery) + { + container.AddComponent(mediaGallery); + return container; + } + + public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, + IEnumerable<MediaGalleryItemProperties> items, + int? id = null) + => container.WithMediaGallery(new MediaGalleryBuilder() + .WithItems(items) + .WithId(id)); + + public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, + IEnumerable<string> urls, + int? id = null) + => container.WithMediaGallery(new MediaGalleryBuilder() + .WithItems(urls.Select(x => new MediaGalleryItemProperties(new UnfurledMediaItemProperties(x)))) + .WithId(id)); + + public static IStaticComponentContainer WithSeparator(this IStaticComponentContainer container, SeparatorBuilder separator) + { + container.AddComponent(separator); + return container; + } + + public static IStaticComponentContainer WithSeparator(this IStaticComponentContainer container, + SeparatorSpacingSize spacing = SeparatorSpacingSize.Small, + bool isDivider = true, + int? id = null) + => container.WithSeparator(new SeparatorBuilder() + .WithSpacing(spacing) + .WithIsDivider(isDivider) + .WithId(id)); + + public static IStaticComponentContainer WithFile(this IStaticComponentContainer container, FileComponentBuilder file) + { + container.AddComponent(file); + return container; + } + + public static IStaticComponentContainer WithFile(this IStaticComponentContainer container, + string url, + bool isSpoiler = false, + int? id = null) + => container.WithFile(new FileComponentBuilder() + .WithFile(new UnfurledMediaItemProperties(url)) + .WithIsSpoiler(isSpoiler) + .WithId(id)); + + public static IStaticComponentContainer WithContainer(this IStaticComponentContainer container, ContainerComponentBuilder containerComponent) + { + container.AddComponent(containerComponent); + return container; + } + + public static IStaticComponentContainer WithContainer(this IStaticComponentContainer container, + IEnumerable<IMessageComponentBuilder> components, + Color? accentColor = null, + bool isSpoiler = false, + int? id = null) + => container.WithContainer(new ContainerComponentBuilder() + .WithComponents(components) + .WithAccentColor(accentColor) + .WithSpoiler(isSpoiler) + .WithId(id)); + + public static IInteractableComponentContainer WithButton(this IInteractableComponentContainer container, ButtonBuilder button) + { + container.AddComponent(button); + return container; + } + + public static IInteractableComponentContainer WithButton(this IInteractableComponentContainer container, + string label = null, + string customId = null, + ButtonStyle style = ButtonStyle.Primary, + IEmote emote = null, + string url = null, + bool disabled = false, + ulong? skuId = null, + int? id = null) + => container.WithButton(new ButtonBuilder() + .WithLabel(label) + .WithStyle(style) + .WithEmote(emote) + .WithCustomId(customId) + .WithUrl(url) + .WithDisabled(disabled) + .WithSkuId(skuId) + .WithId(id)); + + public static IInteractableComponentContainer WithSelectMenu(this IInteractableComponentContainer container, SelectMenuBuilder selectMenu) + { + container.AddComponent(selectMenu); + return container; + } + + public static IInteractableComponentContainer WithSelectMenu(this IInteractableComponentContainer container, + string customId, + List<SelectMenuOptionBuilder> options = null, + string placeholder = null, + int minValues = 1, + int maxValues = 1, + bool disabled = false, + int row = 0, + ComponentType type = ComponentType.SelectMenu, + ChannelType[] channelTypes = null, + SelectMenuDefaultValue[] defaultValues = null, + int? id = null) + => container.WithSelectMenu(new SelectMenuBuilder() + .WithCustomId(customId) + .WithOptions(options) + .WithPlaceholder(placeholder) + .WithMaxValues(maxValues) + .WithMinValues(minValues) + .WithDisabled(disabled) + .WithType(type) + .WithChannelTypes(channelTypes) + .WithDefaultValues(defaultValues) + .WithId(id)); + + public static IStaticComponentContainer WithActionRow(this IStaticComponentContainer container, ActionRowBuilder actionRow) + { + container.AddComponent(actionRow); + return container; + } + + public static IStaticComponentContainer WithActionRow(this IStaticComponentContainer container, + IEnumerable<IMessageComponentBuilder> components, + int? id = null) + => container.WithActionRow(new ActionRowBuilder() + .WithComponents(components) + .WithId(id)); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs index 5eebad2c63..44c18ac6b1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; namespace Discord; -public class ContainerComponentBuilder : IMessageComponentBuilder +public class ContainerComponentBuilder : IMessageComponentBuilder, IComponentContainer { public ComponentType Type => ComponentType.Container; @@ -18,21 +19,26 @@ public List<IMessageComponentBuilder> Components set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); } - public int? AccentColor { get; set; } + public uint? AccentColor { get; set; } public bool? IsSpoiler { get; set; } - public ContainerComponentBuilder WithId(int id) + public ContainerComponentBuilder WithId(int? id) { Id = id; return this; } - public ContainerComponentBuilder WithAccentColor(int accentColor) + public ContainerComponentBuilder WithAccentColor(uint? accentColor) { AccentColor = accentColor; return this; } + public ContainerComponentBuilder WithAccentColor(Color? color) + { + AccentColor = color?.RawValue; + return this; + } public ContainerComponentBuilder WithSpoiler(bool isSpoiler) { @@ -46,6 +52,19 @@ public ContainerComponentBuilder AddComponent(IMessageComponentBuilder component return this; } + public ContainerComponentBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + { + foreach (var component in components) + Components.Add(component); + return this; + } + + public ContainerComponentBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) + { + Components = components.ToList(); + return this; + } + public ContainerComponentBuilder WithComponents(List<IMessageComponentBuilder> components) { Components = components; @@ -58,4 +77,7 @@ public ContainerComponent Build() } IMessageComponent IMessageComponentBuilder.Build() => Build(); + IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); + IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs index a40b7e4888..6e7f825ecd 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs @@ -22,7 +22,7 @@ public FileComponentBuilder WithIsSpoiler(bool? isSpoiler) return this; } - public FileComponentBuilder WithId(int id) + public FileComponentBuilder WithId(int? id) { Id = id; return this; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs new file mode 100644 index 0000000000..c8f28045b0 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Discord; + +public interface IComponentContainer +{ + List<IMessageComponentBuilder> Components { get; } + + IComponentContainer AddComponent(IMessageComponentBuilder component); + + IComponentContainer AddComponents(params IEnumerable<IMessageComponentBuilder> components); + + IComponentContainer WithComponents(IEnumerable<IMessageComponentBuilder> components); +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs new file mode 100644 index 0000000000..69c310a120 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs @@ -0,0 +1,6 @@ +namespace Discord; + +public interface IInteractableComponentContainer : IComponentContainer +{ + +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs new file mode 100644 index 0000000000..aed2af1b54 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs @@ -0,0 +1,6 @@ +namespace Discord; + +public interface IStaticComponentContainer : IComponentContainer +{ + +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs index 563d5396e9..5e5d96f272 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs @@ -24,7 +24,26 @@ public MediaGalleryBuilder AddItem(MediaGalleryItemProperties item) return this; } - public MediaGalleryBuilder WithId(int id) + public MediaGalleryBuilder AddItem(string url, string description = null, bool isSpoiler = false) + { + _items.Add(new MediaGalleryItemProperties(new UnfurledMediaItemProperties(url), description, isSpoiler)); + return this; + } + + public MediaGalleryBuilder AddItems(params IEnumerable<MediaGalleryItemProperties> items) + { + foreach (var item in items) + _items.Add(item); + return this; + } + + public MediaGalleryBuilder WithItems(IEnumerable<MediaGalleryItemProperties> items) + { + _items = items.ToList(); + return this; + } + + public MediaGalleryBuilder WithId(int? id) { Id = id; return this; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs index dcc08ffd02..43251fbfda 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs @@ -5,7 +5,7 @@ namespace Discord; -public class SectionBuilder : IMessageComponentBuilder +public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContainer { public ComponentType Type => ComponentType.Section; @@ -18,29 +18,36 @@ public List<IMessageComponentBuilder> Components set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); } - public IMessageComponentBuilder Accessory { get; set; } + public SectionBuilder AddComponent(IMessageComponentBuilder component) + { + Components.Add(component); + return this; + } - public SectionBuilder WithId(int id) + public SectionBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) { - Id = id; + foreach (var component in components) + AddComponent(component); return this; } - public SectionBuilder WithAccessory(IMessageComponentBuilder accessory) + public SectionBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) { - Accessory = accessory; + Components = components.ToList(); return this; } - public SectionBuilder AddComponent(IMessageComponentBuilder component) + public IMessageComponentBuilder Accessory { get; set; } + + public SectionBuilder WithId(int? id) { - Components.Add(component); + Id = id; return this; } - public SectionBuilder WithComponents(List<IMessageComponentBuilder> components) + public SectionBuilder WithAccessory(IMessageComponentBuilder accessory) { - Components = components; + Accessory = accessory; return this; } @@ -50,4 +57,7 @@ public SectionComponent Build() } IMessageComponent IMessageComponentBuilder.Build() => Build(); + IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); + IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components.ToList()); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs index 7c1c2b2702..c68e8623a9 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs @@ -397,6 +397,12 @@ public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes) return this; } + public SelectMenuBuilder WithId(int? id) + { + Id = id; + return this; + } + /// <summary> /// Builds a <see cref="SelectMenuComponent"/> /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs index ba62fe2a0a..085d18a960 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs @@ -22,7 +22,7 @@ public SeparatorBuilder WithSpacing(SeparatorSpacingSize? spacing) return this; } - public SeparatorBuilder WithId(int id) + public SeparatorBuilder WithId(int? id) { Id = id; return this; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs index 42e331ceb9..1ad3612995 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs @@ -18,7 +18,7 @@ public TextDisplayBuilder WithContent(string content) return this; } - public TextDisplayBuilder WithId(int id) + public TextDisplayBuilder WithId(int? id) { Id = id; return this; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs index 30fa37d39f..f434a0e021 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs @@ -10,11 +10,11 @@ public class ContainerComponent : IMessageComponent public IReadOnlyCollection<IMessageComponent> Components { get; } - public int? AccentColor { get; } + public uint? AccentColor { get; } public bool? IsSpoiler { get; } - internal ContainerComponent(IReadOnlyCollection<IMessageComponent> components, int? accentColor, bool? isSpoiler, int? id = null) + internal ContainerComponent(IReadOnlyCollection<IMessageComponent> components, uint? accentColor, bool? isSpoiler, int? id = null) { Components = components; AccentColor = accentColor; diff --git a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs index 6681c82b33..50bd8b08d2 100644 --- a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs @@ -13,7 +13,7 @@ internal class ContainerComponent : IMessageComponent public Optional<int> Id { get; set; } [JsonProperty("accent_color")] - public Optional<int> AccentColor { get; set; } + public Optional<uint> AccentColor { get; set; } [JsonProperty("spoiler")] public Optional<bool> IsSpoiler { get; set; } @@ -27,7 +27,7 @@ public ContainerComponent(Discord.ContainerComponent component) { Type = component.Type; Id = component.Id ?? Optional<int>.Unspecified; - AccentColor = component.AccentColor ?? Optional<int>.Unspecified; + AccentColor = component.AccentColor ?? Optional<uint>.Unspecified; IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified; Components = component.Components.Select(x => x.ToModel()).ToArray(); } From e7eda0698cd165307847fb2b3393466b7cf0b000 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Tue, 4 Feb 2025 23:36:29 +0300 Subject: [PATCH 07/15] actually usable stuff now --- .../Builders/ComponentContainerExtensions.cs | 61 ++++++++++++------- ...omponentBuilder.cs => ContainerBuilder.cs} | 18 +++--- 2 files changed, 49 insertions(+), 30 deletions(-) rename src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/{ContainerComponentBuilder.cs => ContainerBuilder.cs} (71%) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs index 44216b7de3..cebe2471b6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs @@ -5,108 +5,122 @@ namespace Discord; public static class ComponentContainerExtensions { - public static IStaticComponentContainer WithTextDisplay(this IStaticComponentContainer container, TextDisplayBuilder textDisplay) + public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, TextDisplayBuilder textDisplay) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(textDisplay); return container; } - public static IStaticComponentContainer WithTextDisplay(this IStaticComponentContainer container, + + public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, string content, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithTextDisplay(new TextDisplayBuilder() .WithContent(content) .WithId(id)); - public static IStaticComponentContainer WithSection(this IStaticComponentContainer container, SectionBuilder section) + public static BuilderT WithSection<BuilderT>(this BuilderT container, SectionBuilder section) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(section); return container; } - public static IStaticComponentContainer WithSection(this IStaticComponentContainer container, + public static BuilderT WithSection<BuilderT>(this BuilderT container, IEnumerable<TextDisplayBuilder> components, IMessageComponentBuilder accessory, bool isSpoiler = false, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithSection(new SectionBuilder() .WithComponents(components) .WithAccessory(accessory) .WithId(id)); - public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, MediaGalleryBuilder mediaGallery) + public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, MediaGalleryBuilder mediaGallery) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(mediaGallery); return container; } - public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, + public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, IEnumerable<MediaGalleryItemProperties> items, - int? id = null) + int? id = null) where BuilderT : class, IStaticComponentContainer => container.WithMediaGallery(new MediaGalleryBuilder() .WithItems(items) .WithId(id)); - public static IStaticComponentContainer WithMediaGallery(this IStaticComponentContainer container, + public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, IEnumerable<string> urls, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithMediaGallery(new MediaGalleryBuilder() .WithItems(urls.Select(x => new MediaGalleryItemProperties(new UnfurledMediaItemProperties(x)))) .WithId(id)); - public static IStaticComponentContainer WithSeparator(this IStaticComponentContainer container, SeparatorBuilder separator) + public static BuilderT WithSeparator<BuilderT>(this BuilderT container, SeparatorBuilder separator) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(separator); return container; } - public static IStaticComponentContainer WithSeparator(this IStaticComponentContainer container, + public static BuilderT WithSeparator<BuilderT>(this BuilderT container, SeparatorSpacingSize spacing = SeparatorSpacingSize.Small, bool isDivider = true, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithSeparator(new SeparatorBuilder() .WithSpacing(spacing) .WithIsDivider(isDivider) .WithId(id)); - public static IStaticComponentContainer WithFile(this IStaticComponentContainer container, FileComponentBuilder file) + public static BuilderT WithFile<BuilderT>(this BuilderT container, FileComponentBuilder file) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(file); return container; } - public static IStaticComponentContainer WithFile(this IStaticComponentContainer container, + public static BuilderT WithFile<BuilderT>(this BuilderT container, string url, bool isSpoiler = false, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithFile(new FileComponentBuilder() .WithFile(new UnfurledMediaItemProperties(url)) .WithIsSpoiler(isSpoiler) .WithId(id)); - public static IStaticComponentContainer WithContainer(this IStaticComponentContainer container, ContainerComponentBuilder containerComponent) + public static BuilderT WithContainer<BuilderT>(this BuilderT container, ContainerBuilder containerComponent) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(containerComponent); return container; } - public static IStaticComponentContainer WithContainer(this IStaticComponentContainer container, + public static BuilderT WithContainer<BuilderT>(this BuilderT container, IEnumerable<IMessageComponentBuilder> components, Color? accentColor = null, bool isSpoiler = false, int? id = null) - => container.WithContainer(new ContainerComponentBuilder() + where BuilderT : class, IStaticComponentContainer + => container.WithContainer(new ContainerBuilder() .WithComponents(components) .WithAccentColor(accentColor) .WithSpoiler(isSpoiler) .WithId(id)); - public static IInteractableComponentContainer WithButton(this IInteractableComponentContainer container, ButtonBuilder button) + public static BuilderT WithButton<BuilderT>(this BuilderT container, ButtonBuilder button) + where BuilderT : class, IInteractableComponentContainer { container.AddComponent(button); return container; } - public static IInteractableComponentContainer WithButton(this IInteractableComponentContainer container, + public static BuilderT WithButton<BuilderT>(this BuilderT container, string label = null, string customId = null, ButtonStyle style = ButtonStyle.Primary, @@ -115,6 +129,7 @@ public static IInteractableComponentContainer WithButton(this IInteractableCompo bool disabled = false, ulong? skuId = null, int? id = null) + where BuilderT : class, IInteractableComponentContainer => container.WithButton(new ButtonBuilder() .WithLabel(label) .WithStyle(style) @@ -125,13 +140,14 @@ public static IInteractableComponentContainer WithButton(this IInteractableCompo .WithSkuId(skuId) .WithId(id)); - public static IInteractableComponentContainer WithSelectMenu(this IInteractableComponentContainer container, SelectMenuBuilder selectMenu) + public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, SelectMenuBuilder selectMenu) + where BuilderT : class, IInteractableComponentContainer { container.AddComponent(selectMenu); return container; } - public static IInteractableComponentContainer WithSelectMenu(this IInteractableComponentContainer container, + public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, @@ -143,6 +159,7 @@ public static IInteractableComponentContainer WithSelectMenu(this IInteractableC ChannelType[] channelTypes = null, SelectMenuDefaultValue[] defaultValues = null, int? id = null) + where BuilderT : class, IInteractableComponentContainer => container.WithSelectMenu(new SelectMenuBuilder() .WithCustomId(customId) .WithOptions(options) @@ -155,15 +172,17 @@ public static IInteractableComponentContainer WithSelectMenu(this IInteractableC .WithDefaultValues(defaultValues) .WithId(id)); - public static IStaticComponentContainer WithActionRow(this IStaticComponentContainer container, ActionRowBuilder actionRow) + public static BuilderT WithActionRow<BuilderT>(this BuilderT container, ActionRowBuilder actionRow) + where BuilderT : class, IStaticComponentContainer { container.AddComponent(actionRow); return container; } - public static IStaticComponentContainer WithActionRow(this IStaticComponentContainer container, + public static BuilderT WithActionRow<BuilderT>(this BuilderT container, IEnumerable<IMessageComponentBuilder> components, int? id = null) + where BuilderT : class, IStaticComponentContainer => container.WithActionRow(new ActionRowBuilder() .WithComponents(components) .WithId(id)); diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs similarity index 71% rename from src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs index 44c18ac6b1..75d0d2ea5f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs @@ -5,7 +5,7 @@ namespace Discord; -public class ContainerComponentBuilder : IMessageComponentBuilder, IComponentContainer +public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContainer { public ComponentType Type => ComponentType.Container; @@ -23,49 +23,49 @@ public List<IMessageComponentBuilder> Components public bool? IsSpoiler { get; set; } - public ContainerComponentBuilder WithId(int? id) + public ContainerBuilder WithId(int? id) { Id = id; return this; } - public ContainerComponentBuilder WithAccentColor(uint? accentColor) + public ContainerBuilder WithAccentColor(uint? accentColor) { AccentColor = accentColor; return this; } - public ContainerComponentBuilder WithAccentColor(Color? color) + public ContainerBuilder WithAccentColor(Color? color) { AccentColor = color?.RawValue; return this; } - public ContainerComponentBuilder WithSpoiler(bool isSpoiler) + public ContainerBuilder WithSpoiler(bool isSpoiler) { IsSpoiler = isSpoiler; return this; } - public ContainerComponentBuilder AddComponent(IMessageComponentBuilder component) + public ContainerBuilder AddComponent(IMessageComponentBuilder component) { Components.Add(component); return this; } - public ContainerComponentBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + public ContainerBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) { foreach (var component in components) Components.Add(component); return this; } - public ContainerComponentBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) + public ContainerBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) { Components = components.ToList(); return this; } - public ContainerComponentBuilder WithComponents(List<IMessageComponentBuilder> components) + public ContainerBuilder WithComponents(List<IMessageComponentBuilder> components) { Components = components; return this; From 4d61ba70551f27663a97d722522e891adeae2c8a Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Tue, 4 Feb 2025 23:54:24 +0300 Subject: [PATCH 08/15] few more changes --- .../Builders/ContainerBuilder.cs | 5 ----- .../Builders/ThumbnailBuilder.cs | 16 +++++++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs index 75d0d2ea5f..a7bc9efa8b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs @@ -29,11 +29,6 @@ public ContainerBuilder WithId(int? id) return this; } - public ContainerBuilder WithAccentColor(uint? accentColor) - { - AccentColor = accentColor; - return this; - } public ContainerBuilder WithAccentColor(Color? color) { AccentColor = color?.RawValue; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs index 3d6b3d127d..df98bd07b8 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs @@ -1,6 +1,6 @@ namespace Discord; -public class ThumbnailComponentBuilder : IMessageComponentBuilder +public class ThumbnailBuilder : IMessageComponentBuilder { public ComponentType Type => ComponentType.Thumbnail; @@ -12,25 +12,31 @@ public class ThumbnailComponentBuilder : IMessageComponentBuilder public bool IsSpoiler { get; set; } = false; - public ThumbnailComponentBuilder WithMedia(UnfurledMediaItemProperties media) + public ThumbnailBuilder WithMedia(UnfurledMediaItemProperties media) { Media = media; return this; } - public ThumbnailComponentBuilder WithDescription(string description) + public ThumbnailBuilder WithMedia(string url) + { + Media = new UnfurledMediaItemProperties(url); + return this; + } + + public ThumbnailBuilder WithDescription(string description) { Description = description; return this; } - public ThumbnailComponentBuilder WithId(int id) + public ThumbnailBuilder WithId(int id) { Id = id; return this; } - public ThumbnailComponentBuilder WithSpoiler(bool isSpoiler) + public ThumbnailBuilder WithSpoiler(bool isSpoiler) { IsSpoiler = isSpoiler; return this; From 4433ab2b0c794af2b2125def7ef315eb973b7847 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Wed, 5 Feb 2025 00:28:20 +0300 Subject: [PATCH 09/15] fixes, changes, syntax sugar --- .../Builders/TextDisplayBuilder.cs | 14 +++++++++----- .../MessageComponents/Builders/ThumbnailBuilder.cs | 9 +++++++++ .../Builders/UnfurledMediaItemProperties.cs | 2 ++ .../Extensions/MessageComponentExtension.cs | 5 ++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs index 1ad3612995..484e85d94b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs @@ -6,12 +6,16 @@ public class TextDisplayBuilder : IMessageComponentBuilder public int? Id { get; set; } - private string _content; - public string Content + public string Content { get; set; } + + public TextDisplayBuilder() { } + + public TextDisplayBuilder(string content, int? id = null) { - get => _content; - set => _content = value; + Content = content; + Id = id; } + public TextDisplayBuilder WithContent(string content) { Content = content; @@ -26,7 +30,7 @@ public TextDisplayBuilder WithId(int? id) public TextDisplayComponent Build() { - return new(_content, Id); + return new(Content, Id); } IMessageComponent IMessageComponentBuilder.Build() => Build(); diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs index df98bd07b8..d6e411411d 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs @@ -12,6 +12,15 @@ public class ThumbnailBuilder : IMessageComponentBuilder public bool IsSpoiler { get; set; } = false; + public ThumbnailBuilder() { } + + public ThumbnailBuilder(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false) + { + Media = media; + Description = description; + IsSpoiler = isSpoiler; + } + public ThumbnailBuilder WithMedia(UnfurledMediaItemProperties media) { Media = media; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs index e8babb78da..c9d2b85d30 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs @@ -9,4 +9,6 @@ public UnfurledMediaItemProperties(string url) { Url = url; } + + public static implicit operator UnfurledMediaItemProperties(string url) => new(url); } diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs index 19d986eaa5..ad409bf64f 100644 --- a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -9,6 +9,9 @@ internal static IMessageComponent ToModel(this IMessageComponent component) { switch (component) { + case ActionRowComponent actionRow: + return new API.ActionRowComponent(actionRow); + case ButtonComponent btn: return new API.ButtonComponent(btn); @@ -50,7 +53,7 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) case ComponentType.ActionRow: { var parsed = (API.ActionRowComponent)component; - return new ActionRowComponent() + return new ActionRowComponent { Id = component.Id, Components = parsed.Components.Select(x => x.ToEntity()).ToImmutableArray() From de10572374227116b88adf0917a15df6c6faa7a2 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Wed, 5 Feb 2025 17:43:03 +0300 Subject: [PATCH 10/15] rework message flags + auto add `ComponentsV2` --- .../Builders/ComponentContainerExtensions.cs | 4 +- .../Builders/FileComponentBuilder.cs | 9 ++++ .../Builders/MediaGalleryBuilder.cs | 8 +++ .../Builders/ThumbnailBuilder.cs | 2 +- src/Discord.Net.Core/Utils/Preconditions.cs | 6 +++ .../API/Rest/CreateWebhookMessageParams.cs | 13 ++++- .../API/Rest/UploadInteractionFileParams.cs | 18 +++++-- .../API/Rest/UploadWebhookFileParams.cs | 18 +++++-- .../Entities/Channels/ChannelHelper.cs | 12 +++-- .../SocketMessageComponent.cs | 52 +++++++++++------- .../Interaction/Modals/SocketModal.cs | 53 +++++++++++-------- .../SocketBaseCommand/SocketCommandBase.cs | 52 +++++++++++------- 12 files changed, 166 insertions(+), 81 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs index cebe2471b6..797d4b4ece 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs @@ -85,12 +85,12 @@ public static BuilderT WithFile<BuilderT>(this BuilderT container, FileComponent } public static BuilderT WithFile<BuilderT>(this BuilderT container, - string url, + UnfurledMediaItemProperties file, bool isSpoiler = false, int? id = null) where BuilderT : class, IStaticComponentContainer => container.WithFile(new FileComponentBuilder() - .WithFile(new UnfurledMediaItemProperties(url)) + .WithFile(file) .WithIsSpoiler(isSpoiler) .WithId(id)); diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs index 6e7f825ecd..0322423ad2 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs @@ -10,6 +10,15 @@ public class FileComponentBuilder : IMessageComponentBuilder public bool? IsSpoiler { get; set; } + public FileComponentBuilder() {} + + public FileComponentBuilder(UnfurledMediaItemProperties media, bool isSpoiler = false, int? id = null) + { + File = media; + Id = id; + IsSpoiler = isSpoiler; + } + public FileComponentBuilder WithFile(UnfurledMediaItemProperties file) { File = file; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs index 5e5d96f272..509cbe1387 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs @@ -12,6 +12,14 @@ public class MediaGalleryBuilder : IMessageComponentBuilder private List<MediaGalleryItemProperties> _items = new(); + public MediaGalleryBuilder() { } + + public MediaGalleryBuilder(IEnumerable<MediaGalleryItemProperties> items, int? id = null) + { + Items = items.ToList(); + Id = id; + } + public List<MediaGalleryItemProperties> Items { get => _items; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs index d6e411411d..b4da4bcfc4 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs @@ -10,7 +10,7 @@ public class ThumbnailBuilder : IMessageComponentBuilder public string Description { get; set; } - public bool IsSpoiler { get; set; } = false; + public bool IsSpoiler { get; set; } public ThumbnailBuilder() { } diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 83da3a66ad..372238bf77 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -401,5 +401,11 @@ public static void Options(string name, string description) } #endregion + + public static void ValidateMessageFlags(MessageFlags flags) + { + if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification and not MessageFlags.ComponentsV2 and not MessageFlags.Ephemeral) + throw new ArgumentException("The only valid MessageFlags are Ephemeral, SuppressEmbeds, SuppressNotification, ComponentsV2 and None.", nameof(flags)); + } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 0258425c7b..e3b10738bf 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace Discord.API.Rest @@ -55,6 +56,7 @@ public IReadOnlyDictionary<string, object> ToDictionary() { var d = new Dictionary<string, object>(); + var extraFlags = MessageFlags.None; if (File.IsSpecified) { d["file"] = File.Value; @@ -77,14 +79,21 @@ public IReadOnlyDictionary<string, object> ToDictionary() payload["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) payload["allowed_mentions"] = AllowedMentions.Value; + + if (Components.IsSpecified) + { payload["components"] = Components.Value; + if (Components.Value.Any(x => x.Type is not ComponentType.ActionRow)) + extraFlags |= MessageFlags.ComponentsV2; + } + + payload["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags; + if (ThreadName.IsSpecified) payload["thread_name"] = ThreadName.Value; if (AppliedTags.IsSpecified) payload["applied_tags"] = AppliedTags.Value; - if (Flags.IsSpecified) - payload["flags"] = Flags.Value; if (Poll.IsSpecified) payload["poll"] = Poll.Value; diff --git a/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs index 26697ecbe9..0497495dfa 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadInteractionFileParams.cs @@ -44,8 +44,10 @@ public IReadOnlyDictionary<string, object> ToDictionary() { var d = new Dictionary<string, object>(); + var extraFlags = MessageFlags.None; + if (Files.Any(x => x.Waveform is not null && x.DurationSeconds is not null)) - Flags = Flags.GetValueOrDefault(MessageFlags.None) | MessageFlags.VoiceMessage; + extraFlags |= MessageFlags.VoiceMessage; var payload = new Dictionary<string, object>(); payload["type"] = Type; @@ -55,14 +57,20 @@ public IReadOnlyDictionary<string, object> ToDictionary() data["content"] = Content.Value; if (IsTTS.IsSpecified) data["tts"] = IsTTS.Value; - if (MessageComponents.IsSpecified) - data["components"] = MessageComponents.Value; if (Embeds.IsSpecified) data["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) data["allowed_mentions"] = AllowedMentions.Value; - if (Flags.IsSpecified) - data["flags"] = Flags.Value; + + if (MessageComponents.IsSpecified) + { + data["components"] = MessageComponents.Value; + if (MessageComponents.Value.Any(x => x.Type is not ComponentType.ActionRow)) + extraFlags |= MessageFlags.ComponentsV2; + } + + data["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags; + if (Poll.IsSpecified) data["poll"] = Poll.Value; diff --git a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs index 6c404475cc..4da38e9dab 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs @@ -37,8 +37,10 @@ public IReadOnlyDictionary<string, object> ToDictionary() { var d = new Dictionary<string, object>(); + var extraFlags = MessageFlags.None; + if (Files.Any(x => x.Waveform is not null && x.DurationSeconds is not null)) - Flags = Flags.GetValueOrDefault(MessageFlags.None) | MessageFlags.VoiceMessage; + extraFlags |= MessageFlags.VoiceMessage; var payload = new Dictionary<string, object>(); if (Content.IsSpecified) @@ -51,14 +53,20 @@ public IReadOnlyDictionary<string, object> ToDictionary() payload["username"] = Username.Value; if (AvatarUrl.IsSpecified) payload["avatar_url"] = AvatarUrl.Value; - if (MessageComponents.IsSpecified) - payload["components"] = MessageComponents.Value; if (Embeds.IsSpecified) payload["embeds"] = Embeds.Value; if (AllowedMentions.IsSpecified) payload["allowed_mentions"] = AllowedMentions.Value; - if (Flags.IsSpecified) - payload["flags"] = Flags.Value; + + if (MessageComponents.IsSpecified) + { + payload["components"] = MessageComponents.Value; + if (MessageComponents.Value.Any(x => x.Type is not ComponentType.ActionRow)) + extraFlags |= MessageFlags.ComponentsV2; + } + + payload["flags"] = Flags.GetValueOrDefault(MessageFlags.None) | extraFlags; + if (ThreadName.IsSpecified) payload["thread_name"] = ThreadName.Value; if (AppliedTags.IsSpecified) diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 0c8e2395bb..9229bc73f5 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -308,10 +308,10 @@ public static async Task<RestUserMessage> SendMessageAsync(IMessageChannel chann Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); } - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) - throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags)); + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; - + Preconditions.ValidateMessageFlags(flags); var args = new CreateMessageParams { @@ -434,8 +434,10 @@ public static async Task<RestUserMessage> SendFilesAsync(IMessageChannel channel } } - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) - throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags)); + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + + Preconditions.ValidateMessageFlags(flags); if (stickers != null) { diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs index 16bc37722f..984c898454 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs @@ -116,6 +116,13 @@ public override async Task RespondWithFilesAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray()) { Type = InteractionResponseType.ChannelMessageWithSource, @@ -124,11 +131,7 @@ public override async Task RespondWithFilesAsync( Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -188,6 +191,13 @@ public override async Task RespondAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var response = new API.InteractionResponse { Type = InteractionResponseType.ChannelMessageWithSource, @@ -197,11 +207,7 @@ public override async Task RespondAsync( AllowedMentions = allowedMentions?.ToModel(), Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } @@ -346,6 +352,14 @@ public override Task<RestFollowupMessage> FollowupAsync( Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.ValidatePoll(poll); + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + + var args = new API.Rest.CreateWebhookMessageParams { Content = text, @@ -354,11 +368,7 @@ public override Task<RestFollowupMessage> FollowupAsync( Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, }; return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); @@ -410,14 +420,16 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs index 90c85a260e..a6de28d332 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs @@ -112,6 +112,12 @@ public override async Task RespondWithFilesAsync( throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray()) { @@ -121,11 +127,7 @@ public override async Task RespondWithFilesAsync( Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -141,7 +143,7 @@ public override async Task RespondWithFilesAsync( HasResponded = true; } - /// <inheritdoc/> + /// <inheritdoc cref="IDiscordInteraction.RespondAsync"/> public override async Task RespondAsync( string text = null, Embed[] embeds = null, @@ -185,6 +187,13 @@ public override async Task RespondAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var response = new API.InteractionResponse { Type = InteractionResponseType.ChannelMessageWithSource, @@ -194,11 +203,7 @@ public override async Task RespondAsync( AllowedMentions = allowedMentions?.ToModel(), Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } @@ -345,6 +350,13 @@ public override Task<RestFollowupMessage> FollowupAsync( Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.ValidatePoll(poll); + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var args = new API.Rest.CreateWebhookMessageParams { Content = text, @@ -352,11 +364,7 @@ public override Task<RestFollowupMessage> FollowupAsync( IsTTS = isTTS, Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -410,13 +418,16 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 2d8dcf0509..075e4e31fa 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -111,6 +111,13 @@ public override async Task RespondAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var response = new API.InteractionResponse { Type = InteractionResponseType.ChannelMessageWithSource, @@ -121,11 +128,7 @@ public override async Task RespondAsync( Embeds = embeds.Select(x => x.ToModel()).ToArray(), TTS = isTTS, Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified } }; @@ -222,6 +225,13 @@ public override async Task RespondWithFilesAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var response = new API.Rest.UploadInteractionFileParams(attachments?.ToArray()) { Type = InteractionResponseType.ChannelMessageWithSource, @@ -229,11 +239,7 @@ public override async Task RespondWithFilesAsync( AllowedMentions = allowedMentions != null ? allowedMentions?.ToModel() : Optional<API.AllowedMentions>.Unspecified, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, IsTTS = isTTS, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, MessageComponents = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified }; @@ -275,6 +281,13 @@ public override Task<RestFollowupMessage> FollowupAsync( Preconditions.AtMost(embeds.Length, 10, nameof(embeds), "A max of 10 embeds are allowed."); Preconditions.ValidatePoll(poll); + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var args = new API.Rest.CreateWebhookMessageParams { Content = text ?? Optional<string>.Unspecified, @@ -283,11 +296,7 @@ public override Task<RestFollowupMessage> FollowupAsync( Embeds = embeds.Select(x => x.ToModel()).ToArray(), Components = components?.Components.Select(x => x.ToModel()).ToArray() ?? Optional<IMessageComponent[]>.Unspecified, Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified, - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, }; return InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); @@ -340,13 +349,16 @@ public override Task<RestFollowupMessage> FollowupWithFilesAsync( } } + if (components?.Components?.Any(x => x.Type != ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + if (ephemeral) + flags |= MessageFlags.Ephemeral; + + Preconditions.ValidateMessageFlags(flags); + var args = new API.Rest.UploadWebhookFileParams(attachments.ToArray()) { - Flags = ephemeral - ? flags | MessageFlags.Ephemeral - : flags == MessageFlags.None - ? Optional<MessageFlags>.Unspecified - : flags, + Flags = flags, Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional<API.Embed[]>.Unspecified, From 31c9c5d28c0407df8cb13eaffe62809af7e3397b Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Wed, 5 Feb 2025 23:13:41 +0300 Subject: [PATCH 11/15] apparently it's also nullable --- src/Discord.Net.Rest/API/Common/ContainerComponent.cs | 4 ++-- src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs index 50bd8b08d2..969906bcb0 100644 --- a/src/Discord.Net.Rest/API/Common/ContainerComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ContainerComponent.cs @@ -13,7 +13,7 @@ internal class ContainerComponent : IMessageComponent public Optional<int> Id { get; set; } [JsonProperty("accent_color")] - public Optional<uint> AccentColor { get; set; } + public Optional<uint?> AccentColor { get; set; } [JsonProperty("spoiler")] public Optional<bool> IsSpoiler { get; set; } @@ -27,7 +27,7 @@ public ContainerComponent(Discord.ContainerComponent component) { Type = component.Type; Id = component.Id ?? Optional<int>.Unspecified; - AccentColor = component.AccentColor ?? Optional<uint>.Unspecified; + AccentColor = component.AccentColor ?? Optional<uint?>.Unspecified; IsSpoiler = component.IsSpoiler ?? Optional<bool>.Unspecified; Components = component.Components.Select(x => x.ToModel()).ToArray(); } diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs index ad409bf64f..8918c9bffc 100644 --- a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -163,7 +163,7 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) { var parsed = (API.ContainerComponent)component; return new ContainerComponent(parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(), - parsed.AccentColor.ToNullable(), + parsed.AccentColor.GetValueOrDefault(null), parsed.IsSpoiler.ToNullable(), parsed.Id.ToNullable()); } From 2313a34c44285b7f6fa24a55a481e06014f3bae9 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sun, 9 Feb 2025 21:38:29 +0300 Subject: [PATCH 12/15] fix build --- .../MessageComponents/Builders/ComponentBuilderV2.cs | 2 +- .../Interactions/MessageComponents/FileComponent.cs | 4 ++-- .../Entities/Interactions/Modals/ModalBuilder.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs index 9bc2905cca..d961ba4fd7 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -4,7 +4,7 @@ namespace Discord; -public class ComponentBuilderV2 : IStaticComponentContainer +public class ComponentBuilderV2 : IStaticComponentContainer, IInteractableComponentContainer { public ComponentBuilderV2() {} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs index 40b89ba6e7..c1e6303105 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs @@ -4,10 +4,10 @@ public class FileComponent : IMessageComponent { public ComponentType Type => ComponentType.File; - public UnfurledMediaItem File { get; } - public int? Id { get; } + public UnfurledMediaItem File { get; } + public bool? IsSpoiler { get; } internal FileComponent(UnfurledMediaItem file, bool? isSpoiler, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs index 6e04e95d9e..0b5c9bb47a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs @@ -178,13 +178,13 @@ public ModalBuilder UpdateTextInput(string customId, object value) /// <summary> /// Removes a component from this builder by the specified <paramref name="customId"/>. /// </summary> - /// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to remove.</param> + /// <param name="customId">The <see cref="IInteractableComponent.CustomId"/> of the component to remove.</param> /// <returns>The current builder.</returns> public ModalBuilder RemoveComponent(string customId) { Preconditions.NotNull(customId, nameof(customId)); - Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c is IInteractableComponent ic && ic.CustomId == customId)); + Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c is IInteractableComponentBuilder ic && ic.CustomId == customId)); return this; } @@ -225,7 +225,7 @@ public Modal Build() public class ModalComponentBuilder { /// <summary> - /// The max length of a <see cref="IMessageComponent.CustomId"/>. + /// The max length of a <see cref="IInteractableComponent.CustomId"/>. /// </summary> public const int MaxCustomIdLength = 100; From 1f73870bb8909d14688e8ea0232a9bbee18a8d81 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Tue, 18 Feb 2025 23:58:56 +0300 Subject: [PATCH 13/15] webhook component supports + builder validations + some xmldoc --- .../Builders/ActionRowBuilder.cs | 10 +- .../Builders/ButtonBuilder.cs | 6 - .../Builders/ComponentBuilderExtensions.cs | 17 +++ .../Builders/ComponentBuilderV2.cs | 37 ++++- .../Builders/ComponentContainerExtensions.cs | 126 ++++++++++++++++++ .../Builders/ContainerBuilder.cs | 66 ++++++--- .../Builders/FileComponentBuilder.cs | 45 ++++++- .../Builders/IComponentContainer.cs | 26 +++- .../Builders/IInteractableComponentBuilder.cs | 6 + .../IInteractableComponentContainer.cs | 3 + .../Builders/IMessageComponentBuilder.cs | 9 ++ .../Builders/IStaticComponentContainer.cs | 3 + .../Builders/MediaGalleryBuilder.cs | 60 ++++++++- .../Builders/MediaGalleryItemProperties.cs | 20 +++ .../Builders/SectionBuilder.cs | 64 +++++++-- .../Builders/SelectMenuBuilder.cs | 12 +- .../Builders/SeparatorBuilder.cs | 28 +++- .../Builders/TextDisplayBuilder.cs | 35 ++++- .../Builders/ThumbnailBuilder.cs | 59 ++++++-- .../Builders/UnfurledMediaItemProperties.cs | 10 ++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 2 + .../WebhookClientHelper.cs | 13 +- 22 files changed, 565 insertions(+), 92 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderExtensions.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs index f1780f9bd9..66fb654665 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs @@ -42,7 +42,7 @@ public List<IMessageComponentBuilder> Components } - public ActionRowBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + public ActionRowBuilder AddComponents(params IMessageComponentBuilder[] components) { foreach (var component in components) AddComponent(component); @@ -179,12 +179,6 @@ public ActionRowBuilder WithButton(ButtonBuilder button) return this; } - public ActionRowBuilder WithId(int? id) - { - Id = id; - return this; - } - /// <summary> /// Builds the current builder to a <see cref="ActionRowComponent"/> that can be used within a <see cref="ComponentBuilder"/> /// </summary> @@ -220,7 +214,7 @@ internal bool CanTakeComponent(IMessageComponentBuilder component) IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); - IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs index e16a2258e6..e6c56afe99 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs @@ -262,12 +262,6 @@ public ButtonBuilder WithSkuId(ulong? skuId) return this; } - public ButtonBuilder WithId(int? id) - { - Id = id; - return this; - } - /// <summary> /// Builds this builder into a <see cref="ButtonComponent"/> to be used in a <see cref="ComponentBuilder"/>. /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderExtensions.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderExtensions.cs new file mode 100644 index 0000000000..2c930feb5c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderExtensions.cs @@ -0,0 +1,17 @@ +namespace Discord; + +public static class ComponentBuilderExtensions +{ + /// <summary> + /// Sets the custom id for the component. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> + public static BuilderT WithId<BuilderT>(this BuilderT builder, int? id) + where BuilderT : IMessageComponentBuilder + { + builder.Id = id; + return builder; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs index d961ba4fd7..d752bde326 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -6,10 +6,14 @@ namespace Discord; public class ComponentBuilderV2 : IStaticComponentContainer, IInteractableComponentContainer { - public ComponentBuilderV2() {} + /// <summary> + /// Gets the maximum number of components that can be added to this container. + /// </summary> + public const int MaxComponents = 10; private List<IMessageComponentBuilder> _components = new(); + /// <inheritdoc/> public List<IMessageComponentBuilder> Components { get => _components; @@ -19,31 +23,58 @@ public List<IMessageComponentBuilder> Components } } + /// <summary> + /// Initializes a new instance of <see cref="ComponentBuilderV2"/>. + /// </summary> + public ComponentBuilderV2() { } + + /// <inheritdoc cref="IComponentContainer.AddComponent"/> public ComponentBuilderV2 AddComponent(IMessageComponentBuilder component) { Components.Add(component); return this; } - public ComponentBuilderV2 AddComponents(params IEnumerable<IMessageComponentBuilder> components) + /// <inheritdoc cref="IComponentContainer.AddComponents"/> + public ComponentBuilderV2 AddComponents(params IMessageComponentBuilder[] components) { foreach (var component in components) Components.Add(component); return this; } + /// <inheritdoc cref="IComponentContainer.WithComponents"/> public ComponentBuilderV2 WithComponents(IEnumerable<IMessageComponentBuilder> components) { Components = components.ToList(); return this; } + /// <inheritdoc cref="IMessageComponentBuilder.Build" /> public MessageComponent Build() { + if (_components.Count is 0 or >MaxComponents) + throw new InvalidOperationException($"The number of components must be between 1 and {MaxComponents}."); + + if (_components.Any(x => + x is not ActionRowBuilder + and not SectionBuilder + and not TextDisplayBuilder + and not MediaGalleryBuilder + and not FileComponentBuilder + and not SeparatorBuilder + and not ContainerBuilder)) + throw new InvalidOperationException($"Only the following components can be at the top level: {nameof(ActionRowBuilder)}, {nameof(TextDisplayBuilder)}, {nameof(SectionBuilder)}, {nameof(MediaGalleryBuilder)}, {nameof(SeparatorBuilder)}, or {nameof(FileComponentBuilder)} components."); + return new MessageComponent(Components.Select(x => x.Build()).ToList()); } + /// <inheritdoc/> IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); - IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + + /// <inheritdoc/> + IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); + + /// <inheritdoc/> IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs index 797d4b4ece..bddf0137f1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs @@ -5,6 +5,12 @@ namespace Discord; public static class ComponentContainerExtensions { + /// <summary> + /// Adds a <see cref="TextDisplayBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, TextDisplayBuilder textDisplay) where BuilderT : class, IStaticComponentContainer { @@ -12,6 +18,12 @@ public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, TextDi return container; } + /// <summary> + /// Adds a <see cref="TextDisplayBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, string content, int? id = null) @@ -20,6 +32,12 @@ public static BuilderT WithTextDisplay<BuilderT>(this BuilderT container, .WithContent(content) .WithId(id)); + /// <summary> + /// Adds a <see cref="SectionBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSection<BuilderT>(this BuilderT container, SectionBuilder section) where BuilderT : class, IStaticComponentContainer { @@ -27,6 +45,12 @@ public static BuilderT WithSection<BuilderT>(this BuilderT container, SectionBui return container; } + /// <summary> + /// Adds a <see cref="SectionBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSection<BuilderT>(this BuilderT container, IEnumerable<TextDisplayBuilder> components, IMessageComponentBuilder accessory, @@ -38,6 +62,12 @@ public static BuilderT WithSection<BuilderT>(this BuilderT container, .WithAccessory(accessory) .WithId(id)); + /// <summary> + /// Adds a <see cref="MediaGalleryBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, MediaGalleryBuilder mediaGallery) where BuilderT : class, IStaticComponentContainer { @@ -45,6 +75,12 @@ public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, Media return container; } + /// <summary> + /// Adds a <see cref="MediaGalleryBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, IEnumerable<MediaGalleryItemProperties> items, int? id = null) where BuilderT : class, IStaticComponentContainer @@ -52,6 +88,12 @@ public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, .WithItems(items) .WithId(id)); + /// <summary> + /// Adds a <see cref="MediaGalleryBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, IEnumerable<string> urls, int? id = null) @@ -60,6 +102,12 @@ public static BuilderT WithMediaGallery<BuilderT>(this BuilderT container, .WithItems(urls.Select(x => new MediaGalleryItemProperties(new UnfurledMediaItemProperties(x)))) .WithId(id)); + /// <summary> + /// Adds a <see cref="SeparatorBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSeparator<BuilderT>(this BuilderT container, SeparatorBuilder separator) where BuilderT : class, IStaticComponentContainer { @@ -67,6 +115,12 @@ public static BuilderT WithSeparator<BuilderT>(this BuilderT container, Separato return container; } + /// <summary> + /// Adds a <see cref="SeparatorBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSeparator<BuilderT>(this BuilderT container, SeparatorSpacingSize spacing = SeparatorSpacingSize.Small, bool isDivider = true, @@ -77,6 +131,12 @@ public static BuilderT WithSeparator<BuilderT>(this BuilderT container, .WithIsDivider(isDivider) .WithId(id)); + /// <summary> + /// Adds a <see cref="FileComponentBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithFile<BuilderT>(this BuilderT container, FileComponentBuilder file) where BuilderT : class, IStaticComponentContainer { @@ -84,6 +144,12 @@ public static BuilderT WithFile<BuilderT>(this BuilderT container, FileComponent return container; } + /// <summary> + /// Adds a <see cref="FileComponentBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithFile<BuilderT>(this BuilderT container, UnfurledMediaItemProperties file, bool isSpoiler = false, @@ -94,6 +160,12 @@ public static BuilderT WithFile<BuilderT>(this BuilderT container, .WithIsSpoiler(isSpoiler) .WithId(id)); + /// <summary> + /// Adds a <see cref="ContainerBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithContainer<BuilderT>(this BuilderT container, ContainerBuilder containerComponent) where BuilderT : class, IStaticComponentContainer { @@ -101,6 +173,12 @@ public static BuilderT WithContainer<BuilderT>(this BuilderT container, Containe return container; } + /// <summary> + /// Adds a <see cref="ContainerBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithContainer<BuilderT>(this BuilderT container, IEnumerable<IMessageComponentBuilder> components, Color? accentColor = null, @@ -113,6 +191,24 @@ public static BuilderT WithContainer<BuilderT>(this BuilderT container, .WithSpoiler(isSpoiler) .WithId(id)); + /// <summary> + /// Adds a <see cref="ContainerBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> + public static BuilderT WithContainer<BuilderT>(this BuilderT container, + params IMessageComponentBuilder[] components) + where BuilderT : class, IStaticComponentContainer + => container.WithContainer(new ContainerBuilder() + .WithComponents(components)); + + /// <summary> + /// Adds a <see cref="ButtonBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithButton<BuilderT>(this BuilderT container, ButtonBuilder button) where BuilderT : class, IInteractableComponentContainer { @@ -120,6 +216,12 @@ public static BuilderT WithButton<BuilderT>(this BuilderT container, ButtonBuild return container; } + /// <summary> + /// Adds a <see cref="ButtonBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithButton<BuilderT>(this BuilderT container, string label = null, string customId = null, @@ -140,6 +242,12 @@ public static BuilderT WithButton<BuilderT>(this BuilderT container, .WithSkuId(skuId) .WithId(id)); + /// <summary> + /// Adds a <see cref="SelectMenuBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, SelectMenuBuilder selectMenu) where BuilderT : class, IInteractableComponentContainer { @@ -147,6 +255,12 @@ public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, SelectM return container; } + /// <summary> + /// Adds a <see cref="SelectMenuBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, string customId, List<SelectMenuOptionBuilder> options = null, @@ -172,6 +286,12 @@ public static BuilderT WithSelectMenu<BuilderT>(this BuilderT container, .WithDefaultValues(defaultValues) .WithId(id)); + /// <summary> + /// Adds a <see cref="ActionRowBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithActionRow<BuilderT>(this BuilderT container, ActionRowBuilder actionRow) where BuilderT : class, IStaticComponentContainer { @@ -179,6 +299,12 @@ public static BuilderT WithActionRow<BuilderT>(this BuilderT container, ActionRo return container; } + /// <summary> + /// Adds a <see cref="ActionRowBuilder"/> to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> public static BuilderT WithActionRow<BuilderT>(this BuilderT container, IEnumerable<IMessageComponentBuilder> components, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs index a7bc9efa8b..fe179868ea 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs @@ -5,74 +5,108 @@ namespace Discord; -public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContainer +public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContainer { + /// <summary> + /// The maximum number of components allowed in a container. + /// </summary> + public const int MaxComponents = 10; + + /// <inheritdoc /> public ComponentType Type => ComponentType.Container; + /// <inheritdoc /> public int? Id { get; set; } private List<IMessageComponentBuilder> _components = new(); + /// <inheritdoc/> public List<IMessageComponentBuilder> Components { get => _components; set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); } + /// <summary> + /// Gets or sets the accent color of this container. + /// </summary> public uint? AccentColor { get; set; } + /// <summary> + /// Gets or sets whether this container is a spoiler. + /// </summary> public bool? IsSpoiler { get; set; } - public ContainerBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <summary> + /// Sets the accent color of this container. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public ContainerBuilder WithAccentColor(Color? color) { AccentColor = color?.RawValue; return this; } + /// <summary> + /// Sets whether this container is a spoiler. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public ContainerBuilder WithSpoiler(bool isSpoiler) { IsSpoiler = isSpoiler; return this; } + /// <inheritdoc cref="IComponentContainer.AddComponent"/> public ContainerBuilder AddComponent(IMessageComponentBuilder component) { Components.Add(component); return this; } - public ContainerBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + /// <inheritdoc cref="IComponentContainer.AddComponents"/> + public ContainerBuilder AddComponents(params IMessageComponentBuilder[] components) { foreach (var component in components) Components.Add(component); return this; } + /// <inheritdoc cref="IComponentContainer.WithComponents"/> public ContainerBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) { Components = components.ToList(); return this; } - public ContainerBuilder WithComponents(List<IMessageComponentBuilder> components) - { - Components = components; - return this; - } - + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public ContainerComponent Build() { + if (_components.Count is 0 or > MaxComponents) + throw new InvalidOperationException($"A container must have between 1 and {MaxComponents} components."); + + if (_components.Any(x => x + is not ActionRowBuilder + and not TextDisplayBuilder + and not SectionBuilder + and not MediaGalleryBuilder + and not SeparatorBuilder + and not FileComponentBuilder)) + throw new InvalidOperationException($"A container can only contain {nameof(ActionRowBuilder)}, {nameof(TextDisplayBuilder)}, {nameof(SectionBuilder)}, {nameof(MediaGalleryBuilder)}, {nameof(SeparatorBuilder)}, or {nameof(FileComponentBuilder)} components."); + return new(Components.ConvertAll(x => x.Build()).ToImmutableArray(), AccentColor, IsSpoiler, Id); } - + + /// <inheritdoc /> IMessageComponent IMessageComponentBuilder.Build() => Build(); + /// <inheritdoc /> IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); - IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + /// <inheritdoc /> + IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); + /// <inheritdoc /> IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs index 0322423ad2..d2851b7bef 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/FileComponentBuilder.cs @@ -1,17 +1,36 @@ +using System; + namespace Discord; public class FileComponentBuilder : IMessageComponentBuilder { + /// <inheritdoc /> public ComponentType Type => ComponentType.File; + /// <inheritdoc /> public int? Id { get; set; } + /// <summary> + /// Gets or sets the file for the component. + /// </summary> + /// <remarks> + /// Only attachment URLs are supported. + /// </remarks> public UnfurledMediaItemProperties File { get; set; } + /// <summary> + /// Gets or sets whether the file is a spoiler. + /// </summary> public bool? IsSpoiler { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="FileComponentBuilder"/>. + /// </summary> public FileComponentBuilder() {} + /// <summary> + /// Initializes a new instance of the <see cref="FileComponentBuilder"/>. + /// </summary> public FileComponentBuilder(UnfurledMediaItemProperties media, bool isSpoiler = false, int? id = null) { File = media; @@ -19,28 +38,42 @@ public FileComponentBuilder(UnfurledMediaItemProperties media, bool isSpoiler = IsSpoiler = isSpoiler; } + /// <summary> + /// Sets the file for the component. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public FileComponentBuilder WithFile(UnfurledMediaItemProperties file) { File = file; return this; } + /// <summary> + /// Sets whether the file is a spoiler. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public FileComponentBuilder WithIsSpoiler(bool? isSpoiler) { IsSpoiler = isSpoiler; return this; } - public FileComponentBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <inheritdoc cref="IMessageComponentBuilder.Build" /> public FileComponent Build() { + if (string.IsNullOrWhiteSpace(File.Url)) + throw new InvalidOperationException("File URL must be set."); + + if (!File.Url.StartsWith("attachment://")) + throw new InvalidOperationException("File URL must be an attachment URL."); + return new(new UnfurledMediaItem(File.Url), IsSpoiler, Id); } + /// <inheritdoc /> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs index c8f28045b0..2c2734fb7c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs @@ -2,13 +2,37 @@ namespace Discord; +/// <summary> +/// Represents a container with child components. +/// </summary> public interface IComponentContainer { + /// <summary> + /// Gets the components in the container. + /// </summary> List<IMessageComponentBuilder> Components { get; } + /// <summary> + /// Adds a component to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> IComponentContainer AddComponent(IMessageComponentBuilder component); - IComponentContainer AddComponents(params IEnumerable<IMessageComponentBuilder> components); + /// <summary> + /// Adds components to the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> + IComponentContainer AddComponents(params IMessageComponentBuilder[] components); + /// <summary> + /// Sets the components in the container. + /// </summary> + /// <returns> + /// The current container. + /// </returns> IComponentContainer WithComponents(IEnumerable<IMessageComponentBuilder> components); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs index 703d9950b7..242060fcdd 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentBuilder.cs @@ -1,6 +1,12 @@ namespace Discord; +/// <summary> +/// Represents a builder for an interactable component. +/// </summary> public interface IInteractableComponentBuilder : IMessageComponentBuilder { + /// <summary> + /// Gets or sets the custom id for the component. + /// </summary> string CustomId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs index 69c310a120..c86a189e79 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IInteractableComponentContainer.cs @@ -1,5 +1,8 @@ namespace Discord; +/// <summary> +/// Represents a container for interactable components. +/// </summary> public interface IInteractableComponentContainer : IComponentContainer { diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs index cb5ab6e511..88305308c1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IMessageComponentBuilder.cs @@ -2,9 +2,18 @@ namespace Discord; public interface IMessageComponentBuilder { + /// <summary> + /// Gets the type of the component. + /// </summary> ComponentType Type { get; } + /// <summary> + /// Gets or sets the id for the component. An autoincremented id will be assigned if not set. + /// </summary> int? Id { get; set; } + /// <summary> + /// Runs validation checks and builds the component. + /// </summary> IMessageComponent Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs index aed2af1b54..829b77bd09 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IStaticComponentContainer.cs @@ -1,5 +1,8 @@ namespace Discord; +/// <summary> +/// Represents a container for static components. +/// </summary> public interface IStaticComponentContainer : IComponentContainer { diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs index 509cbe1387..b6121acc35 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -6,38 +7,72 @@ namespace Discord; public class MediaGalleryBuilder : IMessageComponentBuilder { + /// <summary> + /// Gets the maximum number of items that can be added to a media gallery. + /// </summary> + public const int MaxItems = 10; + + /// <inheritdoc/> public ComponentType Type => ComponentType.MediaGallery; + /// <inheritdoc/> public int? Id { get; set; } private List<MediaGalleryItemProperties> _items = new(); + /// <summary> + /// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>. + /// </summary> public MediaGalleryBuilder() { } + /// <summary> + /// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>. + /// </summary> public MediaGalleryBuilder(IEnumerable<MediaGalleryItemProperties> items, int? id = null) { Items = items.ToList(); Id = id; } + /// <summary> + /// Gets or sets the items in this media gallery. + /// </summary> public List<MediaGalleryItemProperties> Items { get => _items; set => _items = value; } + /// <summary> + /// Adds a new item to the media gallery. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public MediaGalleryBuilder AddItem(MediaGalleryItemProperties item) { _items.Add(item); return this; } + /// <summary> + /// Adds a new item to the media gallery. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public MediaGalleryBuilder AddItem(string url, string description = null, bool isSpoiler = false) { _items.Add(new MediaGalleryItemProperties(new UnfurledMediaItemProperties(url), description, isSpoiler)); return this; } + /// <summary> + /// Adds a list of items to the media gallery. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public MediaGalleryBuilder AddItems(params IEnumerable<MediaGalleryItemProperties> items) { foreach (var item in items) @@ -45,22 +80,35 @@ public MediaGalleryBuilder AddItems(params IEnumerable<MediaGalleryItemPropertie return this; } + /// <summary> + /// Sets the items in the media gallery. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public MediaGalleryBuilder WithItems(IEnumerable<MediaGalleryItemProperties> items) { _items = items.ToList(); return this; } - public MediaGalleryBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public MediaGalleryComponent Build() { + if (_items.Any(x => (x.Description?.Length ?? 0) > MediaGalleryItemProperties.MaxDescriptionLength)) + throw new ArgumentException($"{nameof(MediaGalleryItemProperties)} description length cannot exceed {MediaGalleryItemProperties.MaxDescriptionLength} characters."); + + if (_items.Any(x => !(x.Media.Url?.StartsWith("http://") ?? false) + && !(x.Media.Url?.StartsWith("https://") ?? false) + && !(x.Media.Url?.StartsWith("attachment://") ?? false))) + throw new ArgumentException($"{nameof(MediaGalleryItemProperties)} description must be a valid URL or attachment."); + + if (_items.Count is 0 or > MaxItems) + throw new ArgumentOutOfRangeException(nameof(Items), $"Media gallery items count must be in range [1, {MaxItems}]"); + return new(_items.Select(x => new MediaGalleryItem(new UnfurledMediaItem(x.Media.Url), x.Description, x.IsSpoiler)).ToImmutableArray(), Id); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs index 9bbc8e02c4..32d7d97aa8 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryItemProperties.cs @@ -2,14 +2,34 @@ namespace Discord; public struct MediaGalleryItemProperties { + /// <summary> + /// The maximum length of the description. + /// </summary> + public const int MaxDescriptionLength = 256; + + /// <summary> + /// Gets or sets the media item to display. + /// </summary> public UnfurledMediaItemProperties Media { get; set; } + /// <summary> + /// Gets or sets the description of the media item. + /// </summary> public string Description { get; set; } + /// <summary> + /// Gets or sets whether the media item is a spoiler. + /// </summary> public bool IsSpoiler { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="MediaGalleryItemProperties"/>. + /// </summary> public MediaGalleryItemProperties() { } + /// <summary> + /// Initializes a new instance of the <see cref="MediaGalleryItemProperties"/>. + /// </summary> public MediaGalleryItemProperties(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false) { Media = media; diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs index 43251fbfda..6c9727f974 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs @@ -7,57 +7,101 @@ namespace Discord; public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContainer { + /// <summary> + /// Gets the maximum number of components allowed in this container. + /// </summary> + public const int MaxComponents = 3; + + /// <inheritdoc/> public ComponentType Type => ComponentType.Section; + /// <inheritdoc/> public int? Id { get; set; } + /// <summary> + /// Gets or sets the accessory component. + /// </summary> + /// <remarks> + /// Only supports <see cref="ButtonBuilder"/> and <see cref="ThumbnailBuilder"/> currently. + /// </remarks> + public IMessageComponentBuilder Accessory { get; set; } + private List<IMessageComponentBuilder> _components = new(); + + /// <inheritdoc/> + /// <remarks> + /// Only <see cref="TextDisplayBuilder"/> is supported. + /// </remarks> public List<IMessageComponentBuilder> Components { get => _components; set => _components = value ?? throw new ArgumentNullException(nameof(value), $"{nameof(Components)} cannot be null."); } + /// <inheritdoc cref="IComponentContainer.AddComponent"/> + /// <remarks> + /// Only <see cref="TextDisplayBuilder"/> is supported. + /// </remarks> public SectionBuilder AddComponent(IMessageComponentBuilder component) { Components.Add(component); return this; } - public SectionBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components) + /// <inheritdoc cref="IComponentContainer.AddComponents"/> + /// <remarks> + /// Only <see cref="TextDisplayBuilder"/> is supported. + /// </remarks> + public SectionBuilder AddComponents(params IMessageComponentBuilder[] components) { foreach (var component in components) AddComponent(component); return this; } + /// <inheritdoc cref="IComponentContainer.WithComponents"/> + /// <remarks> + /// Only <see cref="TextDisplayBuilder"/> is supported. + /// </remarks> public SectionBuilder WithComponents(IEnumerable<IMessageComponentBuilder> components) { Components = components.ToList(); return this; } - public IMessageComponentBuilder Accessory { get; set; } - - public SectionBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <summary> + /// Sets the accessory component. + /// </summary> public SectionBuilder WithAccessory(IMessageComponentBuilder accessory) { Accessory = accessory; return this; } + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public SectionComponent Build() { + if (_components.Count is 0 or > MaxComponents) + throw new InvalidOperationException($"Section component can only contain {MaxComponents} child components!"); + + if (_components.Any(x => x is not TextDisplayBuilder)) + throw new InvalidOperationException($"Section component can only contain {nameof(TextDisplayBuilder)}!"); + + if (Accessory is null) + throw new ArgumentNullException(nameof(Accessory), "A section must have an accessory"); + + if (Accessory is not ButtonBuilder and not ThumbnailBuilder) + throw new InvalidOperationException($"Accessory component can only be {nameof(ButtonBuilder)} or {nameof(ThumbnailBuilder)}!"); + return new(Id, Components.Select(x => x.Build()).ToImmutableArray(), Accessory?.Build()); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); + /// <inheritdoc/> IComponentContainer IComponentContainer.AddComponent(IMessageComponentBuilder component) => AddComponent(component); - IComponentContainer IComponentContainer.AddComponents(params IEnumerable<IMessageComponentBuilder> components) => AddComponents(components); + /// <inheritdoc/> + IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); + /// <inheritdoc/> IComponentContainer IComponentContainer.WithComponents(IEnumerable<IMessageComponentBuilder> components) => WithComponents(components.ToList()); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs index c68e8623a9..81f1def8a1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SelectMenuBuilder.cs @@ -136,15 +136,16 @@ public List<SelectMenuDefaultValue> DefaultValues } } + /// <inheritdoc/> public int? Id { get; set; } - private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>(); + private List<SelectMenuOptionBuilder> _options = []; private int _minValues = 1; private int _maxValues = 1; private string _placeholder; private string _customId; private ComponentType _type = ComponentType.SelectMenu; - private List<SelectMenuDefaultValue> _defaultValues = new(); + private List<SelectMenuDefaultValue> _defaultValues = []; /// <summary> /// Creates a new instance of a <see cref="SelectMenuBuilder"/>. @@ -397,12 +398,6 @@ public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes) return this; } - public SelectMenuBuilder WithId(int? id) - { - Id = id; - return this; - } - /// <summary> /// Builds a <see cref="SelectMenuComponent"/> /// </summary> @@ -414,5 +409,6 @@ public SelectMenuComponent Build() return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, Id, ChannelTypes, DefaultValues); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs index 085d18a960..d308eddc60 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SeparatorBuilder.cs @@ -2,36 +2,52 @@ namespace Discord; public class SeparatorBuilder : IMessageComponentBuilder { + /// <inheritdoc/> public ComponentType Type => ComponentType.Separator; + /// <summary> + /// Gets or sets whether the component is a divider. + /// </summary> public bool? IsDivider { get; set; } + /// <summary> + /// Gets or sets the spacing of the separator. + /// </summary> public SeparatorSpacingSize? Spacing { get; set; } + /// <inheritdoc/> public int? Id { get; set; } + /// <summary> + /// Sets whether the component is a divider. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public SeparatorBuilder WithIsDivider(bool? isDivider) { IsDivider = isDivider; return this; } + /// <summary> + /// Sets the spacing of the separator. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public SeparatorBuilder WithSpacing(SeparatorSpacingSize? spacing) { Spacing = spacing; return this; } - public SeparatorBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public SeparatorComponent Build() { return new(IsDivider, Spacing, Id); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs index 484e85d94b..eb2234eb42 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/TextDisplayBuilder.cs @@ -1,37 +1,60 @@ +using System; + namespace Discord; public class TextDisplayBuilder : IMessageComponentBuilder { + /// <summary> + /// The maximum length of the content. + /// </summary> + public const int MaxContentLength = 4096; + + /// <inheritdoc/> public ComponentType Type => ComponentType.ActionRow; + /// <inheritdoc/> public int? Id { get; set; } + /// <summary> + /// Gets or sets the content of the text display. + /// </summary> public string Content { get; set; } + /// <summary> + /// Initializes a new <see cref="TextDisplayBuilder"/>. + /// </summary> public TextDisplayBuilder() { } + /// <summary> + /// Initializes a new <see cref="TextDisplayBuilder"/> with the specified content. + /// </summary> public TextDisplayBuilder(string content, int? id = null) { Content = content; Id = id; } + /// <summary> + /// Sets the content of the text display. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public TextDisplayBuilder WithContent(string content) { Content = content; return this; } - public TextDisplayBuilder WithId(int? id) - { - Id = id; - return this; - } - + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public TextDisplayComponent Build() { + if (Content.Length > MaxContentLength) + throw new ArgumentException($"Content length must be less than or equal to {MaxContentLength}.", nameof(Content)); + return new(Content, Id); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs index b4da4bcfc4..79a5fa05fe 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ThumbnailBuilder.cs @@ -1,19 +1,43 @@ +using System; + namespace Discord; public class ThumbnailBuilder : IMessageComponentBuilder { + /// <summary> + /// Gets the maximum length of the description. + /// </summary> + public const int MaxDescriptionLength = 1024; + + /// <inheritdoc/> public ComponentType Type => ComponentType.Thumbnail; + /// <inheritdoc/> public int? Id { get; set; } + /// <summary> + /// Gets or sets the media of the thumbnail. + /// </summary> public UnfurledMediaItemProperties Media { get; set; } + /// <summary> + /// Gets or sets the description of the thumbnail. + /// </summary> public string Description { get; set; } + /// <summary> + /// Gets or sets whether the thumbnail is a spoiler. + /// </summary> public bool IsSpoiler { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="ThumbnailBuilder"/>. + /// </summary> public ThumbnailBuilder() { } + /// <summary> + /// Initializes a new instance of the <see cref="ThumbnailBuilder"/>. + /// </summary> public ThumbnailBuilder(UnfurledMediaItemProperties media, string description = null, bool isSpoiler = false) { Media = media; @@ -21,40 +45,51 @@ public ThumbnailBuilder(UnfurledMediaItemProperties media, string description = IsSpoiler = isSpoiler; } + /// <summary> + /// Sets the media of the thumbnail. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public ThumbnailBuilder WithMedia(UnfurledMediaItemProperties media) { Media = media; return this; } - public ThumbnailBuilder WithMedia(string url) - { - Media = new UnfurledMediaItemProperties(url); - return this; - } - + /// <summary> + /// Sets the description of the thumbnail. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public ThumbnailBuilder WithDescription(string description) { Description = description; return this; } - public ThumbnailBuilder WithId(int id) - { - Id = id; - return this; - } - + /// <summary> + /// Sets whether the thumbnail is a spoiler. + /// </summary> + /// <returns> + /// The current builder. + /// </returns> public ThumbnailBuilder WithSpoiler(bool isSpoiler) { IsSpoiler = isSpoiler; return this; } + /// <inheritdoc cref="IMessageComponentBuilder.Build"/> public ThumbnailComponent Build() { + if (Description is not null && Description.Length > MaxDescriptionLength) + throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); + return new(Id, new UnfurledMediaItem(Media.Url), Description, IsSpoiler); } + /// <inheritdoc/> IMessageComponent IMessageComponentBuilder.Build() => Build(); } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs index c9d2b85d30..1bb2f85eb0 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/UnfurledMediaItemProperties.cs @@ -2,9 +2,19 @@ namespace Discord; public struct UnfurledMediaItemProperties { + /// <summary> + /// Gets or sets the URL of the media item. + /// </summary> public string Url { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="UnfurledMediaItemProperties"/>. + /// </summary> public UnfurledMediaItemProperties() {} + + /// <summary> + /// Initializes a new instance of the <see cref="UnfurledMediaItemProperties"/>. + /// </summary> public UnfurledMediaItemProperties(string url) { Url = url; diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 91d533a596..765b4cd2ce 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -2779,6 +2779,8 @@ private static string WebhookQuery(bool wait = false, ulong? threadId = null) if (threadId.HasValue) querys.Add($"thread_id={threadId}"); + querys.Add("with_components=true"); + return $"{string.Join("&", querys)}"; } diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index d6b62f8f2b..b8861de93c 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -29,6 +29,11 @@ public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, MessageFlags flags, ulong? threadId = null, string threadName = null, ulong[] appliedTags = null, PollProperties poll = null) { + if (components?.Components.Any(x => x.Type is not ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + + Preconditions.ValidateMessageFlags(flags); + var args = new CreateWebhookMessageParams { Content = text, @@ -56,8 +61,6 @@ public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client, if (poll != null) args.Poll = poll.ToModel(); - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) - throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags)); var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options, threadId: threadId).ConfigureAwait(false); return model.Id; @@ -193,8 +196,10 @@ public static async Task<ulong> SendFilesAsync(DiscordWebhookClient client, } } - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) - throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags)); + if (components?.Components.Any(x => x.Type is not ComponentType.ActionRow) ?? false) + flags |= MessageFlags.ComponentsV2; + + Preconditions.ValidateMessageFlags(flags); var args = new UploadWebhookFileParams(attachments.ToArray()) { From 2d665980cbb6b695940838b3278f392c74396803 Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Wed, 19 Feb 2025 00:27:14 +0300 Subject: [PATCH 14/15] `ResolvedUnfurledMediaItem` + fix missing accessory on sections --- .../ResolvedUnfurledMediaItem.cs | 23 ++++++++++++++++++ .../MessageComponents/UnfurledMediaItem.cs | 2 +- .../UnfurledMediaItemLoadingState.cs | 24 +++++++++++++++++++ .../API/Common/ThumbnailComponent.cs | 3 ++- .../API/Common/UnfurledMediaItem.cs | 17 ++++++++++++- .../Extensions/MessageComponentExtension.cs | 17 ++++++++++--- 6 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs create mode 100644 src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItemLoadingState.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs new file mode 100644 index 0000000000..f75e8bdcd8 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs @@ -0,0 +1,23 @@ +namespace Discord; + +public class ResolvedUnfurledMediaItem : UnfurledMediaItem +{ + public string ProxyUrl { get; } + + public int Height { get; } + + public int Width { get; } + + public string ContentType { get;} + + public UnfurledMediaItemLoadingState LoadingState { get; } + + internal ResolvedUnfurledMediaItem(string url, string proxyUrl, int height, int width, string contentType, UnfurledMediaItemLoadingState loadingState) : base(url) + { + ProxyUrl = proxyUrl; + Height = height; + Width = width; + ContentType = contentType; + LoadingState = loadingState; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs index d9cb7b9592..97bf2becd6 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs @@ -1,6 +1,6 @@ namespace Discord; -public readonly struct UnfurledMediaItem +public class UnfurledMediaItem { public string Url { get; } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItemLoadingState.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItemLoadingState.cs new file mode 100644 index 0000000000..f6df157c45 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItemLoadingState.cs @@ -0,0 +1,24 @@ +namespace Discord; + +public enum UnfurledMediaItemLoadingState +{ + /// <summary> + /// The state of the media item is unknown. + /// </summary> + Unknown = 0, + + /// <summary> + /// The media item is currently loading. + /// </summary> + Loading = 1, + + /// <summary> + /// The media item was successfully loaded. + /// </summary> + LoadingSuccess = 2, + + /// <summary> + /// The media item was not found. + /// </summary> + LoadingNotFound = 3 +} diff --git a/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs index 8ee7314fcb..bc8b40dc38 100644 --- a/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs +++ b/src/Discord.Net.Rest/API/Common/ThumbnailComponent.cs @@ -1,3 +1,4 @@ +using Discord.Rest; using Newtonsoft.Json; namespace Discord.API; @@ -25,7 +26,7 @@ public ThumbnailComponent(Discord.ThumbnailComponent component) { Type = component.Type; Id = component.Id ?? Optional<int>.Unspecified; - Media = new UnfurledMediaItem { Url = component.Media.Url }; + Media = component.Media.ToModel(); Description = component.Description; IsSpoiler = component.IsSpoiler; } diff --git a/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs b/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs index 60635c41a7..558d8d4141 100644 --- a/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs +++ b/src/Discord.Net.Rest/API/Common/UnfurledMediaItem.cs @@ -2,8 +2,23 @@ namespace Discord.API; -public class UnfurledMediaItem +internal class UnfurledMediaItem { [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("proxy_url")] + public Optional<string> ProxyUrl { get; set; } + + [JsonProperty("height")] + public Optional<int> Height { get; set; } + + [JsonProperty("width")] + public Optional<int> Width { get; set; } + + [JsonProperty("content_type")] + public Optional<string> ContentType { get; set; } + + [JsonProperty("loading_state")] + public Optional<UnfurledMediaItemLoadingState> LoadingState { get; set; } } diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs index 8918c9bffc..881b1f6c00 100644 --- a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -129,13 +129,18 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) case ComponentType.Section: { var parsed = (API.SectionComponent)component; - return new SectionComponent(parsed.Id.ToNullable(), parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(), parsed.Accessory.ToModel()); + return new SectionComponent(parsed.Id.ToNullable(), + parsed.Components.Select(x => x.ToEntity()).ToImmutableArray(), + parsed.Accessory.ToEntity()); } case ComponentType.Thumbnail: { var parsed = (API.ThumbnailComponent)component; - return new ThumbnailComponent(parsed.Id.ToNullable(), parsed.Media.ToEntity(), parsed.Description.GetValueOrDefault(null), parsed.IsSpoiler.ToNullable()); + return new ThumbnailComponent(parsed.Id.ToNullable(), + parsed.Media.ToEntity(), + parsed.Description.GetValueOrDefault(null), + parsed.IsSpoiler.ToNullable()); } case ComponentType.MediaGallery: @@ -175,8 +180,14 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) internal static UnfurledMediaItem ToEntity(this API.UnfurledMediaItem mediaItem) { - return new UnfurledMediaItem(mediaItem.Url); + return new ResolvedUnfurledMediaItem(mediaItem.Url, + mediaItem.ProxyUrl.Value, + mediaItem.Height.Value, + mediaItem.Width.Value, + mediaItem.ContentType.Value, + mediaItem.LoadingState.Value); } + internal static API.UnfurledMediaItem ToModel(this UnfurledMediaItem mediaItem) { return new API.UnfurledMediaItem From c67aa77f2541e5aec2a0fd0129be1447db3a0b7e Mon Sep 17 00:00:00 2001 From: Misha133 <mihagribkov133@gmail.com> Date: Sun, 9 Mar 2025 21:02:17 +0300 Subject: [PATCH 15/15] missing xmldoc --- .../Builders/ComponentBuilderV2.cs | 2 +- .../MessageComponents/ContainerComponent.cs | 14 ++++++++++++++ .../MessageComponents/FileComponent.cs | 11 +++++++++++ .../IInteractableComponent.cs | 3 +++ .../MessageComponents/MediaGalleryComponent.cs | 8 ++++++++ .../MessageComponents/MediaGalleryItem.cs | 12 ++++++++++++ .../ResolvedUnfurledMediaItem.cs | 18 ++++++++++++++++++ .../MessageComponents/SectionComponent.cs | 11 +++++++++++ .../MessageComponents/SeparatorComponent.cs | 11 +++++++++++ .../MessageComponents/SeparatorSpacingSize.cs | 9 +++++++++ .../MessageComponents/TextDisplayComponent.cs | 8 ++++++++ .../MessageComponents/TextInputComponent.cs | 2 -- .../MessageComponents/TextInputStyle.cs | 1 + .../MessageComponents/ThumbnailComponent.cs | 14 ++++++++++++++ .../MessageComponents/UnfurledMediaItem.cs | 6 ++++++ .../Entities/Messages/FileAttachment.cs | 11 +++++++++++ .../Entities/Messages/SocketMessage.cs | 2 +- 17 files changed, 139 insertions(+), 4 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs index d752bde326..8c119a2810 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -4,7 +4,7 @@ namespace Discord; -public class ComponentBuilderV2 : IStaticComponentContainer, IInteractableComponentContainer +public class ComponentBuilderV2 : IStaticComponentContainer { /// <summary> /// Gets the maximum number of components that can be added to this container. diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs index f434a0e021..79a40b153a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ContainerComponent.cs @@ -2,16 +2,30 @@ namespace Discord; +/// <summary> +/// Represents a container component. +/// </summary> public class ContainerComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.Container; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the components in this container. + /// </summary> public IReadOnlyCollection<IMessageComponent> Components { get; } + /// <summary> + /// Gets the accent color of this container. + /// </summary> public uint? AccentColor { get; } + /// <summary> + /// Gets whether this container is a spoiler. + /// </summary> public bool? IsSpoiler { get; } internal ContainerComponent(IReadOnlyCollection<IMessageComponent> components, uint? accentColor, bool? isSpoiler, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs index c1e6303105..91a3c75f8c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/FileComponent.cs @@ -1,13 +1,24 @@ namespace Discord; +/// <summary> +/// Represents a file component. +/// </summary> public class FileComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.File; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the file of this component. + /// </summary> public UnfurledMediaItem File { get; } + /// <summary> + /// Gets whether this file is a spoiler. + /// </summary> public bool? IsSpoiler { get; } internal FileComponent(UnfurledMediaItem file, bool? isSpoiler, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs index 61c2265889..013004908b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IInteractableComponent.cs @@ -1,5 +1,8 @@ namespace Discord; +/// <summary> +/// Represents a message component that can be interacted with. +/// </summary> public interface IInteractableComponent : IMessageComponent { /// <summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs index d08ecaa545..ce3c398917 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryComponent.cs @@ -2,12 +2,20 @@ namespace Discord; +/// <summary> +/// Represents a media gallery component. +/// </summary> public class MediaGalleryComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.MediaGallery; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the items in this media gallery. + /// </summary> public IReadOnlyCollection<MediaGalleryItem> Items { get; } internal MediaGalleryComponent(IReadOnlyCollection<MediaGalleryItem> items, int? id) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs index db1b4c0d17..da33f7c91c 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MediaGalleryItem.cs @@ -1,11 +1,23 @@ namespace Discord; +/// <summary> +/// Represents a media gallery item. +/// </summary> public readonly struct MediaGalleryItem { + /// <summary> + /// Gets the media for this item. + /// </summary> public UnfurledMediaItem Media { get; } + /// <summary> + /// Gets the description for this item. + /// </summary> public string Description { get; } + /// <summary> + /// Gets whether this item is a spoiler. + /// </summary> public bool IsSpoiler { get; } internal MediaGalleryItem(UnfurledMediaItem media, string description, bool? isSpoiler) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs index f75e8bdcd8..d7cfd1b754 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ResolvedUnfurledMediaItem.cs @@ -1,15 +1,33 @@ namespace Discord; +/// <summary> +/// Represents a media item that has been unfurled and resolved. +/// </summary> public class ResolvedUnfurledMediaItem : UnfurledMediaItem { + /// <summary> + /// Gets the proxy URL for this media item. + /// </summary> public string ProxyUrl { get; } + /// <summary> + /// Gets the height of this media item. + /// </summary> public int Height { get; } + /// <summary> + /// Gets the width of this media item. + /// </summary> public int Width { get; } + /// <summary> + /// Gets the content type of this media item. + /// </summary> public string ContentType { get;} + /// <summary> + /// Gets the loading state of this media item. + /// </summary> public UnfurledMediaItemLoadingState LoadingState { get; } internal ResolvedUnfurledMediaItem(string url, string proxyUrl, int height, int width, string contentType, UnfurledMediaItemLoadingState loadingState) : base(url) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs index 90a7e12df7..52d71e3419 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SectionComponent.cs @@ -2,14 +2,25 @@ namespace Discord; +/// <summary> +/// Represents a section component. +/// </summary> public class SectionComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.Section; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the components in this section. + /// </summary> public IReadOnlyCollection<IMessageComponent> Components { get; } + /// <summary> + /// Gets the accessory of this section. + /// </summary> public IMessageComponent Accessory { get; } internal SectionComponent(int? id, IReadOnlyCollection<IMessageComponent> components, IMessageComponent accessory) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs index b722683a3a..00c2e7cb9a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorComponent.cs @@ -1,13 +1,24 @@ namespace Discord; +/// <summary> +/// Represents a separator component. +/// </summary> public class SeparatorComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.Separator; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets whether this component is a divider. + /// </summary> public bool? IsDivider { get; } + /// <summary> + /// Gets the spacing of this component. + /// </summary> public SeparatorSpacingSize? Spacing { get; } internal SeparatorComponent(bool? isDivider, SeparatorSpacingSize? spacing, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs index 405b08c58d..0336eb747a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SeparatorSpacingSize.cs @@ -1,8 +1,17 @@ namespace Discord; +/// <summary> +/// Represents the spacing of a separator component. +/// </summary> public enum SeparatorSpacingSize { + /// <summary> + /// The separator has a small spacing. + /// </summary> Small = 1, + /// <summary> + /// The separator has a large spacing. + /// </summary> Large = 2 } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs index e96bac84e9..e2aec7e858 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextDisplayComponent.cs @@ -1,11 +1,19 @@ namespace Discord; +/// <summary> +/// Represents a text display component. +/// </summary> public class TextDisplayComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.TextDisplay; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the content of this component. + /// </summary> public string Content { get; } internal TextDisplayComponent(string content, int? id = null) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs index c462d0c45c..d76014bc07 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs @@ -1,5 +1,3 @@ -using Newtonsoft.Json; - namespace Discord { /// <summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputStyle.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputStyle.cs index 9bbcf687f9..292f26b4fb 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputStyle.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputStyle.cs @@ -6,6 +6,7 @@ public enum TextInputStyle /// Intended for short, single-line text. /// </summary> Short = 1, + /// <summary> /// Intended for longer or multiline text. /// </summary> diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs index fbbaf34ecd..b42233923b 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ThumbnailComponent.cs @@ -1,15 +1,29 @@ namespace Discord; +/// <summary> +/// Represents a thumbnail component. +/// </summary> public class ThumbnailComponent : IMessageComponent { + /// <inheritdoc/> public ComponentType Type => ComponentType.Thumbnail; + /// <inheritdoc/> public int? Id { get; } + /// <summary> + /// Gets the media of the component. + /// </summary> public UnfurledMediaItem Media { get; } + /// <summary> + /// Gets the description of the component. + /// </summary> public string Description { get; } + /// <summary> + /// Gets whether the component is a spoiler. + /// </summary> public bool IsSpoiler { get; } internal ThumbnailComponent(int? id, UnfurledMediaItem media, string description, bool? isSpoiler) diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs index 97bf2becd6..52177794ed 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/UnfurledMediaItem.cs @@ -1,7 +1,13 @@ namespace Discord; +/// <summary> +/// Represents a media item that has been unfurled. +/// </summary> public class UnfurledMediaItem { + /// <summary> + /// Gets the URL of this media item. + /// </summary> public string Url { get; } internal UnfurledMediaItem(string url) diff --git a/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs b/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs index 57bb8ba262..c43b3f3e8a 100644 --- a/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs +++ b/src/Discord.Net.Core/Entities/Messages/FileAttachment.cs @@ -123,5 +123,16 @@ public void Dispose() _isDisposed = true; } } + + /// <summary> + /// Gets the url formatted with <c>attachment://</c> protocol. + /// </summary> + /// <returns> + /// The formatted url. + /// </returns> + public string GetAttachmentUrl() + { + return $"attachment://{FileName}"; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 882050789a..4cba1d73d8 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -67,7 +67,7 @@ public abstract class SocketMessage : SocketEntity<ulong>, IMessage /// <inheritdoc /> public MessageReference Reference { get; private set; } - /// <inheritdoc/> + /// <inheritdoc cref="IMessage.Components"/> public IReadOnlyCollection<IMessageComponent> Components { get; private set; } /// <summary>