Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
627eadf
add interaction service attributes new modal input types
Cenngo Sep 17, 2025
5d4d7fe
add new modal component type converters
Cenngo Sep 17, 2025
4e41461
relocate hide attribute outside of slash command enum converter for g…
Cenngo Sep 17, 2025
6e95200
refactor base inputcomponentbuilder types to use modal component type…
Cenngo Sep 17, 2025
055fe77
add builders for new modal input types
Cenngo Sep 17, 2025
297c327
add component insertion methods to modal builder for new input types
Cenngo Sep 17, 2025
1d971c8
refactor base inputcomponentinfo class to use modal component typecon…
Cenngo Sep 17, 2025
d773b4e
add component info classes for newly added modal input types
Cenngo Sep 17, 2025
3c920d4
add build logic for new modal input metadata classes
Cenngo Sep 17, 2025
73004bb
add componet collection properties for new modal input types to modal…
Cenngo Sep 17, 2025
f6256e5
implement convertion logic for new modal inputs to the respond with m…
Cenngo Sep 17, 2025
651329a
implement modal input typeconverters into interaction service
Cenngo Sep 17, 2025
1de62c0
add read logic to enum modal component typeconverter
Cenngo Sep 17, 2025
9a59117
add default entity modal component typeconverter
Cenngo Sep 17, 2025
fc95bb4
add write logic to default value modal component typeconverter
Cenngo Sep 17, 2025
6c50876
add write logic to the nullable modal component typeconverter
Cenngo Sep 17, 2025
b12bc40
Merge branch 'feature/modal-components-v2' into dev
Cenngo Nov 9, 2025
0bc1da7
Merge pull request #1 from Cenngo/dev
Cenngo Nov 9, 2025
dafd51f
Merge branch 'feature/modal-components-v2' of https://github.com/Cenn…
Cenngo Nov 9, 2025
741aa51
add description property to input label attribute
Cenngo Nov 10, 2025
85c8583
add inline docs to modal attributes
Cenngo Nov 10, 2025
375fcfe
add modal file upload attribute
Cenngo Nov 10, 2025
eca76f3
add inline docs to input component infos
Cenngo Nov 10, 2025
b24a115
add description property to input component builder
Cenngo Nov 10, 2025
2eb8672
add inline docs to modal component builders
Cenngo Nov 10, 2025
81581ee
add modal file upload component info
Cenngo Nov 10, 2025
7af0c48
add modal file upload component builder
Cenngo Nov 10, 2025
0ae42e0
rename select input attribute
Cenngo Nov 10, 2025
7e878a8
refactor select input attribute and add description property in modul…
Cenngo Nov 10, 2025
120ab04
add inline docs to modalBuilder
Cenngo Nov 10, 2025
6633091
add description to inputComponentInfo
Cenngo Nov 10, 2025
33bc29d
file-scope namespace for commandBuilder and modal interfaces
Cenngo Nov 10, 2025
8bd22d8
update respondWithModal logic to include new components
Cenngo Nov 10, 2025
2d9327a
add inline docs and file upload component to modalInfo
Cenngo Nov 10, 2025
8fbca81
add attachment modal typeconverter
Cenngo Nov 10, 2025
bf63514
create base non-input modal component entities
Cenngo Nov 12, 2025
77d1062
update modal component typeconverter namespaces and remove unused
Cenngo Nov 12, 2025
a4518f1
add default min/max values to select input attribute
Cenngo Nov 12, 2025
eedf53e
create text display builder and info classes
Cenngo Nov 12, 2025
54266b8
add text display parsing logic
Cenngo Nov 12, 2025
3e5e196
add text display attribute
Cenngo Nov 12, 2025
751a71d
add modal select menu option attribute
Cenngo Nov 12, 2025
02c75ea
add docs to text display component info
Cenngo Nov 12, 2025
54d0379
fix text display parsing
Cenngo Nov 12, 2025
7ac5559
Merge branch 'discord-net:dev' into feature/modal-components-v2
Cenngo Nov 12, 2025
f9e11bd
add isRequired mapping to select menus
Cenngo Nov 12, 2025
1e61ea3
revert to block-scope to clear diff false-positives
Cenngo Nov 12, 2025
625e7c2
fix inline doc annotations
Cenngo Nov 12, 2025
18e67aa
fix build errors
Cenngo Nov 12, 2025
7d039f4
add interaction parameter to modal component typeconverter write method
Cenngo Nov 12, 2025
d4ff359
add null check to select menu option attribute
Cenngo Nov 12, 2025
cb21cbe
add null check to default value modalTypeConverter write method
Cenngo Nov 12, 2025
fa4d7a1
make ctors of modal component base attributes internal
Cenngo Nov 13, 2025
d2b66ca
implement predicate to hide attribute and enum modalcomponent typecon…
Cenngo Nov 13, 2025
a5acfb3
fix HideAttribute inline docs build errors
Cenngo Nov 13, 2025
56ba54f
simplify naming of the component classes and normalize namespaces
Cenngo Nov 13, 2025
138134f
fix build errors in module class builder
Cenngo Nov 13, 2025
cef3aa0
add inline docs to modalComponentTypeConverter TryGetModalInteraction…
Cenngo Nov 13, 2025
21fb8ee
add min/max values parameters to ModalChannelSelectAttribute
Cenngo Nov 13, 2025
c404da4
fix defaultArrayModalTypeConverter chanell type write logic
Cenngo Nov 13, 2025
2e0b57f
simplify addDefaultValue methods for channe, mentionable, role, and u…
Cenngo Nov 15, 2025
51e3239
add emoji support to select menu options
Cenngo Nov 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/Discord.Net.Interactions/Attributes/HideAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Discord.Interactions;

