diff --git a/src/Discord.Net.Interactions/Attributes/HideAttribute.cs b/src/Discord.Net.Interactions/Attributes/HideAttribute.cs
new file mode 100644
index 0000000000..9e6f9a2835
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/HideAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Discord.Interactions;
+
+///
+/// Enum values tagged with this attribute will not be displayed as a parameter choice
+///
+///
+/// This attribute must be used along with the default
+///
+[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
+public sealed class HideAttribute : Attribute { }
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalChannelSelectInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalChannelSelectInputAttribute.cs
new file mode 100644
index 0000000000..263518e04c
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalChannelSelectInputAttribute.cs
@@ -0,0 +1,10 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public class ModalChannelSelectInputAttribute : SelectInputAttribute
+{
+ public override ComponentType ComponentType => ComponentType.ChannelSelect;
+
+ public ModalChannelSelectInputAttribute(string customId) : base(customId)
+ {
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalMentionableSelectInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalMentionableSelectInputAttribute.cs
new file mode 100644
index 0000000000..f3088b53a6
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalMentionableSelectInputAttribute.cs
@@ -0,0 +1,10 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public class ModalMentionableSelectInputAttribute : SelectInputAttribute
+{
+ public override ComponentType ComponentType => ComponentType.MentionableSelect;
+
+ public ModalMentionableSelectInputAttribute(string customId) : base(customId)
+ {
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalRoleSelectInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalRoleSelectInputAttribute.cs
new file mode 100644
index 0000000000..18e611b1f7
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalRoleSelectInputAttribute.cs
@@ -0,0 +1,10 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public class ModalRoleSelectInputAttribute : SelectInputAttribute
+{
+ public override ComponentType ComponentType => ComponentType.RoleSelect;
+
+ public ModalRoleSelectInputAttribute(string customId) : base(customId)
+ {
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalSelectMenuInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalSelectMenuInputAttribute.cs
new file mode 100644
index 0000000000..d5a17799b1
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalSelectMenuInputAttribute.cs
@@ -0,0 +1,11 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public sealed class ModalSelectMenuInputAttribute : SelectInputAttribute
+{
+ public override ComponentType ComponentType => ComponentType.SelectMenu;
+
+ public ModalSelectMenuInputAttribute(string customId) : base(customId)
+ {
+
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/ModalUserSelectInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/ModalUserSelectInputAttribute.cs
new file mode 100644
index 0000000000..3299524cfc
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/ModalUserSelectInputAttribute.cs
@@ -0,0 +1,10 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public class ModalUserSelectInputAttribute : SelectInputAttribute
+{
+ public override ComponentType ComponentType => ComponentType.UserSelect;
+
+ public ModalUserSelectInputAttribute(string customId) : base(customId)
+ {
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Modals/SelectInputAttribute.cs b/src/Discord.Net.Interactions/Attributes/Modals/SelectInputAttribute.cs
new file mode 100644
index 0000000000..7106125eb5
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Modals/SelectInputAttribute.cs
@@ -0,0 +1,14 @@
+namespace Discord.Interactions.Attributes.Modals;
+
+public abstract class SelectInputAttribute : ModalInputAttribute
+{
+ public int MinValues { get; set; } = 1;
+
+ public int MaxValues { get; set; } = 1;
+
+ public string Placeholder { get; set; }
+
+ public SelectInputAttribute(string customId) : base(customId)
+ {
+ }
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/ChannelSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/ChannelSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..3a5c52426d
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/ChannelSelectInputComponentBuilder.cs
@@ -0,0 +1,39 @@
+using Discord.Interactions.Info.InputComponents;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public class ChannelSelectInputComponentBuilder : SnowflakeSelectInputComponentBuilder
+{
+ protected override ChannelSelectInputComponentBuilder Instance => this;
+
+ public ChannelSelectInputComponentBuilder(ModalBuilder modal) : base(modal, ComponentType.ChannelSelect) { }
+
+ public ChannelSelectInputComponentBuilder AddDefaulValue(IChannel channel)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(channel.Id, SelectDefaultValueType.Channel));
+ return this;
+ }
+
+ public ChannelSelectInputComponentBuilder AddDefaulValue(ulong channelId)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(channelId, SelectDefaultValueType.Channel));
+ return this;
+ }
+
+ public ChannelSelectInputComponentBuilder AddDefaultValues(params IChannel[] channels)
+ {
+ _defaultValues.AddRange(channels.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.Channel)));
+ return this;
+ }
+
+ public ChannelSelectInputComponentBuilder AddDefaultValues(IEnumerable channels)
+ {
+ _defaultValues.AddRange(channels.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.Channel)));
+ return this;
+ }
+
+ internal override ChannelSelectInputComponentInfo Build(ModalInfo modal)
+ => new(this, modal);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
index 68c26fd037..e46d4ca1b3 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
@@ -1,3 +1,4 @@
+using Discord.Interactions.TypeConverters.ModalInputs;
using System;
using System.Collections.Generic;
using System.Reflection;
@@ -45,12 +46,12 @@ public interface IInputComponentBuilder
PropertyInfo PropertyInfo { get; }
///
- /// Get the assigned to this input.
+ /// Get the assigned to this input.
///
- ComponentTypeConverter TypeConverter { get; }
+ ModalComponentTypeConverter TypeConverter { get; }
///
- /// Gets the default value of this input component.
+ /// Gets the default value of this input component property.
///
object DefaultValue { get; }
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/ISnowflakeSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/ISnowflakeSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..817415dc93
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/ISnowflakeSelectInputComponentBuilder.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public interface ISnowflakeSelectInputComponentBuilder : IInputComponentBuilder
+{
+ int MinValues { get; }
+
+ int MaxValues { get; }
+
+ string Placeholder { get; set; }
+
+ IReadOnlyCollection DefaultValues { get; }
+
+ SelectDefaultValueType? DefaultValuesType { get; }
+
+ ISnowflakeSelectInputComponentBuilder AddDefaultValue(SelectMenuDefaultValue defaultValue);
+
+ ISnowflakeSelectInputComponentBuilder WithMinValues(int minValues);
+
+ ISnowflakeSelectInputComponentBuilder WithMaxValues(int maxValues);
+
+ ISnowflakeSelectInputComponentBuilder WithPlaceholder(string placeholder);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
index af0ab3a70e..8da3d07177 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
@@ -1,3 +1,4 @@
+using Discord.Interactions.TypeConverters.ModalInputs;
using System;
using System.Collections.Generic;
using System.Reflection;
@@ -38,7 +39,7 @@ public abstract class InputComponentBuilder : IInputComponentBu
public PropertyInfo PropertyInfo { get; internal set; }
///
- public ComponentTypeConverter TypeConverter { get; private set; }
+ public ModalComponentTypeConverter TypeConverter { get; private set; }
///
public object DefaultValue { get; set; }
@@ -102,7 +103,7 @@ public TBuilder SetIsRequired(bool isRequired)
///
/// The builder instance.
///
- public TBuilder WithComponentType(ComponentType componentType)
+ public virtual TBuilder WithComponentType(ComponentType componentType)
{
ComponentType = componentType;
return Instance;
@@ -118,7 +119,7 @@ public TBuilder WithComponentType(ComponentType componentType)
public TBuilder WithType(Type type)
{
Type = type;
- TypeConverter = Modal._interactionService.GetComponentTypeConverter(type);
+ TypeConverter = Modal._interactionService.GetModalInputTypeConverter(type);
return Instance;
}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/MentionableSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/MentionableSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..2122cef92b
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/MentionableSelectInputComponentBuilder.cs
@@ -0,0 +1,37 @@
+using Discord.Interactions.Info.InputComponents;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public class MentionableSelectInputComponentBuilder : SnowflakeSelectInputComponentBuilder
+{
+ protected override MentionableSelectInputComponentBuilder Instance => this;
+
+ public MentionableSelectInputComponentBuilder(ModalBuilder modal) : base(modal, ComponentType.MentionableSelect) { }
+
+ public MentionableSelectInputComponentBuilder AddDefaultValue(ulong id, SelectDefaultValueType type)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(id, type));
+ return this;
+ }
+
+ public MentionableSelectInputComponentBuilder AddDefaultValue(IUser user)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(user.Id, SelectDefaultValueType.User));
+ return this;
+ }
+
+ public MentionableSelectInputComponentBuilder AddDefaultValue(IChannel channel)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(channel.Id, SelectDefaultValueType.Channel));
+ return this;
+ }
+
+ public MentionableSelectInputComponentBuilder AddDefaulValue(IRole role)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(role.Id, SelectDefaultValueType.Role));
+ return this;
+ }
+
+ internal override MentionableSelectInputComponentInfo Build(ModalInfo modal)
+ => new(this, modal);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/RoleSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/RoleSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..e566ea3e6f
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/RoleSelectInputComponentBuilder.cs
@@ -0,0 +1,39 @@
+using Discord.Interactions.Info.InputComponents;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public class RoleSelectInputComponentBuilder : SnowflakeSelectInputComponentBuilder
+{
+ protected override RoleSelectInputComponentBuilder Instance => this;
+
+ public RoleSelectInputComponentBuilder(ModalBuilder modal) : base(modal, ComponentType.RoleSelect) { }
+
+ public RoleSelectInputComponentBuilder AddDefaulValue(IRole role)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(role.Id, SelectDefaultValueType.Role));
+ return this;
+ }
+
+ public RoleSelectInputComponentBuilder AddDefaulValue(ulong roleId)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(roleId, SelectDefaultValueType.Role));
+ return this;
+ }
+
+ public RoleSelectInputComponentBuilder AddDefaultValues(params IRole[] roles)
+ {
+ _defaultValues.AddRange(roles.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.Role)));
+ return this;
+ }
+
+ public RoleSelectInputComponentBuilder AddDefaultValues(IEnumerable roles)
+ {
+ _defaultValues.AddRange(roles.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.Role)));
+ return this;
+ }
+
+ internal override RoleSelectInputComponentInfo Build(ModalInfo modal)
+ => new(this, modal);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/SelectMenuInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/SelectMenuInputComponentBuilder.cs
new file mode 100644
index 0000000000..c75850b0fc
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/SelectMenuInputComponentBuilder.cs
@@ -0,0 +1,42 @@
+using Discord.Interactions.Info.InputComponents;
+using System;
+using System.Collections.Generic;
+
+namespace Discord.Interactions.Builders;
+
+public class SelectMenuInputComponentBuilder : InputComponentBuilder
+{
+ private readonly List _options;
+
+ protected override SelectMenuInputComponentBuilder Instance => this;
+
+ public string Placeholder { get; set; }
+
+ public int MinValues { get; set; }
+
+ public int MaxValues { get; set; }
+
+ public IReadOnlyCollection Options => _options;
+
+ public SelectMenuInputComponentBuilder(ModalBuilder modal) : base(modal)
+ {
+ _options = new();
+ }
+
+ public SelectMenuInputComponentBuilder AddOption(SelectMenuOptionBuilder option)
+ {
+ _options.Add(option);
+ return this;
+ }
+
+ public SelectMenuInputComponentBuilder AddOption(Action configure)
+ {
+ var builder = new SelectMenuOptionBuilder();
+ configure(builder);
+ _options.Add(builder);
+ return this;
+ }
+
+ internal override SelectMenuInputComponentInfo Build(ModalInfo modal)
+ => new(this, modal);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/SnowflakeSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/SnowflakeSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..e7c508f510
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/SnowflakeSelectInputComponentBuilder.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public abstract class SnowflakeSelectInputComponentBuilder : InputComponentBuilder, ISnowflakeSelectInputComponentBuilder
+ where TInfo : InputComponentInfo
+ where TBuilder : InputComponentBuilder, ISnowflakeSelectInputComponentBuilder
+{
+ protected readonly List _defaultValues;
+
+ public int MinValues { get; set; } = 1;
+
+ public int MaxValues { get; set; } = 1;
+
+ public string Placeholder { get; set; }
+
+ public IReadOnlyCollection DefaultValues => _defaultValues.AsReadOnly();
+
+ public SelectDefaultValueType? DefaultValuesType
+ {
+ get
+ {
+ return ComponentType switch
+ {
+ ComponentType.UserSelect => SelectDefaultValueType.User,
+ ComponentType.RoleSelect => SelectDefaultValueType.Role,
+ ComponentType.ChannelSelect => SelectDefaultValueType.Channel,
+ ComponentType.MentionableSelect => null,
+ _ => throw new InvalidOperationException("Component type must be a snowflake select type."),
+ };
+ }
+ }
+
+ public SnowflakeSelectInputComponentBuilder(ModalBuilder modal, ComponentType componentType) : base(modal)
+ {
+ ValidateComponentType(componentType);
+
+ ComponentType = componentType;
+ _defaultValues = new();
+ }
+
+ public TBuilder AddDefaultValue(SelectMenuDefaultValue defaultValue)
+ {
+ if (DefaultValuesType.HasValue && defaultValue.Type != DefaultValuesType.Value)
+ throw new ArgumentException($"Only default values with {Enum.GetName(typeof(SelectDefaultValueType), DefaultValuesType.Value)} are support by {nameof(TInfo)} select type.", nameof(defaultValue));
+
+ _defaultValues.Add(defaultValue);
+ return Instance;
+ }
+
+ public override TBuilder WithComponentType(ComponentType componentType)
+ {
+ ValidateComponentType(componentType);
+ return base.WithComponentType(componentType);
+ }
+
+ public TBuilder WithMinValues(int minValues)
+ {
+ MinValues = minValues;
+ return Instance;
+ }
+
+ public TBuilder WithMaxValues(int maxValues)
+ {
+ MaxValues = maxValues;
+ return Instance;
+ }
+
+ public TBuilder WithPlaceholder(string placeholder)
+ {
+ Placeholder = placeholder;
+ return Instance;
+ }
+
+ private void ValidateComponentType(ComponentType componentType)
+ {
+ if (componentType is not ComponentType.UserSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.ChannelSelect)
+ throw new ArgumentException("Component type must be a snowflake select type.", nameof(componentType));
+
+ }
+
+ ISnowflakeSelectInputComponentBuilder ISnowflakeSelectInputComponentBuilder.AddDefaultValue(SelectMenuDefaultValue defaultValue) => AddDefaultValue(defaultValue);
+
+ ISnowflakeSelectInputComponentBuilder ISnowflakeSelectInputComponentBuilder.WithMinValues(int minValues) => WithMinValues(minValues);
+
+ ISnowflakeSelectInputComponentBuilder ISnowflakeSelectInputComponentBuilder.WithMaxValues(int maxValues) => WithMaxValues(maxValues);
+
+ ISnowflakeSelectInputComponentBuilder ISnowflakeSelectInputComponentBuilder.WithPlaceholder(string placeholder) => WithPlaceholder(placeholder);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/UserSelectInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/UserSelectInputComponentBuilder.cs
new file mode 100644
index 0000000000..87180dbe53
--- /dev/null
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/UserSelectInputComponentBuilder.cs
@@ -0,0 +1,39 @@
+using Discord.Interactions.Info.InputComponents;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Discord.Interactions.Builders.Modals.Inputs;
+
+public class UserSelectInputComponentBuilder : SnowflakeSelectInputComponentBuilder
+{
+ protected override UserSelectInputComponentBuilder Instance => this;
+
+ public UserSelectInputComponentBuilder(ModalBuilder modal) : base(modal, ComponentType.UserSelect) { }
+
+ public UserSelectInputComponentBuilder AddDefaulValue(IUser user)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(user.Id, SelectDefaultValueType.User));
+ return this;
+ }
+
+ public UserSelectInputComponentBuilder AddDefaulValue(ulong userId)
+ {
+ _defaultValues.Add(new SelectMenuDefaultValue(userId, SelectDefaultValueType.User));
+ return this;
+ }
+
+ public UserSelectInputComponentBuilder AddDefaultValues(params IUser[] users)
+ {
+ _defaultValues.AddRange(users.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.User)));
+ return this;
+ }
+
+ public UserSelectInputComponentBuilder AddDefaultValues(IEnumerable users)
+ {
+ _defaultValues.AddRange(users.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.User)));
+ return this;
+ }
+
+ internal override UserSelectInputComponentInfo Build(ModalInfo modal)
+ => new(this, modal);
+}
diff --git a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
index 66aeadf75b..1d90134915 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
@@ -1,3 +1,4 @@
+using Discord.Interactions.Builders.Modals.Inputs;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -80,6 +81,53 @@ public ModalBuilder AddTextComponent(Action configure
return this;
}
+ ///
+ /// Adds a select menu component to .
+ ///
+ /// Select menu component builder factory.
+ ///
+ /// The builder instance.
+ ///
+ public ModalBuilder AddSelectMenuComponent(Action configure)
+ {
+ var builder = new SelectMenuInputComponentBuilder(this);
+ configure(builder);
+ _components.Add(builder);
+ return this;
+ }
+
+ public ModalBuilder AddUserSelectComponent(Action configure)
+ {
+ var builder = new UserSelectInputComponentBuilder(this);
+ configure(builder);
+ _components.Add(builder);
+ return this;
+ }
+
+ public ModalBuilder AddRoleSelectComponent(Action configure)
+ {
+ var builder = new RoleSelectInputComponentBuilder(this);
+ configure(builder);
+ _components.Add(builder);
+ return this;
+ }
+
+ public ModalBuilder AddMentionableSelectComponent(Action configure)
+ {
+ var builder = new MentionableSelectInputComponentBuilder(this);
+ configure(builder);
+ _components.Add(builder);
+ return this;
+ }
+
+ public ModalBuilder AddChannelSelectComponent(Action configure)
+ {
+ var builder = new ChannelSelectInputComponentBuilder(this);
+ configure(builder);
+ _components.Add(builder);
+ return this;
+ }
+
internal ModalInfo Build() => new(this);
}
}
diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
index 87aefa8f24..2c541ad1be 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
@@ -1,3 +1,6 @@
+using Discord.Interactions.Attributes.Modals;
+using Discord.Interactions.Builders.Modals.Inputs;
+using Discord.Interactions.Info.InputComponents;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -615,6 +618,21 @@ public static ModalInfo BuildModalInfo(Type modalType, InteractionService intera
case ComponentType.TextInput:
builder.AddTextComponent(x => BuildTextInput(x, prop, prop.GetValue(instance)));
break;
+ case ComponentType.SelectMenu:
+ builder.AddSelectMenuComponent(x => BuildSelectMenuInput(x, prop, prop.GetValue(instance)));
+ break;
+ case ComponentType.UserSelect:
+ builder.AddUserSelectComponent(x => BuildSnowflakeSelectInput(x, prop, prop.GetValue(instance)));
+ break;
+ case ComponentType.RoleSelect:
+ builder.AddRoleSelectComponent(x => BuildSnowflakeSelectInput(x, prop, prop.GetValue(instance)));
+ break;
+ case ComponentType.MentionableSelect:
+ builder.AddMentionableSelectComponent(x => BuildSnowflakeSelectInput(x, prop, prop.GetValue(instance)));
+ break;
+ case ComponentType.ChannelSelect:
+ builder.AddChannelSelectComponent(x => BuildSnowflakeSelectInput(x, prop, prop.GetValue(instance)));
+ break;
case null:
throw new InvalidOperationException($"{prop.Name} of {prop.DeclaringType.Name} isn't a valid modal input field.");
default:
@@ -666,6 +684,74 @@ private static void BuildTextInput(TextInputComponentBuilder builder, PropertyIn
}
}
}
+
+ private static void BuildSelectMenuInput(SelectMenuInputComponentBuilder builder, PropertyInfo propertyInfo, object defaultValue)
+ {
+ var attributes = propertyInfo.GetCustomAttributes();
+
+ builder.Label = propertyInfo.Name;
+ builder.DefaultValue = defaultValue;
+ builder.WithType(propertyInfo.PropertyType);
+ builder.PropertyInfo = propertyInfo;
+
+ foreach (var attribute in attributes)
+ {
+ switch (attribute)
+ {
+ case ModalSelectMenuInputAttribute selectMenuInput:
+ builder.CustomId = selectMenuInput.CustomId;
+ builder.ComponentType = selectMenuInput.ComponentType;
+ builder.MinValues = selectMenuInput.MinValues;
+ builder.MaxValues = selectMenuInput.MaxValues;
+ builder.Placeholder = selectMenuInput.Placeholder;
+ break;
+ case RequiredInputAttribute requiredInput:
+ builder.IsRequired = requiredInput.IsRequired;
+ break;
+ case InputLabelAttribute inputLabel:
+ builder.Label = inputLabel.Label;
+ break;
+ default:
+ builder.WithAttributes(attribute);
+ break;
+ }
+ }
+ }
+
+ private static void BuildSnowflakeSelectInput(SnowflakeSelectInputComponentBuilder builder, PropertyInfo propertyInfo, object defaultValue)
+ where TInfo: SnowflakeSelectInputComponentInfo
+ where TBuilder: SnowflakeSelectInputComponentBuilder
+ {
+ var attributes = propertyInfo.GetCustomAttributes();
+
+ builder.Label = propertyInfo.Name;
+ builder.DefaultValue = defaultValue;
+ builder.WithType(propertyInfo.PropertyType);
+ builder.PropertyInfo = propertyInfo;
+
+ foreach (var attribute in attributes)
+ {
+ switch (attribute)
+ {
+ case SelectInputAttribute selectInput:
+ builder.CustomId = selectInput.CustomId;
+ builder.ComponentType = selectInput.ComponentType;
+ builder.MinValues = selectInput.MinValues;
+ builder.MaxValues = selectInput.MaxValues;
+ builder.Placeholder = selectInput.Placeholder;
+ break;
+ case RequiredInputAttribute requiredInput:
+ builder.IsRequired = requiredInput.IsRequired;
+ break;
+ case InputLabelAttribute inputLabel:
+ builder.Label = inputLabel.Label;
+ break;
+ default:
+ builder.WithAttributes(attribute);
+ break;
+ }
+ }
+ }
#endregion
internal static bool IsValidModuleDefinition(TypeInfo typeInfo)
diff --git a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
index 85f53af3f5..a2e9ca669c 100644
--- a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
+++ b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs
@@ -1,4 +1,6 @@
+using Discord.Interactions.Info.InputComponents;
using System;
+using System.Linq;
using System.Threading.Tasks;
namespace Discord.Interactions
@@ -54,7 +56,7 @@ public static Task RespondWithModalAsync(this IDiscordInteraction interaction
/// The request options for this request.
/// Delegate that can be used to modify the modal.
///
- public static Task RespondWithModalAsync(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null,
+ public static async Task RespondWithModalAsync(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null,
Action modifyModal = null)
where T : class, IModal
{
@@ -68,13 +70,30 @@ public static Task RespondWithModalAsync(this IDiscordInteraction interaction
{
case TextInputComponentInfo textComponent:
{
- var boxedValue = textComponent.Getter(modal);
- var value = textComponent.TypeOverridesToString
- ? boxedValue?.ToString()
- : boxedValue as string;
+ var inputBuilder = new TextInputBuilder(textComponent.Label, textComponent.CustomId, textComponent.Style, textComponent.Placeholder, textComponent.IsRequired ? textComponent.MinLength : null,
+ textComponent.MaxLength, textComponent.IsRequired);
- builder.AddTextInput(textComponent.Label, textComponent.CustomId, textComponent.Style, textComponent.Placeholder, textComponent.IsRequired ? textComponent.MinLength : null,
- textComponent.MaxLength, textComponent.IsRequired, value);
+ await textComponent.TypeConverter.WriteAsync(inputBuilder, textComponent, textComponent.Getter(modal));
+
+ builder.AddTextInput(inputBuilder);
+ }
+ break;
+ case SelectMenuInputComponentInfo selectMenuComponent:
+ {
+ var inputBuilder = new SelectMenuBuilder(selectMenuComponent.CustomId, selectMenuComponent.Options.Select(x => new SelectMenuOptionBuilder(x)).ToList(), selectMenuComponent.Placeholder, selectMenuComponent.MaxValues, selectMenuComponent.MinValues, false);
+
+ await selectMenuComponent.TypeConverter.WriteAsync(inputBuilder, selectMenuComponent, selectMenuComponent.Getter(modal));
+
+ //todo: add to builder
+ }
+ break;
+ case SnowflakeSelectInputComponentInfo snowflakeSelectComponent:
+ {
+ var inputBuilder = new SelectMenuBuilder(snowflakeSelectComponent.CustomId, null, snowflakeSelectComponent.Placeholder, snowflakeSelectComponent.MaxValues, snowflakeSelectComponent.MinValues, false, snowflakeSelectComponent.ComponentType, null, snowflakeSelectComponent.DefaultValues.ToList());
+
+ await snowflakeSelectComponent.TypeConverter.WriteAsync(inputBuilder, snowflakeSelectComponent, snowflakeSelectComponent.Getter(modal));
+
+ //todo: add to builder
}
break;
default:
@@ -84,7 +103,7 @@ public static Task RespondWithModalAsync(this IDiscordInteraction interaction
if (modifyModal is not null)
modifyModal(builder);
- return interaction.RespondWithModalAsync(builder.Build(), options);
+ await interaction.RespondWithModalAsync(builder.Build(), options);
}
private static Task SendModalResponseAsync(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, RequestOptions options = null, Action modifyModal = null)
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/ChannelSelectInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/ChannelSelectInputComponentInfo.cs
new file mode 100644
index 0000000000..032a85f46f
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/ChannelSelectInputComponentInfo.cs
@@ -0,0 +1,8 @@
+using Discord.Interactions.Builders.Modals.Inputs;
+
+namespace Discord.Interactions.Info.InputComponents;
+
+public class ChannelSelectInputComponentInfo : SnowflakeSelectInputComponentInfo
+{
+ public ChannelSelectInputComponentInfo(ChannelSelectInputComponentBuilder builder, ModalInfo modal) : base(builder, modal) { }
+}
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
index 23a0db8447..dc7fa02e08 100644
--- a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
+++ b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
@@ -1,3 +1,4 @@
+using Discord.Interactions.TypeConverters.ModalInputs;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -50,12 +51,12 @@ public abstract class InputComponentInfo
public PropertyInfo PropertyInfo { get; }
///
- /// Gets the assigned to this component.
+ /// Gets the assigned to this component.
///
- public ComponentTypeConverter TypeConverter { get; }
+ public ModalComponentTypeConverter TypeConverter { get; }
///
- /// Gets the default value of this component.
+ /// Gets the default value of this component property.
///
public object DefaultValue { get; }
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/MentionableSelectInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/MentionableSelectInputComponentInfo.cs
new file mode 100644
index 0000000000..3924384b73
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/MentionableSelectInputComponentInfo.cs
@@ -0,0 +1,8 @@
+using Discord.Interactions.Builders.Modals.Inputs;
+
+namespace Discord.Interactions.Info.InputComponents;
+
+public class MentionableSelectInputComponentInfo : SnowflakeSelectInputComponentInfo
+{
+ public MentionableSelectInputComponentInfo(MentionableSelectInputComponentBuilder builder, ModalInfo modal) : base(builder, modal) { }
+}
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/RoleSelectInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/RoleSelectInputComponentInfo.cs
new file mode 100644
index 0000000000..676d3ee01b
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/RoleSelectInputComponentInfo.cs
@@ -0,0 +1,8 @@
+using Discord.Interactions.Builders.Modals.Inputs;
+
+namespace Discord.Interactions.Info.InputComponents;
+
+public class RoleSelectInputComponentInfo : SnowflakeSelectInputComponentInfo
+{
+ public RoleSelectInputComponentInfo(RoleSelectInputComponentBuilder builder, ModalInfo modal) : base(builder, modal) { }
+}
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/SelectMenuInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/SelectMenuInputComponentInfo.cs
new file mode 100644
index 0000000000..12bacf3ac2
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/SelectMenuInputComponentInfo.cs
@@ -0,0 +1,24 @@
+using Discord.Interactions.Builders;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Discord.Interactions.Info.InputComponents;
+public class SelectMenuInputComponentInfo : InputComponentInfo
+{
+ public string Placeholder { get; set; }
+
+ public int MinValues { get; set; }
+
+ public int MaxValues { get; set; }
+
+ public IReadOnlyCollection Options { get; }
+
+ internal SelectMenuInputComponentInfo(SelectMenuInputComponentBuilder builder, ModalInfo modal) : base(builder, modal)
+ {
+ Placeholder = builder.Placeholder;
+ MinValues = builder.MinValues;
+ MaxValues = builder.MaxValues;
+ Options = builder.Options.Select(x => x.Build()).ToImmutableArray();
+ }
+}
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/SnowflakeSelectInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/SnowflakeSelectInputComponentInfo.cs
new file mode 100644
index 0000000000..175e013f34
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/SnowflakeSelectInputComponentInfo.cs
@@ -0,0 +1,27 @@
+using Discord.Interactions.Builders.Modals.Inputs;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace Discord.Interactions.Info.InputComponents;
+
+public abstract class SnowflakeSelectInputComponentInfo : InputComponentInfo
+{
+ public int MinValues { get; }
+
+ public int MaxValues { get; }
+
+ public string Placeholder { get; }
+
+ public IReadOnlyCollection DefaultValues { get; }
+
+ public SelectDefaultValueType? DefaultValueType { get; }
+
+ internal SnowflakeSelectInputComponentInfo(ISnowflakeSelectInputComponentBuilder builder, ModalInfo modal) : base(builder, modal)
+ {
+ MinValues = builder.MinValues;
+ MaxValues = builder.MaxValues;
+ Placeholder = builder.Placeholder;
+ DefaultValues = builder.DefaultValues.ToImmutableArray();
+ DefaultValueType = builder.DefaultValuesType;
+ }
+}
diff --git a/src/Discord.Net.Interactions/Info/InputComponents/UserSelectInputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/UserSelectInputComponentInfo.cs
new file mode 100644
index 0000000000..dd189084bb
--- /dev/null
+++ b/src/Discord.Net.Interactions/Info/InputComponents/UserSelectInputComponentInfo.cs
@@ -0,0 +1,8 @@
+using Discord.Interactions.Builders.Modals.Inputs;
+
+namespace Discord.Interactions.Info.InputComponents;
+
+public class UserSelectInputComponentInfo : SnowflakeSelectInputComponentInfo
+{
+ public UserSelectInputComponentInfo(UserSelectInputComponentBuilder builder, ModalInfo modal) : base(builder, modal) { }
+}
diff --git a/src/Discord.Net.Interactions/Info/ModalInfo.cs b/src/Discord.Net.Interactions/Info/ModalInfo.cs
index bef789ac9d..93e6c14449 100644
--- a/src/Discord.Net.Interactions/Info/ModalInfo.cs
+++ b/src/Discord.Net.Interactions/Info/ModalInfo.cs
@@ -1,3 +1,6 @@
+using Discord.Interactions.Builders;
+using Discord.Interactions.Builders.Modals.Inputs;
+using Discord.Interactions.Info.InputComponents;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -43,17 +46,40 @@ public class ModalInfo
///
public IReadOnlyCollection TextComponents { get; }
+ ///
+ /// Get a collection of the select menu components of this modal.
+ ///
+ public IReadOnlyCollection SelectMenuComponents { get; }
+
+ public IReadOnlyCollection UserSelectComponents { get; }
+
+ public IReadOnlyCollection RoleSelectComponents { get; }
+
+ public IReadOnlyCollection MentionableSelectComponents { get; }
+
+ public IReadOnlyCollection ChannelSelectComponents { get; }
+
internal ModalInfo(Builders.ModalBuilder builder)
{
Title = builder.Title;
Type = builder.Type;
- Components = builder.Components.Select(x => x switch
+ Components = builder.Components.Select(x => x switch
{
- Builders.TextInputComponentBuilder textComponent => textComponent.Build(this),
+ TextInputComponentBuilder textComponent => textComponent.Build(this),
+ SelectMenuInputComponentBuilder selectMenuComponent => selectMenuComponent.Build(this),
+ RoleSelectInputComponentBuilder roleSelectComponent => roleSelectComponent.Build(this),
+ ChannelSelectInputComponentBuilder channelSelectComponent => channelSelectComponent.Build(this),
+ UserSelectInputComponentBuilder userSelectComponent => userSelectComponent.Build(this),
+ MentionableSelectInputComponentBuilder mentionableSelectComponent => mentionableSelectComponent.Build(this),
_ => throw new InvalidOperationException($"{x.GetType().FullName} isn't a supported modal input component builder type.")
}).ToImmutableArray();
TextComponents = Components.OfType().ToImmutableArray();
+ SelectMenuComponents = Components.OfType().ToImmutableArray();
+ UserSelectComponents = Components.OfType().ToImmutableArray();
+ RoleSelectComponents = Components.OfType().ToImmutableArray();
+ MentionableSelectComponents = Components.OfType().ToImmutableArray();
+ ChannelSelectComponents = Components.OfType().ToImmutableArray();
_interactionService = builder._interactionService;
_initializer = builder.ModalInitializer;
diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs
index 3089aa5846..c12cbe486a 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -1,7 +1,10 @@
using Discord.Interactions.Builders;
+using Discord.Interactions.TypeConverters.ModalComponents;
+using Discord.Interactions.TypeConverters.ModalInputs;
using Discord.Logging;
using Discord.Rest;
using Discord.WebSocket;
+using Newtonsoft.Json.Bson;
using System;
using System.Collections;
using System.Collections.Concurrent;
@@ -98,6 +101,7 @@ public event Func InteractionE
private readonly TypeMap _typeConverterMap;
private readonly TypeMap _compTypeConverterMap;
private readonly TypeMap _typeReaderMap;
+ private readonly TypeMap _modalInputTypeConverterMap;
private readonly ConcurrentDictionary _autocompleteHandlers = new();
private readonly ConcurrentDictionary _modalInfos = new();
private readonly SemaphoreSlim _lock;
@@ -228,6 +232,16 @@ private InteractionService(Func getRestClient, InteractionSer
[typeof(Enum)] = typeof(EnumReader<>),
[typeof(Nullable<>)] = typeof(NullableReader<>)
});
+
+ _modalInputTypeConverterMap = new TypeMap(this, new ConcurrentDictionary
+ {
+ }, new ConcurrentDictionary
+ {
+ [typeof(IConvertible)] = typeof(DefaultValueModalComponentConverter<>),
+ [typeof(Enum)] = typeof(EnumModalComponentConverter<>),
+ [typeof(Nullable<>)] = typeof(NullableComponentConverter<>),
+ [typeof(Array)] = typeof(DefaultArrayModalComponentConverter<>)
+ });
}
///
@@ -1064,6 +1078,94 @@ public bool TryRemoveGenericTypeReader(out Type readerType)
public bool TryRemoveGenericTypeReader(Type type, out Type readerType)
=> _typeReaderMap.TryRemoveGeneric(type, out readerType);
+ internal ModalComponentTypeConverter GetModalInputTypeConverter(Type type, IServiceProvider services = null) =>
+ _modalInputTypeConverterMap.Get(type, services);
+
+ ///
+ /// Add a concrete type .
+ ///
+ /// Primary target of the .
+ /// The instance.
+ public void AddModalComponentTypeConverter(ModalComponentTypeConverter converter) =>
+ AddModalComponentTypeConverter(typeof(T), converter);
+
+ ///
+ /// Add a concrete type .
+ ///
+ /// Primary target of the .
+ /// The instance.
+ public void AddModalComponentTypeConverter(Type type, ModalComponentTypeConverter converter) =>
+ _modalInputTypeConverterMap.AddConcrete(type, converter);
+
+ ///
+ /// Add a generic type .
+ ///
+ /// Generic Type constraint of the of the .
+ /// Type of the .
+ public void AddGenericModalComponentTypeConverter(Type converterType) =>
+ AddGenericModalComponentTypeConverter(typeof(T), converterType);
+
+ ///
+ /// Add a generic type .
+ ///
+ /// Generic Type constraint of the of the .
+ /// Type of the .
+ public void AddGenericModalComponentTypeConverter(Type targetType, Type converterType) =>
+ _modalInputTypeConverterMap.AddGeneric(targetType, converterType);
+
+ ///
+ /// Removes a for the type .
+ ///
+ ///
+ /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// You need to reload the modules for the changes to take effect.
+ ///
+ /// The type to remove the converter from.
+ /// The converter if the resulting remove operation was successful.
+ /// if the remove operation was successful; otherwise .
+ public bool TryRemoveModalComponentTypeConverter(out ModalComponentTypeConverter converter) =>
+ TryRemoveModalComponentTypeConverter(typeof(T), out converter);
+
+ ///
+ /// Removes a for the type .
+ ///
+ ///
+ /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// You need to reload the modules for the changes to take effect.
+ ///
+ /// The type to remove the converter from.
+ /// The converter if the resulting remove operation was successful.
+ /// if the remove operation was successful; otherwise .
+ public bool TryRemoveModalComponentTypeConverter(Type type, out ModalComponentTypeConverter converter) =>
+ _modalInputTypeConverterMap.TryRemoveConcrete(type, out converter);
+
+ ///
+ /// Removes a generic for the type .
+ ///
+ ///
+ /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// You need to reload the modules for the changes to take effect.
+ ///
+ /// The type to remove the converter from.
+ /// The converter if the resulting remove operation was successful.
+ /// if the remove operation was successful; otherwise .
+ public bool TryRemoveGenericModalComponentTypeConverter(out Type converterType) =>
+ TryRemoveGenericModalComponentTypeConverter(typeof(T), out converterType);
+
+ ///
+ /// Removes a generic for the type .
+ ///
+ ///
+ /// Removing a from the will not dereference the from the loaded module/command instances.
+ /// You need to reload the modules for the changes to take effect.
+ ///
+ /// The type to remove the converter from.
+ /// The converter if the resulting remove operation was successful.
+ /// if the remove operation was successful; otherwise .
+ public bool TryRemoveGenericModalComponentTypeConverter(Type type, out Type converterType) =>
+ _modalInputTypeConverterMap.TryRemoveGeneric(type, out converterType);
+
+
///
/// Serialize an object using a into a to be placed in a Component CustomId.
///
diff --git a/src/Discord.Net.Interactions/TypeConverters/ModalComponents/DefaultArrayModalComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ModalComponents/DefaultArrayModalComponentConverter.cs
new file mode 100644
index 0000000000..5e8b2c02a3
--- /dev/null
+++ b/src/Discord.Net.Interactions/TypeConverters/ModalComponents/DefaultArrayModalComponentConverter.cs
@@ -0,0 +1,170 @@
+using Discord.Interactions.TypeConverters.ModalInputs;
+using Discord.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions.TypeConverters.ModalComponents;
+
+internal sealed class DefaultArrayModalComponentConverter : ModalComponentTypeConverter
+{
+ private readonly Type _underlyingType;
+ private readonly TypeReader _typeReader;
+ private readonly List _channelTypes;
+
+ public DefaultArrayModalComponentConverter(InteractionService interactionService)
+ {
+ var type = typeof(T);
+
+ if (!type.IsArray)
+ throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter)} cannot be used to convert a non-array type.");
+
+ _underlyingType = typeof(T).GetElementType();
+
+ _typeReader = true switch
+ {
+ _ when typeof(IUser).IsAssignableFrom(_underlyingType)
+ || typeof(IChannel).IsAssignableFrom(_underlyingType)
+ || typeof(IMentionable).IsAssignableFrom(_underlyingType)
+ || typeof(IRole).IsAssignableFrom(_underlyingType) => null,
+ _ => interactionService.GetTypeReader(_underlyingType)
+ };
+
+ _channelTypes = true switch
+ {
+ _ when typeof(IStageChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Stage },
+
+ _ when typeof(IVoiceChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Voice },
+
+ _ when typeof(IDMChannel).IsAssignableFrom(type)
+ => new List { ChannelType.DM },
+
+ _ when typeof(IGroupChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Group },
+
+ _ when typeof(ICategoryChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Category },
+
+ _ when typeof(INewsChannel).IsAssignableFrom(type)
+ => new List { ChannelType.News },
+
+ _ when typeof(IThreadChannel).IsAssignableFrom(type)
+ => new List { ChannelType.PublicThread, ChannelType.PrivateThread, ChannelType.NewsThread },
+
+ _ when typeof(ITextChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Text },
+
+ _ when typeof(IMediaChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Media },
+
+ _ when typeof(IForumChannel).IsAssignableFrom(type)
+ => new List { ChannelType.Forum },
+
+ _ => null
+ };
+ }
+
+ public override async Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
+ {
+ var objs = new List