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 ad27476a..bf19c813 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ActionRowBuilder.cs @@ -2,6 +2,7 @@ using Discord.Utils; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -13,6 +14,18 @@ namespace Discord; [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class ActionRowBuilder : IMessageComponentBuilder, IInteractableComponentContainer { + /// + public ImmutableArray SupportedComponentTypes { get; } = + [ + ComponentType.Button, + ComponentType.SelectMenu, + ComponentType.UserSelect, + ComponentType.RoleSelect, + ComponentType.ChannelSelect, + ComponentType.MentionableSelect, + ComponentType.TextInput + ]; + /// public ComponentType Type => ComponentType.ActionRow; @@ -207,7 +220,10 @@ public class ActionRowBuilder : IMessageComponentBuilder, IInteractableComponent { Preconditions.AtLeast(Components.Count, 1, nameof(Components), "There must be at least 1 component in a row."); Preconditions.AtMost(Components.Count, MaxChildCount, nameof(Components), $"Action row can only contain {MaxChildCount} child components!"); - + + if (Components.Any(x => !SupportedComponentTypes.Contains(x.Type))) + throw new InvalidOperationException($"This component container only supports components of types: {string.Join(", ", SupportedComponentTypes)}"); + return new ActionRowComponent(_components.Select(x => x.Build()).ToList(), Id); } IMessageComponent IMessageComponentBuilder.Build() => Build(); @@ -242,6 +258,8 @@ public class ActionRowBuilder : IMessageComponentBuilder, IInteractableComponent /// IComponentContainer IComponentContainer.WithComponents(IEnumerable components) => WithComponents(components); + /// + int IComponentContainer.MaxChildCount => MaxChildCount; private string DebuggerDisplay => $"{nameof(ActionRowBuilder)}: {this.ComponentCount()} child components."; } 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 66824e3a..20d1ab45 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentBuilderV2.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -9,10 +10,22 @@ namespace Discord; [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class ComponentBuilderV2 : IStaticComponentContainer { + /// + public ImmutableArray SupportedComponentTypes { get; } = + [ + ComponentType.ActionRow, + ComponentType.Section, + ComponentType.MediaGallery, + ComponentType.Separator, + ComponentType.Container, + ComponentType.File, + ComponentType.TextDisplay + ]; + /// /// Gets the maximum number of components that can be added to a message. /// - public const int MaxComponents = 40; + public const int MaxChildCount = 40; private List _components = new(); @@ -74,21 +87,14 @@ public class ComponentBuilderV2 : IStaticComponentContainer { Preconditions.NotNull(Components, nameof(Components)); Preconditions.AtLeast(Components.Count, 1, nameof(Components.Count), "At least 1 component must be added to this container."); - Preconditions.AtMost(this.ComponentCount(), MaxComponents, nameof(Components.Count), $"A message must contain {MaxComponents} components or less."); + Preconditions.AtMost(this.ComponentCount(), MaxChildCount, nameof(Components.Count), $"A message must contain {MaxChildCount} components or less."); var ids = this.GetComponentIds().ToList(); if (ids.Count != ids.Distinct().Count()) throw new InvalidOperationException("Components must have unique ids."); - 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."); + if (Components.Any(x => !SupportedComponentTypes.Contains(x.Type))) + throw new InvalidOperationException($"This component container only supports components of types: {string.Join(", ", SupportedComponentTypes)}"); return new MessageComponent(Components.Select(x => x.Build()).ToList()); } @@ -101,6 +107,8 @@ public class ComponentBuilderV2 : IStaticComponentContainer /// IComponentContainer IComponentContainer.WithComponents(IEnumerable components) => WithComponents(components); + /// + int IComponentContainer.MaxChildCount => MaxChildCount; private string DebuggerDisplay => $"{nameof(ComponentBuilderV2)}: {this.ComponentCount()} child 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 a7c5c58f..5d57d396 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ComponentContainerExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -52,6 +53,21 @@ public static class ComponentContainerExtensions .WithContent(content) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithTextDisplay(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new TextDisplayBuilder(); + options(comp); + return container.WithTextDisplay(comp); + } + /// /// Adds a to the container. /// @@ -65,6 +81,21 @@ public static class ComponentContainerExtensions return container; } + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithSection(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new SectionBuilder(); + options(comp); + return container.WithSection(comp); + } + /// /// Adds a to the container. /// @@ -122,6 +153,21 @@ public static class ComponentContainerExtensions .WithItems(urls.Select(x => new MediaGalleryItemProperties(new UnfurledMediaItemProperties(x)))) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithMediaGallery(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new MediaGalleryBuilder(); + options(comp); + return container.WithMediaGallery(comp); + } + /// /// Adds a to the container. /// @@ -151,6 +197,21 @@ public static class ComponentContainerExtensions .WithIsDivider(isDivider) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithSeparator(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new SeparatorBuilder(); + options(comp); + return container.WithSeparator(comp); + } + /// /// Adds a to the container. /// @@ -180,6 +241,21 @@ public static class ComponentContainerExtensions .WithIsSpoiler(isSpoiler) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithFile(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new FileComponentBuilder(); + options(comp); + return container.WithFile(comp); + } + /// /// Adds a to the container. /// @@ -223,6 +299,21 @@ public static class ComponentContainerExtensions => container.WithContainer(new ContainerBuilder() .WithComponents(components)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithContainer(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var comp = new ContainerBuilder(); + options(comp); + return container.WithContainer(comp); + } + /// /// Adds a to the container. /// @@ -262,6 +353,21 @@ public static class ComponentContainerExtensions .WithSkuId(skuId) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithButton(this BuilderT container, + Action options) + where BuilderT : class, IInteractableComponentContainer + { + var comp = new ButtonBuilder(); + options(comp); + return container.WithButton(comp); + } + /// /// Adds a to the container. /// @@ -306,6 +412,21 @@ public static class ComponentContainerExtensions .WithDefaultValues(defaultValues) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithSelectMenu(this BuilderT container, + Action options) + where BuilderT : class, IInteractableComponentContainer + { + var comp = new SelectMenuBuilder(); + options(comp); + return container.WithSelectMenu(comp); + } + /// /// Adds a to the container. /// @@ -333,6 +454,21 @@ public static class ComponentContainerExtensions .WithComponents(components) .WithId(id)); + /// + /// Adds a to the container. + /// + /// + /// The current container. + /// + public static BuilderT WithActionRow(this BuilderT container, + Action options) + where BuilderT : class, IStaticComponentContainer + { + var cont = new ActionRowBuilder(); + options(cont); + return container.WithActionRow(cont); + } + /// /// Finds the first in the /// or any of its child s with matching id. 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 645316bb..c826966e 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/ContainerBuilder.cs @@ -9,9 +9,25 @@ namespace Discord; [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContainer { + /// + public ImmutableArray SupportedComponentTypes { get; } = + [ + ComponentType.ActionRow, + ComponentType.Section, + ComponentType.Button, + ComponentType.MediaGallery, + ComponentType.Separator, + ComponentType.File, + ComponentType.SelectMenu, + ComponentType.TextDisplay + ]; + /// public ComponentType Type => ComponentType.Container; + /// + public const int MaxChildCount = 39; + /// public int? Id { get; set; } @@ -46,7 +62,7 @@ public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContai { Components = components?.ToList(); } - + /// /// Initializes a new from existing component. /// @@ -107,14 +123,11 @@ public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContai /// public ContainerComponent Build() { - 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."); + Preconditions.NotNull(Components, nameof(Components)); + Preconditions.AtLeast(Components.Count, 1, nameof(Components.Count), "At least 1 component must be added to this container."); + + if (Components.Any(x => !SupportedComponentTypes.Contains(x.Type))) + throw new InvalidOperationException($"This component container only supports components of types: {string.Join(", ", SupportedComponentTypes)}"); return new(Components.ConvertAll(x => x.Build()).ToImmutableArray(), AccentColor, IsSpoiler, Id); } @@ -127,6 +140,8 @@ public class ContainerBuilder : IMessageComponentBuilder, IStaticComponentContai IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); /// IComponentContainer IComponentContainer.WithComponents(IEnumerable components) => WithComponents(components); + /// + int IComponentContainer.MaxChildCount => MaxChildCount; private string DebuggerDisplay => $"{nameof(ContainerBuilder)}: {this.ComponentCount()} child components."; } 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 2c2734fb..58f0b080 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/IComponentContainer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; namespace Discord; @@ -7,6 +8,16 @@ namespace Discord; /// public interface IComponentContainer { + /// + /// Gets the types of child components supported by this container. + /// + ImmutableArray SupportedComponentTypes { get; } + + /// + /// Gets the maximum number of components allowed in the container. + /// + int MaxChildCount { get; } + /// /// Gets the components in the container. /// 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 1cd799ff..14df1877 100644 --- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/Builders/SectionBuilder.cs @@ -10,10 +10,14 @@ namespace Discord; [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContainer { - /// - /// Gets the maximum number of components allowed in this container. - /// - public const int MaxComponents = 3; + /// + public ImmutableArray SupportedComponentTypes { get; } = + [ + ComponentType.TextDisplay + ]; + + /// + public const int MaxChildCount = 3; /// public ComponentType Type => ComponentType.Section; @@ -45,7 +49,7 @@ public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContaine /// Initializes a new . /// public SectionBuilder() { } - + /// /// Initializes a new . /// @@ -108,17 +112,17 @@ public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContaine /// public SectionComponent Build() { - if (_components.Count is 0 or > MaxComponents) - throw new InvalidOperationException($"Section component can only contain {MaxComponents} child components!"); + if (_components.Count is 0 or > MaxChildCount) + throw new InvalidOperationException($"Section component can only contain {MaxChildCount} child components."); - if (_components.Any(x => x is not TextDisplayBuilder)) - throw new InvalidOperationException($"Section component can only contain {nameof(TextDisplayBuilder)}!"); + if (Components.Any(x => !SupportedComponentTypes.Contains(x.Type))) + throw new InvalidOperationException($"This component container only supports components of types: {string.Join(", ", SupportedComponentTypes)}"); if (Accessory is null) - throw new ArgumentNullException(nameof(Accessory), "A section must have an accessory"); + 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)}!"); + throw new InvalidOperationException($"Accessory component can only be {nameof(ButtonBuilder)} or {nameof(ThumbnailBuilder)}."); return new(Id, Components.Select(x => x.Build()).ToImmutableArray(), Accessory?.Build()); } @@ -131,6 +135,7 @@ public class SectionBuilder : IMessageComponentBuilder, IStaticComponentContaine IComponentContainer IComponentContainer.AddComponents(params IMessageComponentBuilder[] components) => AddComponents(components); /// IComponentContainer IComponentContainer.WithComponents(IEnumerable components) => WithComponents(components.ToList()); - + /// + int IComponentContainer.MaxChildCount => MaxChildCount; private string DebuggerDisplay => $"{nameof(SectionBuilder)}: {this.ComponentCount()} child components."; }