[Feature] Modal refactoring & select menu support (#3172)
* add missing xmldoc + new component type * add API models * label builder * most POG commit ever * another pog commit (add file upload component) * add a constructor & missing extension methods * refactor modal builder * Fix build errors * Add xml docs and fluent methods to new components * Fix naming * Set default value of `FileUploadComponentBuilder.IsRequired` to `true` * xmldoc fixes * Support receiving modal interactions with new components & implement resolved data * Add missing text display methods to modal builder * Update src/Discord.Net.Rest/API/Common/FileUploadComponent.cs Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> --------- Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com> Co-authored-by: d4n <dan3436@hotmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ namespace Discord;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ButtonBuilder : IInteractableComponentBuilder
|
public class ButtonBuilder : IInteractableComponentBuilder
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public ComponentType Type => ComponentType.Button;
|
public ComponentType Type => ComponentType.Button;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Discord;
|
|||||||
public class ComponentBuilder
|
public class ComponentBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The max length of a <see cref="ButtonComponent.CustomId"/>.
|
/// The max length of <see cref="ButtonComponent.CustomId"/> and <see cref="SelectMenuComponent.CustomId"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxCustomIdLength = 100;
|
public const int MaxCustomIdLength = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -469,6 +469,54 @@ public static class ComponentContainerExtensions
|
|||||||
return container.WithActionRow(cont);
|
return container.WithActionRow(cont);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="FileUploadComponentBuilder"/> to the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current container.
|
||||||
|
/// </returns>
|
||||||
|
public static BuilderT WithFileUpload<BuilderT>(this BuilderT container, FileUploadComponentBuilder fileUpload)
|
||||||
|
where BuilderT : class, IInteractableComponentContainer
|
||||||
|
{
|
||||||
|
container.AddComponent(fileUpload);
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="FileUploadComponentBuilder"/> to the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current container.
|
||||||
|
/// </returns>
|
||||||
|
public static BuilderT WithFileUpload<BuilderT>(this BuilderT container,
|
||||||
|
string customId,
|
||||||
|
int? minValues = null,
|
||||||
|
int? maxValues = null,
|
||||||
|
bool isRequired = true,
|
||||||
|
int? id = null)
|
||||||
|
where BuilderT : class, IInteractableComponentContainer
|
||||||
|
=> container.WithFileUpload(new FileUploadComponentBuilder()
|
||||||
|
.WithCustomId(customId)
|
||||||
|
.WithMinValues(minValues)
|
||||||
|
.WithMaxValues(maxValues)
|
||||||
|
.WithRequired(isRequired)
|
||||||
|
.WithId(id));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="FileUploadComponentBuilder"/> to the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The current container.
|
||||||
|
/// </returns>
|
||||||
|
public static BuilderT WithFileUpload<BuilderT>(this BuilderT container,
|
||||||
|
Action<FileUploadComponentBuilder> options)
|
||||||
|
where BuilderT : class, IInteractableComponentContainer
|
||||||
|
{
|
||||||
|
var comp = new FileUploadComponentBuilder();
|
||||||
|
options(comp);
|
||||||
|
return container.WithFileUpload(comp);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the first <see cref="IMessageComponentBuilder"/> in the <see cref="IComponentContainer"/>
|
/// Finds the first <see cref="IMessageComponentBuilder"/> in the <see cref="IComponentContainer"/>
|
||||||
/// or any of its child <see cref="IComponentContainer"/>s with matching id.
|
/// or any of its child <see cref="IComponentContainer"/>s with matching id.
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ using System;
|
|||||||
|
|
||||||
namespace Discord;
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class used to build <see cref="FileComponent"/>'s.
|
||||||
|
/// </summary>
|
||||||
public class FileComponentBuilder : IMessageComponentBuilder
|
public class FileComponentBuilder : IMessageComponentBuilder
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class used to build <see cref="FileUploadComponent"/>'s.
|
||||||
|
/// </summary>
|
||||||
|
public class FileUploadComponentBuilder : IInteractableComponentBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of values for the <see cref="FileUploadComponentBuilder.MinValues"/> and <see cref="FileUploadComponentBuilder.MaxValues"/> properties.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxFileCount = 10;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ComponentType Type => ComponentType.FileUpload;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the custom id of the current file upload.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length exceeds <see cref="ModalComponentBuilder.MaxCustomIdLength"/>.</exception>
|
||||||
|
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length subceeds 1.</exception>
|
||||||
|
public string CustomId
|
||||||
|
{
|
||||||
|
get => _customId;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
Preconditions.AtLeast(value.Length, 1, nameof(CustomId));
|
||||||
|
Preconditions.AtMost(value.Length, ModalComponentBuilder.MaxCustomIdLength, nameof(CustomId));
|
||||||
|
}
|
||||||
|
|
||||||
|
_customId = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum number of items that must be uploaded (defaults to 1).
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException" accessor="set"><see cref="MinValues"/> exceeds <see cref="MaxFileCount"/>.</exception>
|
||||||
|
/// <exception cref="ArgumentException" accessor="set"><see cref="MinValues"/> length subceeds 0.</exception>
|
||||||
|
public int? MinValues
|
||||||
|
{
|
||||||
|
get => _minValues;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
Preconditions.AtLeast(value.Value, 0, nameof(MinValues));
|
||||||
|
Preconditions.AtMost(value.Value, MaxFileCount, nameof(MinValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
_minValues = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of items that can be uploaded (defaults to 1).
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException" accessor="set"><see cref="MaxValues"/> exceeds <see cref="MaxFileCount"/>.</exception>
|
||||||
|
public int? MaxValues
|
||||||
|
{
|
||||||
|
get => _maxValues;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
Preconditions.AtMost(value.Value, MaxFileCount, nameof(MaxValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
_maxValues = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the current file upload requires files to be uploaded before submitting the modal (defaults to <see langword="true"></see>).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRequired { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the custom id of the current file upload.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="customId">The id to use for the current file upload.</param>
|
||||||
|
/// <inheritdoc cref="CustomId"/>
|
||||||
|
/// <returns>The current builder.</returns>
|
||||||
|
public FileUploadComponentBuilder WithCustomId(string customId)
|
||||||
|
{
|
||||||
|
CustomId = customId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the minimum number of items that must be uploaded (defaults to 1).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="minValues">Sets the minimum number of items that must be uploaded.</param>
|
||||||
|
/// <inheritdoc cref="MinValues"/>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public FileUploadComponentBuilder WithMinValues(int? minValues)
|
||||||
|
{
|
||||||
|
MinValues = minValues;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the maximum number of items that can be uploaded (defaults to 1).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxValues">The maximum number of items that can be uploaded.</param>
|
||||||
|
/// <inheritdoc cref="MaxValues"/>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public FileUploadComponentBuilder WithMaxValues(int? maxValues)
|
||||||
|
{
|
||||||
|
MaxValues = maxValues;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether the current file upload requires files to be uploaded before submitting the modal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isRequired">Whether the current file upload requires files to be uploaded before submitting the modal.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public FileUploadComponentBuilder WithRequired(bool isRequired)
|
||||||
|
{
|
||||||
|
IsRequired = isRequired;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _customId;
|
||||||
|
private int? _minValues;
|
||||||
|
private int? _maxValues;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileUploadComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public FileUploadComponentBuilder() {}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileUploadComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="customId">The custom id of the current file upload.</param>
|
||||||
|
/// <param name="minValues">The minimum number of items that must be uploaded (defaults to 1).</param>
|
||||||
|
/// <param name="maxValues">the maximum number of items that can be uploaded (defaults to 1).</param>
|
||||||
|
/// <param name="isRequired">Whether the current file upload requires files to be uploaded before submitting the modal.</param>
|
||||||
|
/// <param name="id">The id for the component.</param>
|
||||||
|
public FileUploadComponentBuilder(string customId, int? minValues = null, int? maxValues = null, bool isRequired = true, int? id = null)
|
||||||
|
{
|
||||||
|
CustomId = customId;
|
||||||
|
MinValues = minValues;
|
||||||
|
MaxValues = maxValues;
|
||||||
|
IsRequired = isRequired;
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FileUploadComponentBuilder"/> class from an existing <see cref="FileUploadComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileUpload">The component.</param>
|
||||||
|
public FileUploadComponentBuilder(FileUploadComponent fileUpload)
|
||||||
|
{
|
||||||
|
CustomId = fileUpload.CustomId;
|
||||||
|
MinValues = fileUpload.MinValues;
|
||||||
|
MaxValues = fileUpload.MaxValues;
|
||||||
|
IsRequired = fileUpload.IsRequired;
|
||||||
|
Id = fileUpload.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMessageComponentBuilder.Build" />
|
||||||
|
public FileUploadComponent Build()
|
||||||
|
{
|
||||||
|
Preconditions.NotNullOrWhitespace(CustomId, nameof(CustomId));
|
||||||
|
|
||||||
|
if (MinValues is not null && MaxValues is not null)
|
||||||
|
Preconditions.AtLeast(MaxValues.Value, MinValues.Value, nameof(MaxValues));
|
||||||
|
|
||||||
|
Preconditions.AtMost(MinValues ?? 0, MaxFileCount, nameof(MinValues));
|
||||||
|
Preconditions.AtMost(MaxValues ?? 0, MaxFileCount, nameof(MaxValues));
|
||||||
|
|
||||||
|
return new FileUploadComponent(Id, CustomId, MinValues, MaxValues, IsRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IMessageComponent IMessageComponentBuilder.Build() => Build();
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class used to build <see cref="LabelComponent"/>'s.
|
||||||
|
/// </summary>
|
||||||
|
public class LabelBuilder : IMessageComponentBuilder
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="IComponentContainer.SupportedComponentTypes"/>
|
||||||
|
public ImmutableArray<ComponentType> SupportedComponentTypes { get; } =
|
||||||
|
[
|
||||||
|
ComponentType.SelectMenu,
|
||||||
|
ComponentType.TextInput,
|
||||||
|
ComponentType.UserSelect,
|
||||||
|
ComponentType.RoleSelect,
|
||||||
|
ComponentType.MentionableSelect,
|
||||||
|
ComponentType.ChannelSelect,
|
||||||
|
ComponentType.FileUpload
|
||||||
|
];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum length of the label.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxLabelLength = 45;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum length of the description.
|
||||||
|
/// </summary>
|
||||||
|
public const int MaxDescriptionLength = 100;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ComponentType Type => ComponentType.Label;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the label text.
|
||||||
|
/// </summary>
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
get => _label;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
Preconditions.AtMost(value.Length, MaxLabelLength, nameof(Label));
|
||||||
|
}
|
||||||
|
|
||||||
|
_label = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the description text for the label.
|
||||||
|
/// </summary>
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get => _description;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
Preconditions.AtMost(value.Length, MaxDescriptionLength, nameof(Description));
|
||||||
|
}
|
||||||
|
|
||||||
|
_description = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the component within the label.
|
||||||
|
/// </summary>
|
||||||
|
public IMessageComponentBuilder Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the label text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label text.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public LabelBuilder WithLabel(string label)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the description text for the label.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">The description text for the label.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public LabelBuilder WithDescription(string description)
|
||||||
|
{
|
||||||
|
Description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the component within the label.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component">The component within the label.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public LabelBuilder WithComponent(IMessageComponentBuilder component)
|
||||||
|
{
|
||||||
|
Component = component;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _label;
|
||||||
|
private string _description;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new <see cref="LabelBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public LabelBuilder() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new <see cref="LabelBuilder"/> with the specified content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label text.</param>
|
||||||
|
/// <param name="component">The component within the label.</param>
|
||||||
|
/// <param name="description">The description text for the label.</param>
|
||||||
|
/// <param name="id">The id for the component.</param>
|
||||||
|
public LabelBuilder(string label, IMessageComponentBuilder component, string description = null, int? id = null)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Label = label;
|
||||||
|
Component = component;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new <see cref="LabelBuilder"/> from existing component.
|
||||||
|
/// </summary>
|
||||||
|
public LabelBuilder(LabelComponent label)
|
||||||
|
{
|
||||||
|
Label = label.Label;
|
||||||
|
Description = label.Description;
|
||||||
|
Id = label.Id;
|
||||||
|
Component = label.Component.ToBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMessageComponentBuilder.Build" />
|
||||||
|
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.Contains(Component.Type))
|
||||||
|
throw new InvalidOperationException($"Component can only be {nameof(SelectMenuBuilder)}, {nameof(TextInputBuilder)} or {nameof(FileUploadComponentBuilder)}.");
|
||||||
|
|
||||||
|
return new LabelComponent(Id, Label, Description, Component.Build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IMessageComponent IMessageComponentBuilder.Build() => Build();
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ public class MediaGalleryBuilder : IMessageComponentBuilder
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
private List<MediaGalleryItemProperties> _items = new();
|
private List<MediaGalleryItemProperties> _items = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>.
|
/// Initializes a new instance of the <see cref="MediaGalleryBuilder"/>.
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ using System;
|
|||||||
|
|
||||||
namespace Discord;
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a class used to build <see cref="TextDisplayComponent"/>'s.
|
||||||
|
/// </summary>
|
||||||
public class TextDisplayBuilder : IMessageComponentBuilder
|
public class TextDisplayBuilder : IMessageComponentBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// The max length of a <see cref="TextInputComponent.Placeholder"/>.
|
/// The max length of a <see cref="TextInputComponent.Placeholder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int MaxPlaceholderLength = 100;
|
public const int MaxPlaceholderLength = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max value for <see cref="TextInputBuilder.MaxLength"/> and <see cref="TextInputBuilder.MinLength"/>, and the max length for <see cref="TextInputBuilder.Value"/>.
|
||||||
|
/// </summary>
|
||||||
public const int LargestMaxLength = 4000;
|
public const int LargestMaxLength = 4000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the custom id of the current text input.
|
/// Gets or sets the custom id of the current text input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length exceeds <see cref="ComponentBuilder.MaxCustomIdLength"/></exception>
|
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length exceeds <see cref="ModalComponentBuilder.MaxCustomIdLength"/>.</exception>
|
||||||
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length subceeds 1.</exception>
|
/// <exception cref="ArgumentException" accessor="set"><see cref="CustomId"/> length subceeds 1.</exception>
|
||||||
public string CustomId
|
public string CustomId
|
||||||
{
|
{
|
||||||
@@ -29,7 +33,7 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
if (value is not null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(CustomId));
|
Preconditions.AtLeast(value.Length, 1, nameof(CustomId));
|
||||||
Preconditions.AtMost(value.Length, ComponentBuilder.MaxCustomIdLength, nameof(CustomId));
|
Preconditions.AtMost(value.Length, ModalComponentBuilder.MaxCustomIdLength, nameof(CustomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
_customId = value;
|
_customId = value;
|
||||||
@@ -44,6 +48,7 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the label of the current text input.
|
/// Gets or sets the label of the current text input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Label is no longer supported", error: false)]
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,7 +60,8 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
get => _placeholder;
|
get => _placeholder;
|
||||||
set => _placeholder = (value?.Length ?? 0) <= MaxPlaceholderLength
|
set => _placeholder = (value?.Length ?? 0) <= MaxPlaceholderLength
|
||||||
? value
|
? value
|
||||||
: throw new ArgumentException($"Placeholder cannot have more than {MaxPlaceholderLength} characters. Value: \"{value}\"");
|
: throw new ArgumentException(
|
||||||
|
$"Placeholder cannot have more than {MaxPlaceholderLength} characters. Value: \"{value}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,7 +78,8 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
if (value < 0)
|
if (value < 0)
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MinLength must not be less than 0");
|
throw new ArgumentOutOfRangeException(nameof(value), $"MinLength must not be less than 0");
|
||||||
if (value > LargestMaxLength)
|
if (value > LargestMaxLength)
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MinLength must not be greater than {LargestMaxLength}");
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
$"MinLength must not be greater than {LargestMaxLength}");
|
||||||
if (value > (MaxLength ?? LargestMaxLength))
|
if (value > (MaxLength ?? LargestMaxLength))
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MinLength must be less than MaxLength");
|
throw new ArgumentOutOfRangeException(nameof(value), $"MinLength must be less than MaxLength");
|
||||||
_minLength = value;
|
_minLength = value;
|
||||||
@@ -93,9 +100,11 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
if (value < 0)
|
if (value < 0)
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MaxLength must not be less than 0");
|
throw new ArgumentOutOfRangeException(nameof(value), $"MaxLength must not be less than 0");
|
||||||
if (value > LargestMaxLength)
|
if (value > LargestMaxLength)
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MaxLength most not be greater than {LargestMaxLength}");
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
$"MaxLength most not be greater than {LargestMaxLength}");
|
||||||
if (value < (MinLength ?? -1))
|
if (value < (MinLength ?? -1))
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"MaxLength must be greater than MinLength ({MinLength})");
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
$"MaxLength must be greater than MinLength ({MinLength})");
|
||||||
_maxLength = value;
|
_maxLength = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +114,7 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? Required { get; set; }
|
public bool? Required { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,9 +133,11 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value?.Length > (MaxLength ?? LargestMaxLength))
|
if (value?.Length > (MaxLength ?? LargestMaxLength))
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"Value must not be longer than {MaxLength ?? LargestMaxLength}. Value: \"{value}\"");
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
$"Value must not be longer than {MaxLength ?? LargestMaxLength}. Value: \"{value}\"");
|
||||||
if (value?.Length < (MinLength ?? 0))
|
if (value?.Length < (MinLength ?? 0))
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"Value must not be shorter than {MinLength}. Value: \"{value}\"");
|
throw new ArgumentOutOfRangeException(nameof(value),
|
||||||
|
$"Value must not be shorter than {MinLength}. Value: \"{value}\"");
|
||||||
|
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
@@ -140,17 +152,25 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of a <see cref="TextInputBuilder"/>.
|
/// Creates a new instance of a <see cref="TextInputBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="label">The text input's label.</param>
|
|
||||||
/// <param name="style">The text input's style.</param>
|
/// <param name="style">The text input's style.</param>
|
||||||
/// <param name="customId">The text input's custom id.</param>
|
/// <param name="customId">The text input's custom id.</param>
|
||||||
/// <param name="placeholder">The text input's placeholder.</param>
|
/// <param name="placeholder">The text input's placeholder.</param>
|
||||||
/// <param name="minLength">The text input's minimum length.</param>
|
/// <param name="minLength">The text input's minimum length.</param>
|
||||||
/// <param name="maxLength">The text input's maximum length.</param>
|
/// <param name="maxLength">The text input's maximum length.</param>
|
||||||
/// <param name="required">The text input's required value.</param>
|
/// <param name="required">The text input's required value.</param>
|
||||||
public TextInputBuilder(string label, string customId, TextInputStyle style = TextInputStyle.Short, string placeholder = null,
|
/// <param name="value">The text input's default value.</param>
|
||||||
int? minLength = null, int? maxLength = null, bool? required = null, string value = null, int? id = null)
|
/// <param name="id">The id for the component.</param>
|
||||||
|
public TextInputBuilder(
|
||||||
|
string customId,
|
||||||
|
TextInputStyle style = TextInputStyle.Short,
|
||||||
|
string placeholder = null,
|
||||||
|
int? minLength = null,
|
||||||
|
int? maxLength = null,
|
||||||
|
bool? required = null,
|
||||||
|
string value = null,
|
||||||
|
int? id = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Label = label;
|
|
||||||
Style = style;
|
Style = style;
|
||||||
CustomId = customId;
|
CustomId = customId;
|
||||||
Placeholder = placeholder;
|
Placeholder = placeholder;
|
||||||
@@ -161,12 +181,37 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
Id = id;
|
Id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of a <see cref="TextInputBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The text input's label.</param>
|
||||||
|
/// <param name="style">The text input's style.</param>
|
||||||
|
/// <param name="customId">The text input's custom id.</param>
|
||||||
|
/// <param name="placeholder">The text input's placeholder.</param>
|
||||||
|
/// <param name="minLength">The text input's minimum length.</param>
|
||||||
|
/// <param name="maxLength">The text input's maximum length.</param>
|
||||||
|
/// <param name="required">The text input's required value.</param>
|
||||||
|
[Obsolete("label is no longer supported", error: false)]
|
||||||
|
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? id = null
|
||||||
|
) : this(customId, style, placeholder, minLength, maxLength, required, value, id)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of a <see cref="TextInputBuilder"/>.
|
/// Creates a new instance of a <see cref="TextInputBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextInputBuilder()
|
public TextInputBuilder()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -174,7 +219,9 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public TextInputBuilder(TextInputComponent textInput)
|
public TextInputBuilder(TextInputComponent textInput)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
Label = textInput.Label;
|
Label = textInput.Label;
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
Style = textInput.Style;
|
Style = textInput.Style;
|
||||||
CustomId = textInput.CustomId;
|
CustomId = textInput.CustomId;
|
||||||
Placeholder = textInput.Placeholder;
|
Placeholder = textInput.Placeholder;
|
||||||
@@ -190,6 +237,7 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="label">The value to set.</param>
|
/// <param name="label">The value to set.</param>
|
||||||
/// <returns>The current builder. </returns>
|
/// <returns>The current builder. </returns>
|
||||||
|
[Obsolete("Label is no longer supported", error: false)]
|
||||||
public TextInputBuilder WithLabel(string label)
|
public TextInputBuilder WithLabel(string label)
|
||||||
{
|
{
|
||||||
Label = label;
|
Label = label;
|
||||||
@@ -273,16 +321,19 @@ public class TextInputBuilder : IInteractableComponentBuilder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMessageComponentBuilder.Build" />
|
||||||
public TextInputComponent Build()
|
public TextInputComponent Build()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(CustomId))
|
if (string.IsNullOrEmpty(CustomId))
|
||||||
throw new ArgumentException("TextInputComponents must have a custom id.", nameof(CustomId));
|
throw new ArgumentException("TextInputComponents must have a custom id.", nameof(CustomId));
|
||||||
if (string.IsNullOrWhiteSpace(Label))
|
|
||||||
throw new ArgumentException("TextInputComponents must have a label.", nameof(Label));
|
|
||||||
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));
|
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value, Id);
|
return new TextInputComponent(CustomId, Label, Placeholder, MinLength, MaxLength, Style, Required, Value, Id);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
|
|
||||||
IMessageComponent IMessageComponentBuilder.Build() => Build();
|
IMessageComponent IMessageComponentBuilder.Build() => Build();
|
||||||
|
|||||||
@@ -45,18 +45,49 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ChannelSelect = 8,
|
ChannelSelect = 8,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A container to display text alongside an accessory component.
|
||||||
|
/// </summary>
|
||||||
Section = 9,
|
Section = 9,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component displaying Markdown text.
|
||||||
|
/// </summary>
|
||||||
TextDisplay = 10,
|
TextDisplay = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A small image that can be used as an accessory.
|
||||||
|
/// </summary>
|
||||||
Thumbnail = 11,
|
Thumbnail = 11,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component displaying images and other media.
|
||||||
|
/// </summary>
|
||||||
MediaGallery = 12,
|
MediaGallery = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component displaying an attached file.
|
||||||
|
/// </summary>
|
||||||
File = 13,
|
File = 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component to add vertical padding between other components.
|
||||||
|
/// </summary>
|
||||||
Separator = 14,
|
Separator = 14,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A container that visually groups a set of components.
|
||||||
|
/// </summary>
|
||||||
Container = 17,
|
Container = 17,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A layout component that wraps modal components (text input, select menu or file upload) with a label and description.
|
||||||
|
/// </summary>
|
||||||
|
Label = 18,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component that allows users to upload files in modals.
|
||||||
|
/// </summary>
|
||||||
|
FileUpload = 19
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a component that allows users to upload files in modals.
|
||||||
|
/// </summary>
|
||||||
|
public class FileUploadComponent : IInteractableComponent
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ComponentType Type => ComponentType.FileUpload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of this component.
|
||||||
|
/// </summary>
|
||||||
|
public int? Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the custom ID of this component.
|
||||||
|
/// </summary>
|
||||||
|
public string CustomId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the minimum number of files a user must upload.
|
||||||
|
/// </summary>
|
||||||
|
public int? MinValues { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum number of files a user can upload.
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxValues { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this component requires a file upload to be submitted.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRequired { get; }
|
||||||
|
|
||||||
|
internal FileUploadComponent(int? id, string customId, int? minValues, int? maxValues, bool isRequired)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
CustomId = customId;
|
||||||
|
MinValues = minValues;
|
||||||
|
MaxValues = maxValues;
|
||||||
|
IsRequired = isRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IMessageComponent.ToBuilder"/>
|
||||||
|
public FileUploadComponentBuilder ToBuilder()
|
||||||
|
=> new(this);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IMessageComponentBuilder IMessageComponent.ToBuilder() => ToBuilder();
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ public interface IMessageComponent
|
|||||||
ComponentType Type { get; }
|
ComponentType Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets the id for the component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int? Id { get; }
|
int? Id { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
namespace Discord;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a layout component that wraps modal components (text input, select menu or file upload) with a label and description.
|
||||||
|
/// </summary>
|
||||||
|
public class LabelComponent : IMessageComponent
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ComponentType Type => ComponentType.Label;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int? Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the label text.
|
||||||
|
/// </summary>
|
||||||
|
public string Label { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description text for the label.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the component within the label.
|
||||||
|
/// </summary>
|
||||||
|
public IMessageComponent Component { get; }
|
||||||
|
|
||||||
|
internal LabelComponent(int? id, string label, string description, IMessageComponent component)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Label = label;
|
||||||
|
Description = description;
|
||||||
|
Component = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IMessageComponentBuilder ToBuilder()
|
||||||
|
=> new LabelBuilder(this);
|
||||||
|
}
|
||||||
@@ -49,15 +49,23 @@ public class TextInputComponent : IInteractableComponent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a <see cref="TextInputComponent"/> to a <see cref="TextInputBuilder"/>.
|
/// Converts a <see cref="TextInputComponent"/> to a <see cref="TextInputBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextInputBuilder ToBuilder()
|
public TextInputBuilder ToBuilder()
|
||||||
=> new TextInputBuilder(this);
|
=> new(this);
|
||||||
|
|
||||||
internal TextInputComponent(string customId, string label, string placeholder, int? minLength, int? maxLength,
|
internal TextInputComponent(
|
||||||
TextInputStyle style, bool? required, string value, int? id)
|
string customId,
|
||||||
|
string label,
|
||||||
|
string placeholder,
|
||||||
|
int? minLength,
|
||||||
|
int? maxLength,
|
||||||
|
TextInputStyle style,
|
||||||
|
bool? required,
|
||||||
|
string value,
|
||||||
|
int? id
|
||||||
|
)
|
||||||
{
|
{
|
||||||
CustomId = customId;
|
CustomId = customId;
|
||||||
Label = label;
|
Label = label;
|
||||||
|
|||||||
@@ -16,5 +16,30 @@ namespace Discord
|
|||||||
/// Gets the <see cref="Modal"/> components submitted by the user.
|
/// Gets the <see cref="Modal"/> components submitted by the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyCollection<IComponentInteractionData> Components { get; }
|
IReadOnlyCollection<IComponentInteractionData> Components { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the channels(s) of a <see cref="ComponentType.ChannelSelect"/> component within the modal.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IChannel> Channels { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user(s) of a <see cref="ComponentType.UserSelect"/> or <see cref="ComponentType.MentionableSelect"/> component within the modal.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IUser> Users { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the roles(s) of a <see cref="ComponentType.RoleSelect"/> or <see cref="ComponentType.MentionableSelect"/> component within the modal.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the guild member(s) of a <see cref="ComponentType.UserSelect"/> or <see cref="ComponentType.MentionableSelect"/> component within the modal.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IGuildUser> Members { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attachment(s) of a <see cref="ComponentType.FileUpload"/> component within the modal.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IAttachment> Attachments { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
|
/// Gets the custom id of the modal.
|
||||||
|
/// </summary>
|
||||||
public string CustomId { get; set; }
|
public string CustomId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -11,7 +12,13 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
private string _customId;
|
private string _customId;
|
||||||
|
|
||||||
public ModalBuilder() { }
|
/// <summary>
|
||||||
|
/// Creates a new and empty <see cref="ModalBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ModalBuilder()
|
||||||
|
{
|
||||||
|
Components = new();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="ModalBuilder"/>.
|
/// Creates a new instance of the <see cref="ModalBuilder"/>.
|
||||||
@@ -19,7 +26,6 @@ namespace Discord
|
|||||||
/// <param name="title">The modal's title.</param>
|
/// <param name="title">The modal's title.</param>
|
||||||
/// <param name="customId">The modal's customId.</param>
|
/// <param name="customId">The modal's customId.</param>
|
||||||
/// <param name="components">The modal's components.</param>
|
/// <param name="components">The modal's components.</param>
|
||||||
/// <exception cref="ArgumentException">Only TextInputComponents are allowed.</exception>
|
|
||||||
public ModalBuilder(string title, string customId, ModalComponentBuilder components = null)
|
public ModalBuilder(string title, string customId, ModalComponentBuilder components = null)
|
||||||
{
|
{
|
||||||
Title = title;
|
Title = title;
|
||||||
@@ -27,6 +33,28 @@ namespace Discord
|
|||||||
Components = components ?? new();
|
Components = components ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ModalBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">The modal's title.</param>
|
||||||
|
/// <param name="customId">The modal's customId.</param>
|
||||||
|
/// <param name="components">The modal's components.</param>
|
||||||
|
public ModalBuilder(string title, string customId, params IEnumerable<IMessageComponentBuilder> components)
|
||||||
|
: this(title, customId, new ModalComponentBuilder(components))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ModalBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">The modal's title.</param>
|
||||||
|
/// <param name="customId">The modal's customId.</param>
|
||||||
|
/// <param name="components">The modal's components.</param>
|
||||||
|
public ModalBuilder(string title, string customId, params IEnumerable<IMessageComponent> components)
|
||||||
|
: this(title, customId, new ModalComponentBuilder(components))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the title of the current modal.
|
/// Gets or sets the title of the current modal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -43,7 +71,7 @@ namespace Discord
|
|||||||
if (value is not null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
Preconditions.AtLeast(value.Length, 1, nameof(CustomId));
|
Preconditions.AtLeast(value.Length, 1, nameof(CustomId));
|
||||||
Preconditions.AtMost(value.Length, ComponentBuilder.MaxCustomIdLength, nameof(CustomId));
|
Preconditions.AtMost(value.Length, ModalComponentBuilder.MaxCustomIdLength, nameof(CustomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
_customId = value;
|
_customId = value;
|
||||||
@@ -53,7 +81,7 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the components of the current modal.
|
/// Gets or sets the components of the current modal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ModalComponentBuilder Components { get; set; } = new();
|
public ModalComponentBuilder Components { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the title of the current modal.
|
/// Sets the title of the current modal.
|
||||||
@@ -83,52 +111,227 @@ namespace Discord
|
|||||||
/// <param name="component">The component to add.</param>
|
/// <param name="component">The component to add.</param>
|
||||||
/// <param name="row">The row to add the text input.</param>
|
/// <param name="row">The row to add the text input.</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public ModalBuilder AddTextInput(TextInputBuilder component, int row = 0)
|
[Obsolete("Modal components no longer have rows", error: false)]
|
||||||
|
public ModalBuilder AddTextInput(TextInputBuilder component, int row)
|
||||||
{
|
{
|
||||||
Components.WithTextInput(component, row);
|
Components.WithTextInput(component, row);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc
|
||||||
/// Adds a <see cref="TextInputBuilder"/> to the current builder.
|
/// cref="ModalComponentBuilder.WithTextInput(string, string, TextInputStyle, string, int?, int?, int, bool?, string, int?, string, int?)"
|
||||||
/// </summary>
|
/// />
|
||||||
/// <param name="customId">The input's custom id.</param>
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
/// <param name="label">The input's label.</param>
|
public ModalBuilder AddTextInput(
|
||||||
/// <param name="placeholder">The input's placeholder text.</param>
|
string label,
|
||||||
/// <param name="minLength">The input's minimum length.</param>
|
string customId,
|
||||||
/// <param name="maxLength">The input's maximum length.</param>
|
TextInputStyle style = TextInputStyle.Short,
|
||||||
/// <param name="style">The input's style.</param>
|
string placeholder = null,
|
||||||
/// <returns>The current builder.</returns>
|
int? minLength = null,
|
||||||
public ModalBuilder AddTextInput(string label, string customId, TextInputStyle style = TextInputStyle.Short,
|
int? maxLength = null,
|
||||||
string placeholder = "", int? minLength = null, int? maxLength = null, bool? required = null, string value = null)
|
bool? required = null,
|
||||||
=> AddTextInput(new(label, customId, style, placeholder, minLength, maxLength, required, value));
|
string value = null,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithTextInput(
|
||||||
|
label, customId, style, placeholder, minLength, maxLength, 0, required, value, id, description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithLabel(LabelBuilder)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddLabel(LabelBuilder label)
|
||||||
|
{
|
||||||
|
Components.WithLabel(label);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithLabel(string, IMessageComponentBuilder, string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddLabel(
|
||||||
|
string label,
|
||||||
|
IMessageComponentBuilder component,
|
||||||
|
string description = null,
|
||||||
|
int? id = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithLabel(label, component, description, id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithSelectMenu(string, string, List{SelectMenuOptionBuilder}, string, int, int, bool, ComponentType, ChannelType[], int?, string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddSelectMenu(
|
||||||
|
string label,
|
||||||
|
string customId,
|
||||||
|
List<SelectMenuOptionBuilder> options = null,
|
||||||
|
string placeholder = null,
|
||||||
|
int minValues = 1,
|
||||||
|
int maxValues = 1,
|
||||||
|
bool disabled = false,
|
||||||
|
ComponentType type = ComponentType.SelectMenu,
|
||||||
|
ChannelType[] channelTypes = null,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithSelectMenu(
|
||||||
|
label,
|
||||||
|
customId,
|
||||||
|
options,
|
||||||
|
placeholder,
|
||||||
|
minValues,
|
||||||
|
maxValues,
|
||||||
|
disabled,
|
||||||
|
type,
|
||||||
|
channelTypes,
|
||||||
|
id,
|
||||||
|
description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithSelectMenu(string, SelectMenuBuilder, string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddSelectMenu(
|
||||||
|
string label,
|
||||||
|
SelectMenuBuilder menu,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithSelectMenu(label, menu, description, labelId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithFileUpload(string, FileUploadComponentBuilder, string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddFileUpload(
|
||||||
|
string label,
|
||||||
|
FileUploadComponentBuilder fileUpload,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithFileUpload(label, fileUpload, description, labelId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithFileUpload(string, string, int?, int?, bool, int?, string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddFileUpload(
|
||||||
|
string label,
|
||||||
|
string customId,
|
||||||
|
int? minValues = null,
|
||||||
|
int? maxValues = null,
|
||||||
|
bool isRequired = true,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Components.WithFileUpload(label, customId, minValues, maxValues, isRequired, id, description, labelId);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithTextDisplay(TextDisplayBuilder)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddTextDisplay(TextDisplayBuilder textDisplay)
|
||||||
|
{
|
||||||
|
Components.WithTextDisplay(textDisplay);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ModalComponentBuilder.WithTextDisplay(string, int?)"/>
|
||||||
|
/// <returns>The current <see cref="ModalBuilder"/>.</returns>
|
||||||
|
public ModalBuilder AddTextDisplay(string content, int? id = null)
|
||||||
|
{
|
||||||
|
Components.WithTextDisplay(content, id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds multiple components to the current builder.
|
/// Adds multiple components to the current builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="components">The components to add.</param>
|
/// <param name="components">The components to add.</param>
|
||||||
/// <returns>The current builder</returns>
|
/// <returns>The current builder</returns>
|
||||||
|
[Obsolete("Modal components no longer have rows", error: false)]
|
||||||
public ModalBuilder AddComponents(List<IMessageComponent> components, int row)
|
public ModalBuilder AddComponents(List<IMessageComponent> components, int row)
|
||||||
{
|
{
|
||||||
components.ForEach(x => Components.AddComponent(x, row));
|
components.ForEach(x => Components.AddComponent(x, row));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple components to the current builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="components">The components to add.</param>
|
||||||
|
/// <returns>The current builder</returns>
|
||||||
|
public ModalBuilder AddComponents(params IEnumerable<IMessageComponentBuilder> components)
|
||||||
|
{
|
||||||
|
Components.With(components);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="IInteractableComponentBuilder"/> by the specified <paramref name="customId"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="customId">
|
||||||
|
/// The <see cref="IInteractableComponentBuilder.CustomId"/> of the component to get.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The component that was found, <see langword="null"/> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public IInteractableComponentBuilder GetComponent(string customId) =>
|
||||||
|
GetComponent<IInteractableComponentBuilder>(customId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <typeparamref name="TMessageComponentBuilder"/> by the specified <paramref name="customId"/>.
|
/// Gets a <typeparamref name="TMessageComponentBuilder"/> by the specified <paramref name="customId"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TMessageComponentBuilder">The type of the component to get.</typeparam>
|
/// <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>
|
/// <param name="customId">
|
||||||
|
/// The <see cref="IInteractableComponentBuilder.CustomId"/> of the component to get.
|
||||||
|
/// </param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// The component of type <typeparamref name="TMessageComponentBuilder"/> that was found, <see langword="null"/> otherwise.
|
/// The component of type <typeparamref name="TMessageComponentBuilder"/> that was found,
|
||||||
|
/// <see langword="null"/> otherwise.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public TMessageComponentBuilder GetComponent<TMessageComponentBuilder>(string customId)
|
public TMessageComponentBuilder GetComponent<TMessageComponentBuilder>(string customId)
|
||||||
where TMessageComponentBuilder : class, IInteractableComponentBuilder
|
where TMessageComponentBuilder : class, IInteractableComponentBuilder
|
||||||
{
|
{
|
||||||
Preconditions.NotNull(customId, nameof(customId));
|
Preconditions.NotNull(customId, nameof(customId));
|
||||||
|
|
||||||
return Components.ActionRows?.SelectMany(r => r.Components.OfType<TMessageComponentBuilder>())
|
var components = Components.SelectMany(ExtractComponent);
|
||||||
.FirstOrDefault(c => c.CustomId == customId);
|
|
||||||
|
// optimization: no need for the of type call if we're checking the root type.
|
||||||
|
if (typeof(TMessageComponentBuilder) != typeof(IInteractableComponentBuilder))
|
||||||
|
components = components.OfType<TMessageComponentBuilder>();
|
||||||
|
|
||||||
|
return (TMessageComponentBuilder)components.FirstOrDefault(x => x.CustomId == customId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used to extract depth=1 components from the modal. Allows for the same behaviour of the previous
|
||||||
|
* iteration of the builder, whilst adding support for label components.
|
||||||
|
*
|
||||||
|
* This is not a long-term solution, and can break if more component types are added or nesting is changed.
|
||||||
|
*/
|
||||||
|
static IEnumerable<IInteractableComponentBuilder> ExtractComponent(IMessageComponentBuilder builder)
|
||||||
|
=> builder switch
|
||||||
|
{
|
||||||
|
LabelBuilder { Component: IInteractableComponentBuilder target } => [target],
|
||||||
|
ActionRowBuilder { Components: { } components }
|
||||||
|
=> components.OfType<IInteractableComponentBuilder>(),
|
||||||
|
_ => []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -144,25 +347,21 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
Preconditions.NotNull(customId, nameof(customId));
|
Preconditions.NotNull(customId, 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 component = GetComponent<TextInputBuilder>(customId) ?? throw new ArgumentException(
|
||||||
var row = Components.ActionRows.First(r => r.Components.Contains(component));
|
$"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.",
|
||||||
|
nameof(customId));
|
||||||
|
|
||||||
var builder = new TextInputBuilder
|
/*
|
||||||
{
|
* We can just update the instance in-place, we don't need to update the parent here.
|
||||||
Label = component.Label,
|
*
|
||||||
CustomId = component.CustomId,
|
* NOTE:
|
||||||
Style = component.Style,
|
* this does change the behaviour of this function, since in the previous iteration, we would've removed
|
||||||
Placeholder = component.Placeholder,
|
* and re-added the component to/from the row, which has the inverse effect of sliding it to the end of the
|
||||||
MinLength = component.MinLength,
|
* row. With this change, we no longer update the position within the row, but I think the position
|
||||||
MaxLength = component.MaxLength,
|
* shifting was an unintended side effect- and therefor a bug.
|
||||||
Required = component.Required,
|
*/
|
||||||
Value = component.Value
|
|
||||||
};
|
|
||||||
|
|
||||||
updateTextInput(builder);
|
updateTextInput(component);
|
||||||
|
|
||||||
row.Components.Remove(component);
|
|
||||||
row.AddComponent(builder);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -188,7 +387,35 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
Preconditions.NotNull(customId, nameof(customId));
|
Preconditions.NotNull(customId, nameof(customId));
|
||||||
|
|
||||||
Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c is IInteractableComponentBuilder ic && ic.CustomId == customId));
|
/*
|
||||||
|
* This function actually removed any component with the provided custom id, and could remove
|
||||||
|
* more than one. To keep this behaviour, the below code attempts to do the same.
|
||||||
|
*
|
||||||
|
* For reference, this was the old implementation
|
||||||
|
* Components.ActionRows?.ForEach(r => r
|
||||||
|
* .Components
|
||||||
|
* .RemoveAll(c => c is IInteractableComponentBuilder ic && ic.CustomId == customId)
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach (var parent in Components.ToArray())
|
||||||
|
{
|
||||||
|
switch (parent)
|
||||||
|
{
|
||||||
|
case LabelBuilder { Component: IInteractableComponentBuilder target } label
|
||||||
|
when target.CustomId == customId:
|
||||||
|
// you cannot have a label without a component, so we actually remove the label here
|
||||||
|
Components.Remove(label);
|
||||||
|
break;
|
||||||
|
case ActionRowBuilder row:
|
||||||
|
row.Components.RemoveAll(x =>
|
||||||
|
x is IInteractableComponentBuilder ic &&
|
||||||
|
ic.CustomId == customId
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +426,11 @@ namespace Discord
|
|||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public ModalBuilder RemoveComponentsOfType(ComponentType type)
|
public ModalBuilder RemoveComponentsOfType(ComponentType type)
|
||||||
{
|
{
|
||||||
Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c.Type == type));
|
foreach (var component in Components.ToArray())
|
||||||
|
{
|
||||||
|
if (component.Type == type) Components.Remove(component);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +447,6 @@ namespace Discord
|
|||||||
throw new ArgumentException("Modals must have a custom ID.", nameof(CustomId));
|
throw new ArgumentException("Modals must have a custom ID.", nameof(CustomId));
|
||||||
if (string.IsNullOrWhiteSpace(Title))
|
if (string.IsNullOrWhiteSpace(Title))
|
||||||
throw new ArgumentException("Modals must have a title.", nameof(Title));
|
throw new ArgumentException("Modals must have a title.", nameof(Title));
|
||||||
if (Components.ActionRows?.SelectMany(r => r.Components).Any(c => c.Type != ComponentType.TextInput) ?? false)
|
|
||||||
throw new ArgumentException($"Only components of type {nameof(TextInputComponent)} are allowed.", nameof(Components));
|
|
||||||
|
|
||||||
return new(Title, CustomId, Components.Build());
|
return new(Title, CustomId, Components.Build());
|
||||||
}
|
}
|
||||||
@@ -226,7 +455,7 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a builder for creating a <see cref="ModalComponent"/>.
|
/// Represents a builder for creating a <see cref="ModalComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ModalComponentBuilder
|
public class ModalComponentBuilder : IList<IMessageComponentBuilder>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The max length of a <see cref="IInteractableComponent.CustomId"/>.
|
/// The max length of a <see cref="IInteractableComponent.CustomId"/>.
|
||||||
@@ -236,126 +465,448 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The max amount of rows a <see cref="ModalComponent"/> can have.
|
/// The max amount of rows a <see cref="ModalComponent"/> can have.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Modal components no longer support action rows", error: true)]
|
||||||
public const int MaxActionRowCount = 5;
|
public const int MaxActionRowCount = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Action Rows for this Component Builder.
|
/// Gets the number of components in the builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="ArgumentNullException" accessor="set"><see cref="ActionRows"/> cannot be null.</exception>
|
public int Count => _components.Count;
|
||||||
/// <exception cref="ArgumentException" accessor="set"><see cref="ActionRows"/> count exceeds <see cref="MaxActionRowCount"/>.</exception>
|
|
||||||
public List<ActionRowBuilder> ActionRows
|
/// <summary>
|
||||||
|
/// Gets or sets the component at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the component to get or set</param>
|
||||||
|
public IMessageComponentBuilder this[int index]
|
||||||
{
|
{
|
||||||
get => _actionRows;
|
get => _components[index];
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == null)
|
ValidateComponentBuilder(value);
|
||||||
throw new ArgumentNullException(nameof(value), $"{nameof(ActionRows)} cannot be null.");
|
_components[index] = value;
|
||||||
if (value.Count > MaxActionRowCount)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value), $"Action row count must be less than or equal to {MaxActionRowCount}.");
|
|
||||||
_actionRows = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ActionRowBuilder> _actionRows;
|
private readonly List<IMessageComponentBuilder> _components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs an empty <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ModalComponentBuilder()
|
||||||
|
{
|
||||||
|
_components = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a <see cref="ModalComponentBuilder"/> with the provided
|
||||||
|
/// <see cref="IMessageComponentBuilder"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="components">The components to add to this <see cref="ModalComponentBuilder"/></param>
|
||||||
|
public ModalComponentBuilder(params IEnumerable<IMessageComponentBuilder> components) : this()
|
||||||
|
{
|
||||||
|
foreach (var component in components)
|
||||||
|
{
|
||||||
|
Add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a <see cref="ModalComponentBuilder"/> with the provided
|
||||||
|
/// <see cref="IMessageComponent"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="components">The components to add to this <see cref="ModalComponentBuilder"/></param>
|
||||||
|
public ModalComponentBuilder(params IEnumerable<IMessageComponent> components) : this()
|
||||||
|
{
|
||||||
|
foreach (var component in components)
|
||||||
|
{
|
||||||
|
Add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateComponentBuilder(IMessageComponentBuilder builder)
|
||||||
|
{
|
||||||
|
if (builder is not LabelBuilder and not ActionRowBuilder and not TextDisplayBuilder)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Only top-level modal components (labels, action rows or text displays) are allowed, not {builder.GetType().Name}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new builder from the provided list of components.
|
/// Creates a new builder from the provided list of components.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="components">The components to create the builder from.</param>
|
/// <param name="components">The components to create the builder from.</param>
|
||||||
/// <returns>The newly created builder.</returns>
|
/// <returns>The newly created builder.</returns>
|
||||||
public static ComponentBuilder FromComponents(IReadOnlyCollection<IMessageComponent> components)
|
public static ModalComponentBuilder FromComponents(params IEnumerable<IMessageComponent> components)
|
||||||
{
|
{
|
||||||
var builder = new ComponentBuilder();
|
var builder = new ModalComponentBuilder();
|
||||||
for (int i = 0; i != components.Count; i++)
|
|
||||||
{
|
foreach (var component in components)
|
||||||
var component = components.ElementAt(i);
|
builder.Add(component);
|
||||||
builder.AddComponent(component, i);
|
|
||||||
}
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddComponent(IMessageComponent component, int row)
|
[Obsolete("Modal components no longer have rows", error: true)]
|
||||||
|
internal ModalComponentBuilder AddComponent(IMessageComponent component, int row)
|
||||||
|
=> Add(component);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a component to this <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component">The component to add.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder Add(IMessageComponent component)
|
||||||
|
=> Add(component.ToBuilder());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a component to this <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component">The component to add.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder Add(IMessageComponentBuilder component)
|
||||||
{
|
{
|
||||||
switch (component)
|
ValidateComponentBuilder(component);
|
||||||
{
|
|
||||||
case TextInputComponent text:
|
_components.Add(component);
|
||||||
WithTextInput(text.Label, text.CustomId, text.Style, text.Placeholder, text.MinLength, text.MaxLength, row);
|
return this;
|
||||||
break;
|
|
||||||
case ActionRowComponent actionRow:
|
|
||||||
foreach (var cmp in actionRow.Components)
|
|
||||||
AddComponent(cmp, row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a <see cref="TextInputBuilder"/> to the <see cref="ComponentBuilder"/> at the specific row.
|
/// Sets the components in this builder to the provided <paramref name="components"/>
|
||||||
/// If the row cannot accept the component then it will add it to a row that can.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="customId">The input's custom id.</param>
|
/// <param name="components">The components to set this builder to.</param>
|
||||||
/// <param name="label">The input's label.</param>
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
/// <param name="placeholder">The input's placeholder text.</param>
|
public ModalComponentBuilder With(params IEnumerable<IMessageComponentBuilder> components)
|
||||||
/// <param name="minLength">The input's minimum length.</param>
|
{
|
||||||
/// <param name="maxLength">The input's maximum length.</param>
|
_components.Clear();
|
||||||
/// <param name="style">The input's style.</param>
|
|
||||||
/// <returns>The current builder.</returns>
|
|
||||||
public ModalComponentBuilder WithTextInput(string label, string customId, TextInputStyle style = TextInputStyle.Short,
|
|
||||||
string placeholder = null, int? minLength = null, int? maxLength = null, int row = 0, bool? required = null,
|
|
||||||
string value = null)
|
|
||||||
=> WithTextInput(new(label, customId, style, placeholder, minLength, maxLength, required, value), row);
|
|
||||||
|
|
||||||
/// <summary>
|
foreach (var component in components)
|
||||||
/// Adds a <see cref="TextInputBuilder"/> to the <see cref="ModalComponentBuilder"/> at the specific row.
|
Add(component);
|
||||||
/// If the row cannot accept the component then it will add it to a row that can.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">The <see cref="TextInputBuilder"/> to add.</param>
|
|
||||||
/// <param name="row">The row to add the text input.</param>
|
|
||||||
/// <exception cref="InvalidOperationException">There are no more rows to add a text input to.</exception>
|
|
||||||
/// <exception cref="ArgumentException"><paramref name="row"/> must be less than <see cref="MaxActionRowCount"/>.</exception>
|
|
||||||
/// <returns>The current builder.</returns>
|
|
||||||
public ModalComponentBuilder WithTextInput(TextInputBuilder text, int row = 0)
|
|
||||||
{
|
|
||||||
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
|
|
||||||
|
|
||||||
if (_actionRows == null)
|
|
||||||
{
|
|
||||||
_actionRows = new List<ActionRowBuilder>
|
|
||||||
{
|
|
||||||
new ActionRowBuilder().AddComponent(text)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_actionRows.Count == row)
|
|
||||||
_actionRows.Add(new ActionRowBuilder().AddComponent(text));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActionRowBuilder actionRow;
|
|
||||||
if (_actionRows.Count > row)
|
|
||||||
actionRow = _actionRows.ElementAt(row);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actionRow = new ActionRowBuilder();
|
|
||||||
_actionRows.Add(actionRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionRow.CanTakeComponent(text))
|
|
||||||
actionRow.AddComponent(text);
|
|
||||||
else if (row < MaxActionRowCount)
|
|
||||||
WithTextInput(text, row + 1);
|
|
||||||
else
|
|
||||||
throw new InvalidOperationException($"There are no more rows to add {nameof(text)} to.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="LabelBuilder"/> to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The <see cref="LabelBuilder"/> to add.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithLabel(LabelBuilder label)
|
||||||
|
=> Add(label);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label of the <see cref="LabelBuilder"/>.</param>
|
||||||
|
/// <param name="component">The component of the <see cref="LabelBuilder"/>.</param>
|
||||||
|
/// <param name="description">The description of the <see cref="LabelBuilder"/>.</param>
|
||||||
|
/// <param name="id">The id of the <see cref="LabelBuilder"/>.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithLabel(
|
||||||
|
string label,
|
||||||
|
IMessageComponentBuilder component,
|
||||||
|
string description = null,
|
||||||
|
int? id = null
|
||||||
|
) => WithLabel(new(
|
||||||
|
label,
|
||||||
|
component,
|
||||||
|
description,
|
||||||
|
id
|
||||||
|
));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> containing a <see cref="SelectMenuBuilder"/> to the
|
||||||
|
/// current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="customId">The custom id of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="options">The options of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="placeholder">The placeholder of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="minValues">The min values of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="maxValues">The max values of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="disabled">Whether the <see cref="SelectMenuBuilder"/> is disabled.</param>
|
||||||
|
/// <param name="type">The type of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="channelTypes">The channel types of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="id">The id of the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithSelectMenu(
|
||||||
|
string label,
|
||||||
|
string customId,
|
||||||
|
List<SelectMenuOptionBuilder> options = null,
|
||||||
|
string placeholder = null,
|
||||||
|
int minValues = 1,
|
||||||
|
int maxValues = 1,
|
||||||
|
bool disabled = false,
|
||||||
|
ComponentType type = ComponentType.SelectMenu,
|
||||||
|
ChannelType[] channelTypes = null,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
) => WithSelectMenu(
|
||||||
|
label,
|
||||||
|
new SelectMenuBuilder()
|
||||||
|
.WithId(id)
|
||||||
|
.WithCustomId(customId)
|
||||||
|
.WithOptions(options)
|
||||||
|
.WithPlaceholder(placeholder)
|
||||||
|
.WithMaxValues(maxValues)
|
||||||
|
.WithMinValues(minValues)
|
||||||
|
.WithDisabled(disabled)
|
||||||
|
.WithType(type)
|
||||||
|
.WithChannelTypes(channelTypes),
|
||||||
|
description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with the provided <see cref="SelectMenuBuilder"/> to
|
||||||
|
/// the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="menu">The menu to add.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithSelectMenu(
|
||||||
|
string label,
|
||||||
|
SelectMenuBuilder menu,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
|
||||||
|
return WithLabel(
|
||||||
|
label,
|
||||||
|
menu,
|
||||||
|
description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with the provided
|
||||||
|
/// <see cref="FileUploadComponentBuilder"/> to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="fileUpload">The file upload to add.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithFileUpload(
|
||||||
|
string label,
|
||||||
|
FileUploadComponentBuilder fileUpload,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
) => WithLabel(label, fileUpload, description, labelId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with a <see cref="FileUploadComponentBuilder"/>
|
||||||
|
/// to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="customId">The custom id of the <see cref="FileUploadComponentBuilder"/>.</param>
|
||||||
|
/// <param name="minValues">The min values of the <see cref="FileUploadComponentBuilder"/>.</param>
|
||||||
|
/// <param name="maxValues">The max values of the <see cref="FileUploadComponentBuilder"/>.</param>
|
||||||
|
/// <param name="isRequired">Whether the <see cref="FileUploadComponentBuilder"/> is required.</param>
|
||||||
|
/// <param name="id">The id of the <see cref="FileUploadComponentBuilder"/>.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithFileUpload(
|
||||||
|
string label,
|
||||||
|
string customId,
|
||||||
|
int? minValues = null,
|
||||||
|
int? maxValues = null,
|
||||||
|
bool isRequired = true,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
) => WithLabel(
|
||||||
|
label,
|
||||||
|
new FileUploadComponentBuilder(
|
||||||
|
customId,
|
||||||
|
minValues,
|
||||||
|
maxValues,
|
||||||
|
isRequired,
|
||||||
|
id
|
||||||
|
),
|
||||||
|
description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="TextDisplayBuilder"/> to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textDisplay">The <see cref="TextDisplayBuilder"/> to add.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithTextDisplay(TextDisplayBuilder textDisplay)
|
||||||
|
=> Add(textDisplay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="TextDisplayBuilder"/> to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">The content of the <see cref="TextDisplayBuilder"/>.</param>
|
||||||
|
/// <param name="id">The id of the <see cref="TextDisplayBuilder"/>.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithTextDisplay(string content, int? id = null)
|
||||||
|
=> WithTextDisplay(new TextDisplayBuilder(content, id));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with the provided <see cref="TextInputBuilder"/> to
|
||||||
|
/// the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="textInput">The text input to add.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithTextInput(
|
||||||
|
string label,
|
||||||
|
TextInputBuilder textInput,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
) => WithLabel(label, textInput, description, labelId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with the provided <see cref="TextInputBuilder"/> to
|
||||||
|
/// the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text input to add.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
[Obsolete("text components must be wrapped in a label", error: false)]
|
||||||
|
public ModalComponentBuilder WithTextInput(TextInputBuilder text)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
if (text.Label is null)
|
||||||
|
{
|
||||||
|
// TODO: better explain
|
||||||
|
throw new ArgumentNullException(
|
||||||
|
nameof(text),
|
||||||
|
"Label cannot be null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WithLabel(
|
||||||
|
text.Label,
|
||||||
|
text
|
||||||
|
);
|
||||||
|
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with the provided <see cref="TextInputBuilder"/> to
|
||||||
|
/// the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text input to add.</param>
|
||||||
|
/// <param name="row">The row to add the text input to.</param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
[Obsolete("Modal components no longer have rows", error: false)]
|
||||||
|
public ModalComponentBuilder WithTextInput(TextInputBuilder text, int row)
|
||||||
|
=> WithTextInput(text);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs and adds a <see cref="LabelBuilder"/> with a <see cref="TextInputBuilder"/>
|
||||||
|
/// to the current <see cref="ModalComponentBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="label">The label around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="customId">The custom id of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="style">The style of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="placeholder">The placeholder of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="minLength">The min length of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="maxLength">The max length of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="row"><b>DEPRECATED:</b> The row to place the <see cref="TextInputBuilder"/> on.</param>
|
||||||
|
/// <param name="required">Whether the <see cref="TextInputBuilder"/> is required.</param>
|
||||||
|
/// <param name="value">The value of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="id">The id of the <see cref="TextInputBuilder"/>.</param>
|
||||||
|
/// <param name="description">The description around the <see cref="SelectMenuBuilder"/>.</param>
|
||||||
|
/// <param name="labelId">
|
||||||
|
/// The id of the <see cref="LabelBuilder"/> wrapping the <see cref="SelectMenuBuilder"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The current <see cref="ModalComponentBuilder"/>.</returns>
|
||||||
|
public ModalComponentBuilder WithTextInput(
|
||||||
|
string label,
|
||||||
|
string customId,
|
||||||
|
TextInputStyle style = TextInputStyle.Short,
|
||||||
|
string placeholder = null,
|
||||||
|
int? minLength = null,
|
||||||
|
int? maxLength = null,
|
||||||
|
int row = 0,
|
||||||
|
bool? required = null,
|
||||||
|
string value = null,
|
||||||
|
int? id = null,
|
||||||
|
string description = null,
|
||||||
|
int? labelId = null
|
||||||
|
) => WithLabel(
|
||||||
|
label,
|
||||||
|
new TextInputBuilder(
|
||||||
|
customId,
|
||||||
|
style,
|
||||||
|
placeholder,
|
||||||
|
minLength,
|
||||||
|
maxLength,
|
||||||
|
required,
|
||||||
|
value,
|
||||||
|
id
|
||||||
|
),
|
||||||
|
description,
|
||||||
|
labelId
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
void ICollection<IMessageComponentBuilder>.Add(IMessageComponentBuilder item) => Add(item);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Clear() => _components.Clear();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Contains(IMessageComponentBuilder item) => _components.Contains(item);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(IMessageComponentBuilder[] array, int arrayIndex) => _components.CopyTo(array, arrayIndex);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Remove(IMessageComponentBuilder item) => _components.Remove(item);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int IndexOf(IMessageComponentBuilder item) => _components.IndexOf(item);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Insert(int index, IMessageComponentBuilder item)
|
||||||
|
{
|
||||||
|
ValidateComponentBuilder(item);
|
||||||
|
|
||||||
|
_components.Insert(index, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void RemoveAt(int index) => _components.RemoveAt(index);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<IMessageComponentBuilder> GetEnumerator() => _components.GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a <see cref="ModalComponent"/> representing the builder.
|
/// Get a <see cref="ModalComponent"/> representing the builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="ModalComponent"/> representing the builder.</returns>
|
/// <returns>A <see cref="ModalComponent"/> representing the builder.</returns>
|
||||||
public ModalComponent Build()
|
public ModalComponent Build()
|
||||||
=> new(ActionRows?.Select(x => x.Build()).ToList());
|
=> new(_components.Select(x => x.Build()).ToList());
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_components).GetEnumerator();
|
||||||
|
bool ICollection<IMessageComponentBuilder>.IsReadOnly => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ namespace Discord
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the components to be used in a modal.
|
/// Gets the components to be used in a modal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<ActionRowComponent> Components { get; }
|
public IReadOnlyCollection<IMessageComponent> Components { get; }
|
||||||
|
|
||||||
internal ModalComponent(List<ActionRowComponent> components)
|
internal ModalComponent(List<IMessageComponent> components)
|
||||||
{
|
{
|
||||||
Components = components;
|
Components = components;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Discord.Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
||||||
/// <param name="interaction">The interaction to respond to.</param>
|
/// <param name="interaction">The interaction to respond to.</param>
|
||||||
|
/// <param name="customId">The custom id of the modal.</param>
|
||||||
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
||||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
@@ -31,10 +32,11 @@ namespace Discord.Interactions
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
||||||
/// <param name="interaction">The interaction to respond to.</param>
|
/// <param name="interaction">The interaction to respond to.</param>
|
||||||
|
/// <param name="customId">The custom id of the modal.</param>
|
||||||
/// <param name="interactionService">Interaction service instance that should be used to build <see cref="ModalInfo"/>s.</param>
|
/// <param name="interactionService">Interaction service instance that should be used to build <see cref="ModalInfo"/>s.</param>
|
||||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
||||||
/// <returns></returns>
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
public static Task RespondWithModalAsync<T>(this IDiscordInteraction interaction, string customId, InteractionService interactionService,
|
public static Task RespondWithModalAsync<T>(this IDiscordInteraction interaction, string customId, InteractionService interactionService,
|
||||||
RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
|
RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
|
||||||
where T : class, IModal
|
where T : class, IModal
|
||||||
@@ -50,10 +52,11 @@ namespace Discord.Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
||||||
/// <param name="interaction">The interaction to respond to.</param>
|
/// <param name="interaction">The interaction to respond to.</param>
|
||||||
|
/// <param name="customId">The custom id of the modal.</param>
|
||||||
/// <param name="modal">The <see cref="IModal"/> instance to get field values from.</param>
|
/// <param name="modal">The <see cref="IModal"/> instance to get field values from.</param>
|
||||||
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
||||||
/// <returns></returns>
|
/// <returns>A task that represents the asynchronous operation of responding to the interaction.</returns>
|
||||||
public static Task RespondWithModalAsync<T>(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null,
|
public static Task RespondWithModalAsync<T>(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null,
|
||||||
Action<ModalBuilder> modifyModal = null)
|
Action<ModalBuilder> modifyModal = null)
|
||||||
where T : class, IModal
|
where T : class, IModal
|
||||||
@@ -81,8 +84,7 @@ namespace Discord.Interactions
|
|||||||
throw new InvalidOperationException($"{input.GetType().FullName} isn't a valid component info class");
|
throw new InvalidOperationException($"{input.GetType().FullName} isn't a valid component info class");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modifyModal is not null)
|
modifyModal?.Invoke(builder);
|
||||||
modifyModal(builder);
|
|
||||||
|
|
||||||
return interaction.RespondWithModalAsync(builder.Build(), options);
|
return interaction.RespondWithModalAsync(builder.Build(), options);
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/Discord.Net.Rest/API/Common/FileUploadComponent.cs
Normal file
43
src/Discord.Net.Rest/API/Common/FileUploadComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class FileUploadComponent : IInteractableComponent
|
||||||
|
{
|
||||||
|
[JsonProperty("type")]
|
||||||
|
public ComponentType Type { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public Optional<int> Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("custom_id")]
|
||||||
|
public string CustomId { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("min_values")]
|
||||||
|
public Optional<int> MinValues { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("max_values")]
|
||||||
|
public Optional<int> MaxValues { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("required")]
|
||||||
|
public Optional<bool> IsRequired { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("values")]
|
||||||
|
public Optional<string[]> Values { get; set; }
|
||||||
|
|
||||||
|
public FileUploadComponent() {}
|
||||||
|
|
||||||
|
public FileUploadComponent(Discord.FileUploadComponent component)
|
||||||
|
{
|
||||||
|
Type = component.Type;
|
||||||
|
Id = component.Id ?? Optional<int>.Unspecified;
|
||||||
|
CustomId = component.CustomId;
|
||||||
|
MinValues = component.MinValues ?? Optional<int>.Unspecified;
|
||||||
|
MaxValues = component.MaxValues ?? Optional<int>.Unspecified;
|
||||||
|
IsRequired = component.IsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
int? IMessageComponent.Id => Id.ToNullable();
|
||||||
|
IMessageComponentBuilder IMessageComponent.ToBuilder() => null;
|
||||||
|
}
|
||||||
38
src/Discord.Net.Rest/API/Common/LabelComponent.cs
Normal file
38
src/Discord.Net.Rest/API/Common/LabelComponent.cs
Normal file
@@ -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<int> 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<int>.Unspecified;
|
||||||
|
Label = label.Label;
|
||||||
|
Description = label.Description;
|
||||||
|
Component = label.Component.ToModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMessageComponentBuilder ToBuilder() => null;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
int? IMessageComponent.Id => Id.ToNullable();
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ namespace Discord.API
|
|||||||
public string CustomId { get; set; }
|
public string CustomId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("components")]
|
[JsonProperty("components")]
|
||||||
public API.ActionRowComponent[] Components { get; set; }
|
public IMessageComponent[] Components { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("resolved")]
|
||||||
|
public Optional<ModalInteractionDataResolved> Resolved { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class ModalInteractionDataResolved
|
||||||
|
{
|
||||||
|
[JsonProperty("users")]
|
||||||
|
public Optional<Dictionary<string, User>> Users { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("members")]
|
||||||
|
public Optional<Dictionary<string, GuildMember>> Members { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("roles")]
|
||||||
|
public Optional<Dictionary<string, Role>> Roles { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("channels")]
|
||||||
|
public Optional<Dictionary<string, Channel>> Channels { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("attachments")]
|
||||||
|
public Optional<Dictionary<string, Attachment>> Attachments { get; set; }
|
||||||
|
}
|
||||||
@@ -16,8 +16,9 @@ namespace Discord.API
|
|||||||
[JsonProperty("custom_id")]
|
[JsonProperty("custom_id")]
|
||||||
public string CustomId { get; set; }
|
public string CustomId { get; set; }
|
||||||
|
|
||||||
|
// deprecated
|
||||||
[JsonProperty("label")]
|
[JsonProperty("label")]
|
||||||
public string Label { get; set; }
|
public Optional<string> Label { get; set; }
|
||||||
|
|
||||||
[JsonProperty("placeholder")]
|
[JsonProperty("placeholder")]
|
||||||
public Optional<string> Placeholder { get; set; }
|
public Optional<string> Placeholder { get; set; }
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
CustomId = modal.CustomId,
|
CustomId = modal.CustomId,
|
||||||
Title = modal.Title,
|
Title = modal.Title,
|
||||||
Components = modal.Component.Components.Select(x => new Discord.API.ActionRowComponent(x)).ToArray()
|
Components = modal.Component.Components.Select(x => x.ToModel()).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
CustomId = modal.CustomId,
|
CustomId = modal.CustomId,
|
||||||
Title = modal.Title,
|
Title = modal.Title,
|
||||||
Components = modal.Component.Components.Select(x => new Discord.API.ActionRowComponent(x)).ToArray()
|
Components = modal.Component.Components.Select(x => x.ToModel()).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ namespace Discord.Rest
|
|||||||
Type = component.Type;
|
Type = component.Type;
|
||||||
|
|
||||||
if (component is API.TextInputComponent textInput)
|
if (component is API.TextInputComponent textInput)
|
||||||
Value = textInput.Value.Value;
|
Value = textInput.Value.GetValueOrDefault();
|
||||||
|
|
||||||
if (component is API.SelectMenuComponent select)
|
if (component is API.SelectMenuComponent select)
|
||||||
{
|
{
|
||||||
@@ -129,6 +129,11 @@ namespace Discord.Rest
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (component is API.FileUploadComponent fileUpload)
|
||||||
|
{
|
||||||
|
Values = fileUpload.Values.GetValueOrDefault(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Model = Discord.API.ModalInteractionData;
|
using Model = Discord.API.ModalInteractionData;
|
||||||
|
|
||||||
@@ -17,15 +18,84 @@ namespace Discord.Rest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<RestMessageComponentData> Components { get; }
|
public IReadOnlyCollection<RestMessageComponentData> Components { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Channels"/>
|
||||||
|
public IReadOnlyCollection<RestChannel> Channels { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Users"/>
|
||||||
|
public IReadOnlyCollection<RestUser> Users { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Roles"/>
|
||||||
|
public IReadOnlyCollection<RestRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Members"/>
|
||||||
|
public IReadOnlyCollection<RestGuildUser> Members { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Attachments"/>
|
||||||
|
public IReadOnlyCollection<IAttachment> Attachments { get; }
|
||||||
|
|
||||||
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IChannel> IModalInteractionData.Channels => Channels;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IUser> IModalInteractionData.Users => Users;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IRole> IModalInteractionData.Roles => Roles;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IGuildUser> IModalInteractionData.Members => Members;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IAttachment> IModalInteractionData.Attachments => Attachments;
|
||||||
|
|
||||||
internal RestModalData(Model model, BaseDiscordClient discord, IGuild guild)
|
internal RestModalData(Model model, BaseDiscordClient discord, IGuild guild)
|
||||||
{
|
{
|
||||||
CustomId = model.CustomId;
|
CustomId = model.CustomId;
|
||||||
Components = model.Components
|
Components = model.Components
|
||||||
.SelectMany(x => x.Components.OfType<IInteractableComponent>())
|
.SelectMany(c => c switch
|
||||||
|
{
|
||||||
|
Discord.API.ActionRowComponent row => row.Components, // Preserve the previous behavior
|
||||||
|
Discord.API.LabelComponent label => [label.Component],
|
||||||
|
_ => [c]
|
||||||
|
})
|
||||||
|
.OfType<IInteractableComponent>()
|
||||||
.Select(x => new RestMessageComponentData(x, discord, guild))
|
.Select(x => new RestMessageComponentData(x, discord, guild))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
if (model.Resolved.IsSpecified)
|
||||||
|
{
|
||||||
|
Users = model.Resolved.Value.Users.IsSpecified
|
||||||
|
? model.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Members = model.Resolved.Value.Members.IsSpecified
|
||||||
|
? model.Resolved.Value.Members.Value.Select(member =>
|
||||||
|
{
|
||||||
|
member.Value.User = model.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value;
|
||||||
|
|
||||||
|
return RestGuildUser.Create(discord, guild, member.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Channels = model.Resolved.Value.Channels.IsSpecified
|
||||||
|
? model.Resolved.Value.Channels.Value.Select(channel =>
|
||||||
|
{
|
||||||
|
if (channel.Value.Type is ChannelType.DM)
|
||||||
|
return RestDMChannel.Create(discord, channel.Value);
|
||||||
|
return RestChannel.Create(discord, channel.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Roles = model.Resolved.Value.Roles.IsSpecified
|
||||||
|
? model.Resolved.Value.Roles.Value.Select(role => RestRole.Create(discord, guild, role.Value)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Attachments = model.Resolved.Value.Attachments.IsSpecified
|
||||||
|
? model.Resolved.Value.Attachments.Value.Select(attachment => Attachment.Create(attachment.Value, discord)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ internal static class MessageComponentExtension
|
|||||||
|
|
||||||
case ContainerComponent container:
|
case ContainerComponent container:
|
||||||
return new API.ContainerComponent(container);
|
return new API.ContainerComponent(container);
|
||||||
|
|
||||||
|
case LabelComponent label:
|
||||||
|
return new API.LabelComponent(label);
|
||||||
|
|
||||||
|
case FileUploadComponent fileUpload:
|
||||||
|
return new API.FileUploadComponent(fileUpload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -110,7 +116,7 @@ internal static class MessageComponentExtension
|
|||||||
{
|
{
|
||||||
var parsed = (API.TextInputComponent)component;
|
var parsed = (API.TextInputComponent)component;
|
||||||
return new TextInputComponent(parsed.CustomId,
|
return new TextInputComponent(parsed.CustomId,
|
||||||
parsed.Label,
|
parsed.Label.GetValueOrDefault(),
|
||||||
parsed.Placeholder.GetValueOrDefault(null),
|
parsed.Placeholder.GetValueOrDefault(null),
|
||||||
parsed.MinLength.ToNullable(),
|
parsed.MinLength.ToNullable(),
|
||||||
parsed.MaxLength.ToNullable(),
|
parsed.MaxLength.ToNullable(),
|
||||||
@@ -173,6 +179,22 @@ internal static class MessageComponentExtension
|
|||||||
parsed.Id.ToNullable());
|
parsed.Id.ToNullable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ComponentType.Label:
|
||||||
|
{
|
||||||
|
var parsed = (API.LabelComponent)component;
|
||||||
|
return new LabelComponent(parsed.Id.ToNullable(), parsed.Label, parsed.Description, parsed.Component.ToEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
case ComponentType.FileUpload:
|
||||||
|
{
|
||||||
|
var parsed = (API.FileUploadComponent)component;
|
||||||
|
return new FileUploadComponent(parsed.Id.ToNullable(),
|
||||||
|
parsed.CustomId,
|
||||||
|
parsed.MaxValues.ToNullable(),
|
||||||
|
parsed.MaxValues.ToNullable(),
|
||||||
|
parsed.IsRequired.GetValueOrDefault(false));
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ namespace Discord.Net.Converters
|
|||||||
case ComponentType.Container:
|
case ComponentType.Container:
|
||||||
messageComponent = new API.ContainerComponent();
|
messageComponent = new API.ContainerComponent();
|
||||||
break;
|
break;
|
||||||
|
case ComponentType.Label:
|
||||||
|
messageComponent = new API.LabelComponent();
|
||||||
|
break;
|
||||||
|
case ComponentType.FileUpload:
|
||||||
|
messageComponent = new API.FileUploadComponent();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new JsonSerializationException($"Unknown component type value '{typeProperty}' while deserializing message component");
|
throw new JsonSerializationException($"Unknown component type value '{typeProperty}' while deserializing message component");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ namespace Discord.WebSocket
|
|||||||
{
|
{
|
||||||
CustomId = modal.CustomId,
|
CustomId = modal.CustomId,
|
||||||
Title = modal.Title,
|
Title = modal.Title,
|
||||||
Components = modal.Component.Components.Select(x => new Discord.API.ActionRowComponent(x)).ToArray()
|
Components = modal.Component.Components.Select(x => x.ToModel()).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using Discord.Utils;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -95,9 +93,8 @@ namespace Discord.WebSocket
|
|||||||
CustomId = component.CustomId;
|
CustomId = component.CustomId;
|
||||||
Type = component.Type;
|
Type = component.Type;
|
||||||
|
|
||||||
Value = component.Type == ComponentType.TextInput
|
if (component is API.TextInputComponent textInput)
|
||||||
? ((TextInputComponent)component).Value
|
Value = textInput.Value.GetValueOrDefault();
|
||||||
: null;
|
|
||||||
|
|
||||||
if (component is API.SelectMenuComponent select)
|
if (component is API.SelectMenuComponent select)
|
||||||
{
|
{
|
||||||
@@ -132,6 +129,11 @@ namespace Discord.WebSocket
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (component is API.FileUploadComponent fileUpload)
|
||||||
|
{
|
||||||
|
Values = fileUpload.Values.GetValueOrDefault(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Model = Discord.API.ModalInteractionData;
|
using Model = Discord.API.ModalInteractionData;
|
||||||
|
|
||||||
@@ -20,13 +21,83 @@ namespace Discord.WebSocket
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<SocketMessageComponentData> Components { get; }
|
public IReadOnlyCollection<SocketMessageComponentData> Components { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Channels"/>
|
||||||
|
public IReadOnlyCollection<SocketChannel> Channels { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Users"/>
|
||||||
|
/// <remarks>Returns <see cref="SocketUser"/> if user is cached, <see cref="RestUser"/> otherwise.</remarks>
|
||||||
|
public IReadOnlyCollection<IUser> Users { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Roles"/>
|
||||||
|
public IReadOnlyCollection<SocketRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Members"/>
|
||||||
|
public IReadOnlyCollection<SocketGuildUser> Members { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IModalInteractionData.Attachments"/>
|
||||||
|
public IReadOnlyCollection<IAttachment> Attachments { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IChannel> IModalInteractionData.Channels => Channels;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IUser> IModalInteractionData.Users => Users;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IRole> IModalInteractionData.Roles => Roles;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IGuildUser> IModalInteractionData.Members => Members;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IAttachment> IModalInteractionData.Attachments => Attachments;
|
||||||
|
|
||||||
internal SocketModalData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
|
internal SocketModalData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
|
||||||
{
|
{
|
||||||
CustomId = model.CustomId;
|
CustomId = model.CustomId;
|
||||||
Components = model.Components
|
Components = model.Components
|
||||||
.SelectMany(x => x.Components.Select(y => y.ToEntity()).OfType<IInteractableComponent>())
|
.SelectMany(c => c switch
|
||||||
|
{
|
||||||
|
Discord.API.ActionRowComponent row => row.Components, // Preserve the previous behavior
|
||||||
|
Discord.API.LabelComponent label => [label.Component],
|
||||||
|
_ => [c]
|
||||||
|
})
|
||||||
|
.OfType<IInteractableComponent>()
|
||||||
.Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser))
|
.Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
if (model.Resolved.IsSpecified)
|
||||||
|
{
|
||||||
|
Users = model.Resolved.Value.Users.IsSpecified
|
||||||
|
? model.Resolved.Value.Users.Value.Select(user => (IUser)state.GetUser(user.Value.Id) ?? RestUser.Create(discord, user.Value)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Members = model.Resolved.Value.Members.IsSpecified
|
||||||
|
? model.Resolved.Value.Members.Value.Select(member =>
|
||||||
|
{
|
||||||
|
member.Value.User = model.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value;
|
||||||
|
return SocketGuildUser.Create(guild, state, member.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Channels = model.Resolved.Value.Channels.IsSpecified
|
||||||
|
? model.Resolved.Value.Channels.Value.Select(
|
||||||
|
channel =>
|
||||||
|
{
|
||||||
|
if (channel.Value.Type is ChannelType.DM)
|
||||||
|
return SocketDMChannel.Create(discord, state, channel.Value.Id, dmUser);
|
||||||
|
return (SocketChannel)SocketGuildChannel.Create(guild, state, channel.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Roles = model.Resolved.Value.Roles.IsSpecified
|
||||||
|
? model.Resolved.Value.Roles.Value.Select(role => SocketRole.Create(guild, state, role.Value)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Attachments = model.Resolved.Value.Attachments.IsSpecified
|
||||||
|
? model.Resolved.Value.Attachments.Value.Select(attachment => Attachment.Create(attachment.Value, discord)).ToImmutableArray()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ namespace Discord.WebSocket
|
|||||||
{
|
{
|
||||||
CustomId = modal.CustomId,
|
CustomId = modal.CustomId,
|
||||||
Title = modal.Title,
|
Title = modal.Title,
|
||||||
Components = modal.Component.Components.Select(x => new Discord.API.ActionRowComponent(x)).ToArray()
|
Components = modal.Component.Components.Select(x => x.ToModel()).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user