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 0b4b5f3330..06ddbd5e08 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ButtonBuilder.cs @@ -9,6 +9,7 @@ namespace Discord; /// public class ButtonBuilder : IInteractableComponentBuilder { + /// public ComponentType Type => ComponentType.Button; /// diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/LabelBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/LabelBuilder.cs new file mode 100644 index 0000000000..627b9df922 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/LabelBuilder.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Immutable; +using System.Linq; + +namespace Discord; + +public class LabelBuilder : IMessageComponentBuilder +{ + /// + public ImmutableArray SupportedComponentTypes { get; } = + [ + ComponentType.SelectMenu, + ComponentType.TextInput, + ]; + + /// + /// The maximum length of the label. + /// + public const int MaxLabelLength = 100; + + /// + /// The maximum length of the description. + /// + public const int MaxDescriptionLength = 69420; // TODO: set to the real limit + + /// + public ComponentType Type => ComponentType.Label; + + /// + public int? Id { get; set; } + + /// + /// + /// + public string Label { get; set; } + + /// + /// + /// + public string Description { get; set; } + + public IMessageComponentBuilder Component { get; set; } + + /// + /// Initializes a new . + /// + public LabelBuilder() { } + + /// + /// Initializes a new with the specified content. + /// + public LabelBuilder(string label, IMessageComponentBuilder component, string description = null, int? id = null) + { + Id = id; + Label = label; + Component = component; + Description = description; + } + + /// + /// Initializes a new from existing component. + /// + public LabelBuilder(LabelComponent label) + { + Label = label.Label; + Description = label.Description; + Id = label.Id; + Component = label.Component.ToBuilder(); + } + + public LabelComponent Build() + { + Preconditions.NotNullOrWhitespace(Label, nameof(Label)); + Preconditions.AtMost(Label.Length, MaxLabelLength, nameof(Label)); + + Preconditions.AtMost(Description?.Length ?? 0, MaxDescriptionLength, nameof(Description)); + + Preconditions.NotNull(Component, nameof(Component)); + + if (SupportedComponentTypes.All(x => Component.Type != x)) + throw new InvalidOperationException($"Component can only be {nameof(SelectMenuBuilder)} or {nameof(TextInputBuilder)}."); + + return new LabelComponent(Id, Label, Description, Component.Build()); + } + + /// + IMessageComponent IMessageComponentBuilder.Build() => 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 index a6fcb65b55..94e3cf8f73 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/MediaGalleryBuilder.cs @@ -18,7 +18,7 @@ public class MediaGalleryBuilder : IMessageComponentBuilder /// public int? Id { get; set; } - private List _items = new(); + private List _items = []; /// /// Initializes a new instance of the . diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs index 5d5cce5a77..1b57fd8be4 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs @@ -45,18 +45,44 @@ public enum ComponentType /// ChannelSelect = 8, + /// + /// A container to display text alongside an accessory component. + /// Section = 9, + /// + /// A component displaying Markdown text. + /// TextDisplay = 10, + /// + /// A small image that can be used as an accessory. + /// Thumbnail = 11, + /// + /// A component displaying images and other media. + /// MediaGallery = 12, + /// + /// A component displaying an attached file. + /// File = 13, + /// + /// A component to add vertical padding between other components. + /// Separator = 14, + /// + /// A container that visually groups a set of components. + /// Container = 17, + + /// + /// + /// + Label = 18, } } diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/LabelComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/LabelComponent.cs new file mode 100644 index 0000000000..1f8a8b2d49 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/LabelComponent.cs @@ -0,0 +1,35 @@ +namespace Discord; + +public class LabelComponent : IMessageComponent +{ + /// + public ComponentType Type => ComponentType.Label; + + /// + public int? Id { get; private set; } + + /// + /// + /// + public string Label { get; private set; } + + /// + /// + /// + public string Description { get; private set; } + + /// + /// + /// + public IMessageComponent Component { get; private set; } + + internal LabelComponent(int? id, string label, string description, IMessageComponent component) + { + Id = id; + Label = label; + Description = description; + Component = component; + } + + public IMessageComponentBuilder ToBuilder() => throw new System.NotImplementedException(); +} diff --git a/src/Discord.Net.Rest/API/Common/LabelComponent.cs b/src/Discord.Net.Rest/API/Common/LabelComponent.cs new file mode 100644 index 0000000000..f27875e23a --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/LabelComponent.cs @@ -0,0 +1,38 @@ +using Discord.Rest; +using Newtonsoft.Json; + +namespace Discord.API; + +internal class LabelComponent : IMessageComponent +{ + [JsonProperty("type")] + public ComponentType Type { get; set; } + + [JsonProperty("id")] + public Optional Id { get; } + + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("component")] + public IMessageComponent Component { get; set; } + + public LabelComponent() {} + + public LabelComponent(Discord.LabelComponent label) + { + Type = label.Type; + Id = label.Id ?? Optional.Unspecified; + Label = label.Label; + Description = label.Description; + Component = label.Component.ToModel(); + } + + public IMessageComponentBuilder ToBuilder() => null; + + [JsonIgnore] + int? IMessageComponent.Id => Id.ToNullable(); +} diff --git a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs index 96893f7e4b..9420f9a363 100644 --- a/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs +++ b/src/Discord.Net.Rest/Extensions/MessageComponentExtension.cs @@ -41,6 +41,9 @@ internal static IMessageComponent ToModel(this IMessageComponent component) case ContainerComponent container: return new API.ContainerComponent(container); + + case LabelComponent label: + return new API.LabelComponent(label); } return null; @@ -173,6 +176,12 @@ internal static IMessageComponent ToEntity(this IMessageComponent component) parsed.Id.ToNullable()); } + case ComponentType.Label: + { + var parsed = (API.LabelComponent)component; + return new LabelComponent(parsed.Id.ToNullable(), parsed.Label, parsed.Description, parsed.Component.ToEntity()); + } + default: return null; } diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs index 0f229f33ef..aed0a5ac39 100644 --- a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs @@ -61,6 +61,9 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist case ComponentType.Container: messageComponent = new API.ContainerComponent(); break; + case ComponentType.Label: + messageComponent = new API.LabelComponent(); + break; } serializer.Populate(jsonObject.CreateReader(), messageComponent); return messageComponent;