/// <summary>
/// Enum values tagged with this attribute will not be displayed as a parameter choice
/// </summary>
/// <remarks>
/// This attribute must be used along with the default <see cref="EnumConverter{T}"/> and <see cref="DefaultEntityTypeConverter{T}"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class HideAttribute : Attribute
{
/// <summary>
/// Can be optionally implemented by inherited types to conditionally hide an enum value.
/// </summary>
/// <remarks>
/// Only runs on prior to modal construction. For slash command parameters, this method is ignored.
/// </remarks>
/// <param name="interaction">Interaction that <see cref="IDiscordInteractionExtentions.RespondWithModalAsync{T}(IDiscordInteraction, string, T, RequestOptions, Action{ModalBuilder})"/> is called on.</param>
/// <returns>
/// <see langword="true"/> if the attribute should be active and hide the value.
/// </returns>
public virtual bool Predicate(IDiscordInteraction interaction) => true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ public class InputLabelAttribute : Attribute
/// </summary>
public string Label { get; }

/// <summary>
/// Gets the label description of the input.
/// </summary>
public string Description { get; set; }

/// <summary>
/// Creates a custom label for an modal input.
/// </summary>
/// <param name="label">The label of the input.</param>
public InputLabelAttribute(string label)
/// <param name="description">The label description of the input.</param>
public InputLabelAttribute(string label, string description = null)
{
Label = label;
Description = description;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a channel select.
/// </summary>
public class ModalChannelSelectAttribute : ModalSelectComponentAttribute
{
/// <inheritdoc/>
public override ComponentType ComponentType => ComponentType.ChannelSelect;

/// <summary>
/// Create a new <see cref="ModalChannelSelectAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the channel select component.</param>
public ModalChannelSelectAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId, minValues, maxValues) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Discord.Interactions;

/// <summary>
/// Mark an <see cref="IModal"/> property as a modal component field.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public abstract class ModalComponentAttribute : Attribute
{
/// <summary>
/// Gets the type of the component.
/// </summary>
public abstract ComponentType ComponentType { get; }

internal ModalComponentAttribute() { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a file upload input.
/// </summary>
public class ModalFileUploadAttribute : ModalInputAttribute
{
/// <inheritdoc/>
public override ComponentType ComponentType => ComponentType.FileUpload;

/// <summary>
/// Get the minimum number of files that can be uploaded.
/// </summary>
public int MinValues { get; set; } = 1;

/// <summary>
/// Get the maximum number of files that can be uploaded.
/// </summary>
public int MaxValues { get; set; } = 1;

/// <summary>
/// Create a new <see cref="ModalFileUploadAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the file upload component.</param>
/// <param name="minValues">Minimum number of files that can be uploaded.</param>
/// <param name="maxValues">Maximum number of files that can be uploaded.</param>
public ModalFileUploadAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId)
{
MinValues = minValues;
MaxValues = maxValues;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,18 @@ namespace Discord.Interactions
/// Mark an <see cref="IModal"/> property as a modal input field.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public abstract class ModalInputAttribute : Attribute
public abstract class ModalInputAttribute : ModalComponentAttribute
{
/// <summary>
/// Gets the custom id of the text input.
/// </summary>
public string CustomId { get; }

/// <summary>
/// Gets the type of the component.
/// </summary>
public abstract ComponentType ComponentType { get; }

/// <summary>
/// Create a new <see cref="ModalInputAttribute"/>.
/// </summary>
/// <param name="customId">The custom id of the input.</param>
protected ModalInputAttribute(string customId)
internal ModalInputAttribute(string customId)
{
CustomId = customId;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a mentionable select input.
/// </summary>
public class ModalMentionableSelectAttribute : ModalSelectComponentAttribute
{
/// <inheritdoc />
public override ComponentType ComponentType => ComponentType.MentionableSelect;

/// <summary>
/// Create a new <see cref="ModalMentionableSelectAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the mentionable select component.</param>
/// <param name="minValues">Minimum number of values that can be selected.</param>
/// <param name="maxValues">Maximum number of values that can be selected</param>
public ModalMentionableSelectAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId, minValues, maxValues) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a role select input.
/// </summary>
public class ModalRoleSelectAttribute : ModalSelectComponentAttribute
{
/// <inheritdoc/>
public override ComponentType ComponentType => ComponentType.RoleSelect;

/// <summary>
/// Create a new <see cref="ModalRoleSelectAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the role select component.</param>
/// <param name="minValues">Minimum number of values that can be selected.</param>
/// <param name="maxValues">Maximum number of values that can be selected.</param>
public ModalRoleSelectAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId, minValues, maxValues) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Discord.Interactions;

/// <summary>
/// Base attribute for select-menu, user, channel, role, and mentionable select inputs in modals.
/// </summary>
public abstract class ModalSelectComponentAttribute : ModalInputAttribute
{
/// <summary>
/// Gets or sets the minimum number of values that can be selected.
/// </summary>
public int MinValues { get; set; } = 1;

/// <summary>
/// Gets or sets the maximum number of values that can be selected.
/// </summary>
public int MaxValues { get; set; } = 1;

/// <summary>
/// Gets or sets the placeholder text.
/// </summary>
public string Placeholder { get; set; }

internal ModalSelectComponentAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId)
{
MinValues = minValues;
MaxValues = maxValues;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a select menu input.
/// </summary>
public sealed class ModalSelectMenuAttribute : ModalSelectComponentAttribute
{
/// <inheritdoc />
public override ComponentType ComponentType => ComponentType.SelectMenu;

/// <summary>
/// Create a new <see cref="ModalSelectMenuAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the select menu component.</param>
/// <param name="minValues">Minimum number of values that can be selected.</param>
/// <param name="maxValues">Maximum number of values that can be selected.</param>
public ModalSelectMenuAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId, minValues, maxValues) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;

namespace Discord.Interactions;

/// <summary>
/// Adds a select menu option to the marked field.
/// </summary>
/// <remarks>
/// To add additional metadata to enum fields, use <see cref="SelectMenuOptionAttribute"/> instead.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ModalSelectMenuOptionAttribute : Attribute
{
/// <summary>
/// Gets the label of the option.
/// </summary>
public string Label { get; }

/// <summary>
/// Gets or sets the description of the option.
/// </summary>
public string Description { get; set; }

/// <summary>
/// Gets the value of the option.
/// </summary>
public string Value { get; }

/// <summary>
/// Gets or sets the emote of the option.
/// </summary>
/// <remarks>
/// Can be either an <see cref="Emoji"/> or an <see cref="Discord.Emote"/>
/// </remarks>
public string Emote { get; set; }

/// <summary>
/// Gets or sets whether the option is selected by default.
/// </summary>
public bool IsDefault { get; set; }

/// <summary>
/// Create a new <see cref="ModalSelectComponentAttribute"/>.
/// </summary>
/// <param name="label">Label of the option.</param>
/// <param name="value">Value of the option.</param>
/// <param name="description">Description of the option.</param>
/// <param name="emote">Emote of the option. Can be either an <see cref="Emoji"/> or an <see cref="Discord.Emote"/></param>
/// <param name="isDefault">Whether the option is selected by default</param>
public ModalSelectMenuOptionAttribute(string label, string value, string description = null, string emote = null, bool isDefault = false)
{
Label = label;
Value = value;
Description = description;
Emote = emote;
IsDefault = isDefault;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a text input.
/// </summary>
public class ModalTextDisplayAttribute : ModalComponentAttribute
{
/// <inheritdoc/>
public override ComponentType ComponentType => ComponentType.TextDisplay;

/// <summary>
/// Gets the content of the text display.
/// </summary>
public string Content { get; }

/// <summary>
/// Create a new <see cref="ModalTextInputAttribute"/>.
/// </summary>
/// <param name="content">Content of the text display.</param>
public ModalTextDisplayAttribute(string content = null)
{
Content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Discord.Interactions;

/// <summary>
/// Marks a <see cref="IModal"/> property as a user select input.
/// </summary>
public class ModalUserSelectAttribute : ModalSelectComponentAttribute
{
/// <inheritdoc/>
public override ComponentType ComponentType => ComponentType.UserSelect;

/// <summary>
/// Create a new <see cref="ModalUserSelectAttribute"/>.
/// </summary>
/// <param name="customId">Custom ID of the user select component.</param>
/// <param name="minValues">Minimum number of values that can be selected.</param>
/// <param name="maxValues">Maximum number of values that can be selected.</param>
public ModalUserSelectAttribute(string customId, int minValues = 1, int maxValues = 1) : base(customId, minValues, maxValues) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;

namespace Discord.Interactions.Builders;

/// <summary>
/// Represents a builder for creating <see cref="ChannelSelectComponentInfo"/>.
/// </summary>
public class ChannelSelectComponentBuilder : SnowflakeSelectComponentBuilder<ChannelSelectComponentInfo, ChannelSelectComponentBuilder>
{
protected override ChannelSelectComponentBuilder Instance => this;

/// <summary>
/// Initializes a new <see cref="ChannelSelectComponentBuilder"/>.
/// </summary>
/// <param name="modal">Parent modal of this component.</param>
public ChannelSelectComponentBuilder(ModalBuilder modal) : base(modal, ComponentType.ChannelSelect) { }

/// <summary>
/// Adds a default value to <see cref="SnowflakeSelectComponentBuilder{TInfo, TBuilder}.DefaultValues"/>.
/// </summary>
/// <param name="channelId">The channel ID to add as a default value.</param>
/// <returns>
/// The builder instance.
/// </returns>
public ChannelSelectComponentBuilder AddDefaulValue(ulong channelId)
{
_defaultValues.Add(new SelectMenuDefaultValue(channelId, SelectDefaultValueType.Channel));
return this;
}

/// <summary>
/// Adds default values to <see cref="SnowflakeSelectComponentBuilder{TInfo, TBuilder}.DefaultValues"/>.
/// </summary>
/// <param name="channels">The channels to add as a default value.</param>
/// <returns>
/// The builder instance.
/// </returns>
public ChannelSelectComponentBuilder AddDefaultValues(params IEnumerable<IChannel> channels)
{
_defaultValues.AddRange(channels.Select(x => new SelectMenuDefaultValue(x.Id, SelectDefaultValueType.Channel)));
return this;
}

internal override ChannelSelectComponentInfo Build(ModalInfo modal)
=> new(this, modal);
}
Loading