[Feature] Selects v2 support (#2507)
* Initial support for new select types * Merge branch 'dev' of https://github.com/discord-net/Discord.Net into dev * some component&action row builder additions * remove redundant code * changes1 * maybe working rest part? * working-ish state? * fix some xml docs & small rework * typos * fix `ActionRowBuilder` * update DefaultArrayComponentConverter to accomodate new select-v2 types * now supports dm channels in channel selects * add a note to IF docs * add notes about nullable properties * <see langword="null"/> * update Modal.cs Co-authored-by: cat <lumitydev@gmail.com> Co-authored-by: Cenngo <cenk.ergen1@gmail.com>
This commit is contained in:
@@ -208,6 +208,9 @@ You may use as many wild card characters as you want.
|
|||||||
Unlike button interactions, select menu interactions also contain the values of the selected menu items.
|
Unlike button interactions, select menu interactions also contain the values of the selected menu items.
|
||||||
In this case, you should structure your method to accept a string array.
|
In this case, you should structure your method to accept a string array.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Use arrays of `IUser`, `IChannel`, `IRole`, `IMentionable` or their implementations to get data from a select menu with respective type.
|
||||||
|
|
||||||
[!code-csharp[Dropdown](samples/intro/dropdown.cs)]
|
[!code-csharp[Dropdown](samples/intro/dropdown.cs)]
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
using Discord.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Discord.Utils;
|
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
@@ -92,9 +92,11 @@ namespace Discord
|
|||||||
/// <param name="maxValues">The max values of the placeholder.</param>
|
/// <param name="maxValues">The max values of the placeholder.</param>
|
||||||
/// <param name="disabled">Whether or not the menu is disabled.</param>
|
/// <param name="disabled">Whether or not the menu is disabled.</param>
|
||||||
/// <param name="row">The row to add the menu to.</param>
|
/// <param name="row">The row to add the menu to.</param>
|
||||||
|
/// <param name="type">The type of the select menu.</param>
|
||||||
|
/// <param name="channelTypes">Menus valid channel types (only for <see cref="ComponentType.ChannelSelect"/>)</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ComponentBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options,
|
public ComponentBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options = null,
|
||||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0)
|
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0, ComponentType type = ComponentType.SelectMenu, ChannelType[] channelTypes = null)
|
||||||
{
|
{
|
||||||
return WithSelectMenu(new SelectMenuBuilder()
|
return WithSelectMenu(new SelectMenuBuilder()
|
||||||
.WithCustomId(customId)
|
.WithCustomId(customId)
|
||||||
@@ -102,7 +104,9 @@ namespace Discord
|
|||||||
.WithPlaceholder(placeholder)
|
.WithPlaceholder(placeholder)
|
||||||
.WithMaxValues(maxValues)
|
.WithMaxValues(maxValues)
|
||||||
.WithMinValues(minValues)
|
.WithMinValues(minValues)
|
||||||
.WithDisabled(disabled),
|
.WithDisabled(disabled)
|
||||||
|
.WithType(type)
|
||||||
|
.WithChannelTypes(channelTypes),
|
||||||
row);
|
row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +122,7 @@ namespace Discord
|
|||||||
public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0)
|
public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0)
|
||||||
{
|
{
|
||||||
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
|
Preconditions.LessThan(row, MaxActionRowCount, nameof(row));
|
||||||
if (menu.Options.Distinct().Count() != menu.Options.Count)
|
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.");
|
throw new InvalidOperationException("Please make sure that there is no duplicates values.");
|
||||||
|
|
||||||
var builtMenu = menu.Build();
|
var builtMenu = menu.Build();
|
||||||
@@ -278,8 +282,6 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
if (_actionRows?.SelectMany(x => x.Components)?.Any(x => x.Type == ComponentType.TextInput) ?? false)
|
if (_actionRows?.SelectMany(x => x.Components)?.Any(x => x.Type == ComponentType.TextInput) ?? false)
|
||||||
throw new ArgumentException("TextInputComponents are not allowed in messages.", nameof(ActionRows));
|
throw new ArgumentException("TextInputComponents are not allowed in messages.", nameof(ActionRows));
|
||||||
if (_actionRows?.SelectMany(x => x.Components)?.Any(x => x.Type == ComponentType.ModalSubmit) ?? false)
|
|
||||||
throw new ArgumentException("ModalSubmit components are not allowed in messages.", nameof(ActionRows));
|
|
||||||
|
|
||||||
return _actionRows != null
|
return _actionRows != null
|
||||||
? new MessageComponent(_actionRows.Select(x => x.Build()).ToList())
|
? new MessageComponent(_actionRows.Select(x => x.Build()).ToList())
|
||||||
@@ -357,9 +359,12 @@ namespace Discord
|
|||||||
/// <param name="minValues">The min values of the placeholder.</param>
|
/// <param name="minValues">The min values of the placeholder.</param>
|
||||||
/// <param name="maxValues">The max values of the placeholder.</param>
|
/// <param name="maxValues">The max values of the placeholder.</param>
|
||||||
/// <param name="disabled">Whether or not the menu is disabled.</param>
|
/// <param name="disabled">Whether or not the menu is disabled.</param>
|
||||||
|
/// <param name="type">The type of the select menu.</param>
|
||||||
|
/// <param name="channelTypes">Menus valid channel types (only for <see cref="ComponentType.ChannelSelect"/>)</param>
|
||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public ActionRowBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options,
|
public ActionRowBuilder WithSelectMenu(string customId, List<SelectMenuOptionBuilder> options = null,
|
||||||
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false)
|
string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false,
|
||||||
|
ComponentType type = ComponentType.SelectMenu, ChannelType[] channelTypes = null)
|
||||||
{
|
{
|
||||||
return WithSelectMenu(new SelectMenuBuilder()
|
return WithSelectMenu(new SelectMenuBuilder()
|
||||||
.WithCustomId(customId)
|
.WithCustomId(customId)
|
||||||
@@ -367,7 +372,9 @@ namespace Discord
|
|||||||
.WithPlaceholder(placeholder)
|
.WithPlaceholder(placeholder)
|
||||||
.WithMaxValues(maxValues)
|
.WithMaxValues(maxValues)
|
||||||
.WithMinValues(minValues)
|
.WithMinValues(minValues)
|
||||||
.WithDisabled(disabled));
|
.WithDisabled(disabled)
|
||||||
|
.WithType(type)
|
||||||
|
.WithChannelTypes(channelTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -378,7 +385,7 @@ namespace Discord
|
|||||||
/// <returns>The current builder.</returns>
|
/// <returns>The current builder.</returns>
|
||||||
public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu)
|
public ActionRowBuilder WithSelectMenu(SelectMenuBuilder menu)
|
||||||
{
|
{
|
||||||
if (menu.Options.Distinct().Count() != menu.Options.Count)
|
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.");
|
throw new InvalidOperationException("Please make sure that there is no duplicates values.");
|
||||||
|
|
||||||
var builtMenu = menu.Build();
|
var builtMenu = menu.Build();
|
||||||
@@ -431,10 +438,10 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
var builtButton = button.Build();
|
var builtButton = button.Build();
|
||||||
|
|
||||||
if(Components.Count >= 5)
|
if (Components.Count >= 5)
|
||||||
throw new InvalidOperationException($"Components count reached {MaxChildCount}");
|
throw new InvalidOperationException($"Components count reached {MaxChildCount}");
|
||||||
|
|
||||||
if (Components.Any(x => x.Type == ComponentType.SelectMenu))
|
if (Components.Any(x => x.Type.IsSelectType()))
|
||||||
throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu");
|
throw new InvalidOperationException($"A button cannot be added to a row with a SelectMenu");
|
||||||
|
|
||||||
AddComponent(builtButton);
|
AddComponent(builtButton);
|
||||||
@@ -458,11 +465,15 @@ namespace Discord
|
|||||||
case ComponentType.ActionRow:
|
case ComponentType.ActionRow:
|
||||||
return false;
|
return false;
|
||||||
case ComponentType.Button:
|
case ComponentType.Button:
|
||||||
if (Components.Any(x => x.Type == ComponentType.SelectMenu))
|
if (Components.Any(x => x.Type.IsSelectType()))
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
return Components.Count < 5;
|
return Components.Count < 5;
|
||||||
case ComponentType.SelectMenu:
|
case ComponentType.SelectMenu:
|
||||||
|
case ComponentType.ChannelSelect:
|
||||||
|
case ComponentType.MentionableSelect:
|
||||||
|
case ComponentType.RoleSelect:
|
||||||
|
case ComponentType.UserSelect:
|
||||||
return Components.Count == 0;
|
return Components.Count == 0;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -759,6 +770,18 @@ namespace Discord
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the current select menu.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException">Type must be a select menu type.</exception>
|
||||||
|
public ComponentType Type
|
||||||
|
{
|
||||||
|
get => _type;
|
||||||
|
set => _type = value.IsSelectType()
|
||||||
|
? value
|
||||||
|
: throw new ArgumentException("Type must be a select menu type.", nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the placeholder text of the current select menu.
|
/// Gets or sets the placeholder text of the current select menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -815,8 +838,6 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
if (value != null)
|
if (value != null)
|
||||||
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options));
|
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options));
|
||||||
else
|
|
||||||
throw new ArgumentNullException(nameof(value), $"{nameof(Options)} cannot be null.");
|
|
||||||
|
|
||||||
_options = value;
|
_options = value;
|
||||||
}
|
}
|
||||||
@@ -827,11 +848,17 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDisabled { get; set; }
|
public bool IsDisabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the menu's channel types (only valid on <see cref="ComponentType.ChannelSelect"/>s).
|
||||||
|
/// </summary>
|
||||||
|
public List<ChannelType> ChannelTypes { get; set; }
|
||||||
|
|
||||||
private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>();
|
private List<SelectMenuOptionBuilder> _options = new List<SelectMenuOptionBuilder>();
|
||||||
private int _minValues = 1;
|
private int _minValues = 1;
|
||||||
private int _maxValues = 1;
|
private int _maxValues = 1;
|
||||||
private string _placeholder;
|
private string _placeholder;
|
||||||
private string _customId;
|
private string _customId;
|
||||||
|
private ComponentType _type = ComponentType.SelectMenu;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of a <see cref="SelectMenuBuilder"/>.
|
/// Creates a new instance of a <see cref="SelectMenuBuilder"/>.
|
||||||
@@ -862,7 +889,9 @@ namespace Discord
|
|||||||
/// <param name="maxValues">The max values of this select menu.</param>
|
/// <param name="maxValues">The max values of this select menu.</param>
|
||||||
/// <param name="minValues">The min values of this select menu.</param>
|
/// <param name="minValues">The min values of this select menu.</param>
|
||||||
/// <param name="isDisabled">Disabled this select menu or not.</param>
|
/// <param name="isDisabled">Disabled this select menu or not.</param>
|
||||||
public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false)
|
/// <param name="type">The <see cref="ComponentType"/> of this select menu.</param>
|
||||||
|
/// <param name="channelTypes">The types of channels this menu can select (only valid on <see cref="ComponentType.ChannelSelect"/>s)</param>
|
||||||
|
public SelectMenuBuilder(string customId, List<SelectMenuOptionBuilder> options = null, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List<ChannelType> channelTypes = null)
|
||||||
{
|
{
|
||||||
CustomId = customId;
|
CustomId = customId;
|
||||||
Options = options;
|
Options = options;
|
||||||
@@ -870,6 +899,8 @@ namespace Discord
|
|||||||
IsDisabled = isDisabled;
|
IsDisabled = isDisabled;
|
||||||
MaxValues = maxValues;
|
MaxValues = maxValues;
|
||||||
MinValues = minValues;
|
MinValues = minValues;
|
||||||
|
Type = type;
|
||||||
|
ChannelTypes = channelTypes ?? new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -990,6 +1021,47 @@ namespace Discord
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the menu's current type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of the menu.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public SelectMenuBuilder WithType(ComponentType type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the menus valid channel types (only for <see cref="ComponentType.ChannelSelect"/>s).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelTypes">The valid channel types of the menu.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public SelectMenuBuilder WithChannelTypes(List<ChannelType> channelTypes)
|
||||||
|
{
|
||||||
|
ChannelTypes = channelTypes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the menus valid channel types (only for <see cref="ComponentType.ChannelSelect"/>s).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelTypes">The valid channel types of the menu.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The current builder.
|
||||||
|
/// </returns>
|
||||||
|
public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes)
|
||||||
|
{
|
||||||
|
ChannelTypes = channelTypes is null
|
||||||
|
? ChannelTypeUtils.AllChannelTypes()
|
||||||
|
: channelTypes.ToList();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a <see cref="SelectMenuComponent"/>
|
/// Builds a <see cref="SelectMenuComponent"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -998,7 +1070,7 @@ namespace Discord
|
|||||||
{
|
{
|
||||||
var options = Options?.Select(x => x.Build()).ToList();
|
var options = Options?.Select(x => x.Build()).ToList();
|
||||||
|
|
||||||
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled);
|
return new SelectMenuComponent(CustomId, options, Placeholder, MinValues, MaxValues, IsDisabled, Type, ChannelTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,23 @@ namespace Discord
|
|||||||
TextInput = 4,
|
TextInput = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An interaction sent when a model is submitted.
|
/// A select menu for picking from users.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ModalSubmit = 5,
|
UserSelect = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A select menu for picking from roles.
|
||||||
|
/// </summary>
|
||||||
|
RoleSelect = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A select menu for picking from roles and users.
|
||||||
|
/// </summary>
|
||||||
|
MentionableSelect = 7,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A select menu for picking from channels.
|
||||||
|
/// </summary>
|
||||||
|
ChannelSelect = 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,32 @@ namespace Discord
|
|||||||
ComponentType Type { get; }
|
ComponentType Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value(s) of a <see cref="SelectMenuComponent"/> interaction response.
|
/// Gets the value(s) of a <see cref="ComponentType.SelectMenu"/> interaction response. <see langword="null"/> if select type is different.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyCollection<string> Values { get; }
|
IReadOnlyCollection<string> Values { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value of a <see cref="TextInputComponent"/> interaction response.
|
/// Gets the channels(s) of a <see cref="ComponentType.ChannelSelect"/> interaction response. <see langword="null"/> if select type is different.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IChannel> Channels { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user(s) of a <see cref="ComponentType.UserSelect"/> or <see cref="ComponentType.MentionableSelect"/> interaction response. <see langword="null"/> if select type is different.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IUser> Users { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the roles(s) of a <see cref="ComponentType.RoleSelect"/> or <see cref="ComponentType.MentionableSelect"/> interaction response. <see langword="null"/> if select type is different.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the guild member(s) of a <see cref="ComponentType.UserSelect"/> or <see cref="ComponentType.MentionableSelect"/> interaction response. <see langword="null"/> if type select is different.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<IGuildUser> Members { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a <see cref="ComponentType.TextInput"/> interaction response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ namespace Discord
|
|||||||
public class SelectMenuComponent : IMessageComponent
|
public class SelectMenuComponent : IMessageComponent
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ComponentType Type => ComponentType.SelectMenu;
|
public ComponentType Type { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string CustomId { get; }
|
public string CustomId { get; }
|
||||||
@@ -39,6 +40,11 @@ namespace Discord
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDisabled { get; }
|
public bool IsDisabled { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the allowed channel types for this modal
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<ChannelType> ChannelTypes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Turns this select menu into a builder.
|
/// Turns this select menu into a builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,9 +58,9 @@ namespace Discord
|
|||||||
Placeholder,
|
Placeholder,
|
||||||
MaxValues,
|
MaxValues,
|
||||||
MinValues,
|
MinValues,
|
||||||
IsDisabled);
|
IsDisabled, Type, ChannelTypes.ToList());
|
||||||
|
|
||||||
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues, bool disabled)
|
internal SelectMenuComponent(string customId, List<SelectMenuOption> options, string placeholder, int minValues, int maxValues, bool disabled, ComponentType type, IEnumerable<ChannelType> channelTypes = null)
|
||||||
{
|
{
|
||||||
CustomId = customId;
|
CustomId = customId;
|
||||||
Options = options;
|
Options = options;
|
||||||
@@ -62,6 +68,8 @@ namespace Discord
|
|||||||
MinValues = minValues;
|
MinValues = minValues;
|
||||||
MaxValues = maxValues;
|
MaxValues = maxValues;
|
||||||
IsDisabled = disabled;
|
IsDisabled = disabled;
|
||||||
|
Type = type;
|
||||||
|
ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Discord
|
|||||||
public class Modal : IMessageComponent
|
public class Modal : IMessageComponent
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ComponentType Type => ComponentType.ModalSubmit;
|
public ComponentType Type => throw new NotSupportedException("Modals do not have a component type.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the title of the modal.
|
/// Gets the title of the modal.
|
||||||
|
|||||||
14
src/Discord.Net.Core/Utils/ChannelTypeUtils.cs
Normal file
14
src/Discord.Net.Core/Utils/ChannelTypeUtils.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Discord.Utils;
|
||||||
|
|
||||||
|
public static class ChannelTypeUtils
|
||||||
|
{
|
||||||
|
public static List<ChannelType> AllChannelTypes()
|
||||||
|
=> new List<ChannelType>()
|
||||||
|
{
|
||||||
|
ChannelType.Forum, ChannelType.Category, ChannelType.DM, ChannelType.Group, ChannelType.GuildDirectory,
|
||||||
|
ChannelType.News, ChannelType.NewsThread, ChannelType.PrivateThread, ChannelType.PublicThread,
|
||||||
|
ChannelType.Stage, ChannelType.Store, ChannelType.Text, ChannelType.Voice
|
||||||
|
};
|
||||||
|
}
|
||||||
8
src/Discord.Net.Core/Utils/ComponentType.cs
Normal file
8
src/Discord.Net.Core/Utils/ComponentType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Discord.Utils;
|
||||||
|
|
||||||
|
public static class ComponentTypeUtils
|
||||||
|
{
|
||||||
|
public static bool IsSelectType(this ComponentType type) => type is ComponentType.ChannelSelect
|
||||||
|
or ComponentType.SelectMenu or ComponentType.RoleSelect or ComponentType.UserSelect
|
||||||
|
or ComponentType.MentionableSelect;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Interactions
|
namespace Discord.Interactions
|
||||||
@@ -17,27 +19,56 @@ namespace Discord.Interactions
|
|||||||
throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter<T>)} cannot be used to convert a non-array type.");
|
throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter<T>)} cannot be used to convert a non-array type.");
|
||||||
|
|
||||||
_underlyingType = typeof(T).GetElementType();
|
_underlyingType = typeof(T).GetElementType();
|
||||||
_typeReader = interactionService.GetTypeReader(_underlyingType);
|
|
||||||
|
_typeReader = true switch
|
||||||
|
{
|
||||||
|
_ when typeof(IUser).IsAssignableFrom(_underlyingType)
|
||||||
|
|| typeof(IChannel).IsAssignableFrom(_underlyingType)
|
||||||
|
|| typeof(IMentionable).IsAssignableFrom(_underlyingType)
|
||||||
|
|| typeof(IRole).IsAssignableFrom(_underlyingType) => null,
|
||||||
|
_ => interactionService.GetTypeReader(_underlyingType)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
|
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
|
||||||
{
|
{
|
||||||
var results = new List<TypeConverterResult>();
|
var objs = new List<object>();
|
||||||
|
|
||||||
foreach (var value in option.Values)
|
if(_typeReader is not null && option.Values.Count > 0)
|
||||||
|
foreach (var value in option.Values)
|
||||||
|
{
|
||||||
|
var result = await _typeReader.ReadAsync(context, value, services).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
objs.Add(result.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var result = await _typeReader.ReadAsync(context, value, services).ConfigureAwait(false);
|
var users = new Dictionary<ulong, IUser>();
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (option.Users is not null)
|
||||||
return result;
|
foreach (var user in option.Users)
|
||||||
|
users[user.Id] = user;
|
||||||
|
|
||||||
results.Add(result);
|
if(option.Members is not null)
|
||||||
|
foreach(var member in option.Members)
|
||||||
|
users[member.Id] = member;
|
||||||
|
|
||||||
|
objs.AddRange(users.Values);
|
||||||
|
|
||||||
|
if(option.Roles is not null)
|
||||||
|
objs.AddRange(option.Roles);
|
||||||
|
|
||||||
|
if (option.Channels is not null)
|
||||||
|
objs.AddRange(option.Channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
var destination = Array.CreateInstance(_underlyingType, results.Count);
|
var destination = Array.CreateInstance(_underlyingType, objs.Count);
|
||||||
|
|
||||||
for (var i = 0; i < results.Count; i++)
|
for (var i = 0; i < objs.Count; i++)
|
||||||
destination.SetValue(results[i].Value, i);
|
destination.SetValue(objs[i], i);
|
||||||
|
|
||||||
return TypeConverterResult.FromSuccess(destination);
|
return TypeConverterResult.FromSuccess(destination);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ namespace Discord.API
|
|||||||
{
|
{
|
||||||
ComponentType.Button => new ButtonComponent(x as Discord.ButtonComponent),
|
ComponentType.Button => new ButtonComponent(x as Discord.ButtonComponent),
|
||||||
ComponentType.SelectMenu => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
ComponentType.SelectMenu => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
||||||
|
ComponentType.ChannelSelect => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
||||||
|
ComponentType.UserSelect => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
||||||
|
ComponentType.RoleSelect => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
||||||
|
ComponentType.MentionableSelect => new SelectMenuComponent(x as Discord.SelectMenuComponent),
|
||||||
ComponentType.TextInput => new TextInputComponent(x as Discord.TextInputComponent),
|
ComponentType.TextInput => new TextInputComponent(x as Discord.TextInputComponent),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Discord.API
|
namespace Discord.API
|
||||||
{
|
{
|
||||||
@@ -15,5 +16,8 @@ namespace Discord.API
|
|||||||
|
|
||||||
[JsonProperty("value")]
|
[JsonProperty("value")]
|
||||||
public Optional<string> Value { get; set; }
|
public Optional<string> Value { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("resolved")]
|
||||||
|
public Optional<MessageComponentInteractionDataResolved> Resolved { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Discord.API;
|
||||||
|
|
||||||
|
internal class MessageComponentInteractionDataResolved
|
||||||
|
{
|
||||||
|
[JsonProperty("users")]
|
||||||
|
public Optional<Dictionary<string, User>> Users { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("members")]
|
||||||
|
public Optional<Dictionary<string, GuildMember>> Members { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("channels")]
|
||||||
|
public Optional<Dictionary<string, Channel>> Channels { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("roles")]
|
||||||
|
public Optional<Dictionary<string, Role>> Roles { get; set; }
|
||||||
|
}
|
||||||
@@ -26,6 +26,12 @@ namespace Discord.API
|
|||||||
[JsonProperty("disabled")]
|
[JsonProperty("disabled")]
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("channel_types")]
|
||||||
|
public Optional<ChannelType[]> ChannelTypes { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("resolved")]
|
||||||
|
public Optional<MessageComponentInteractionDataResolved> Resolved { get; set; }
|
||||||
|
|
||||||
[JsonProperty("values")]
|
[JsonProperty("values")]
|
||||||
public Optional<string[]> Values { get; set; }
|
public Optional<string[]> Values { get; set; }
|
||||||
public SelectMenuComponent() { }
|
public SelectMenuComponent() { }
|
||||||
@@ -34,11 +40,12 @@ namespace Discord.API
|
|||||||
{
|
{
|
||||||
Type = component.Type;
|
Type = component.Type;
|
||||||
CustomId = component.CustomId;
|
CustomId = component.CustomId;
|
||||||
Options = component.Options.Select(x => new SelectMenuOption(x)).ToArray();
|
Options = component.Options?.Select(x => new SelectMenuOption(x)).ToArray();
|
||||||
Placeholder = component.Placeholder;
|
Placeholder = component.Placeholder;
|
||||||
MinValues = component.MinValues;
|
MinValues = component.MinValues;
|
||||||
MaxValues = component.MaxValues;
|
MaxValues = component.MaxValues;
|
||||||
Disabled = component.IsDisabled;
|
Disabled = component.IsDisabled;
|
||||||
|
ChannelTypes = component.ChannelTypes.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Discord.Rest
|
|||||||
? (DataModel)model.Data.Value
|
? (DataModel)model.Data.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Data = new RestMessageComponentData(dataModel);
|
Data = new RestMessageComponentData(dataModel, client, Guild);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal new static async Task<RestMessageComponent> CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
|
internal new static async Task<RestMessageComponent> CreateAsync(DiscordRestClient client, Model model, bool doApiCall)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
using Discord.API;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Model = Discord.API.MessageComponentInteractionData;
|
using Model = Discord.API.MessageComponentInteractionData;
|
||||||
|
|
||||||
namespace Discord.Rest
|
namespace Discord.Rest
|
||||||
@@ -10,7 +14,7 @@ namespace Discord.Rest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents data for a <see cref="RestMessageComponent"/>.
|
/// Represents data for a <see cref="RestMessageComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RestMessageComponentData : IComponentInteractionData, IDiscordInteractionData
|
public class RestMessageComponentData : IComponentInteractionData
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string CustomId { get; }
|
public string CustomId { get; }
|
||||||
@@ -21,17 +25,75 @@ namespace Discord.Rest
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IReadOnlyCollection<string> Values { get; }
|
public IReadOnlyCollection<string> Values { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Channels"/>
|
||||||
|
public IReadOnlyCollection<RestChannel> Channels { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Users"/>
|
||||||
|
public IReadOnlyCollection<RestUser> Users { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Roles"/>
|
||||||
|
public IReadOnlyCollection<RestRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Members"/>
|
||||||
|
public IReadOnlyCollection<RestGuildUser> Members { get; }
|
||||||
|
|
||||||
|
#region IComponentInteractionData
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IChannel> IComponentInteractionData.Channels => Channels;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IUser> IComponentInteractionData.Users => Users;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IRole> IComponentInteractionData.Roles => Roles;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IReadOnlyCollection<IGuildUser> IComponentInteractionData.Members => Members;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
internal RestMessageComponentData(Model model)
|
internal RestMessageComponentData(Model model, BaseDiscordClient discord, IGuild guild)
|
||||||
{
|
{
|
||||||
CustomId = model.CustomId;
|
CustomId = model.CustomId;
|
||||||
Type = model.ComponentType;
|
Type = model.ComponentType;
|
||||||
Values = model.Values.GetValueOrDefault();
|
Values = model.Values.GetValueOrDefault();
|
||||||
|
Value = model.Value.GetValueOrDefault();
|
||||||
|
|
||||||
|
if (model.Resolved.IsSpecified)
|
||||||
|
{
|
||||||
|
Users = model.Resolved.Value.Users.IsSpecified
|
||||||
|
? model.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray()
|
||||||
|
: Array.Empty<RestUser>();
|
||||||
|
|
||||||
|
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()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
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()
|
||||||
|
: Array.Empty<RestChannel>();
|
||||||
|
|
||||||
|
Roles = model.Resolved.Value.Roles.IsSpecified
|
||||||
|
? model.Resolved.Value.Roles.Value.Select(role => RestRole.Create(discord, guild, role.Value)).ToImmutableArray()
|
||||||
|
: Array.Empty<RestRole>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal RestMessageComponentData(IMessageComponent component)
|
internal RestMessageComponentData(IMessageComponent component, BaseDiscordClient discord, IGuild guild)
|
||||||
{
|
{
|
||||||
CustomId = component.CustomId;
|
CustomId = component.CustomId;
|
||||||
Type = component.Type;
|
Type = component.Type;
|
||||||
@@ -40,7 +102,33 @@ namespace Discord.Rest
|
|||||||
Value = textInput.Value.Value;
|
Value = textInput.Value.Value;
|
||||||
|
|
||||||
if (component is API.SelectMenuComponent select)
|
if (component is API.SelectMenuComponent select)
|
||||||
Values = select.Values.Value;
|
{
|
||||||
|
Values = select.Values.GetValueOrDefault(null);
|
||||||
|
|
||||||
|
if (select.Resolved.IsSpecified)
|
||||||
|
{
|
||||||
|
Users = select.Resolved.Value.Users.IsSpecified
|
||||||
|
? select.Resolved.Value.Users.Value.Select(user => RestUser.Create(discord, user.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Members = select.Resolved.Value.Members.IsSpecified
|
||||||
|
? select.Resolved.Value.Members.Value.Select(member =>
|
||||||
|
{
|
||||||
|
member.Value.User = select.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value;
|
||||||
|
|
||||||
|
return RestGuildUser.Create(discord, guild, member.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Channels = select.Resolved.Value.Channels.IsSpecified
|
||||||
|
? select.Resolved.Value.Channels.Value.Select(channel => RestChannel.Create(discord, channel.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Roles = select.Resolved.Value.Roles.IsSpecified
|
||||||
|
? select.Resolved.Value.Roles.Value.Select(role => RestRole.Create(discord, guild, role.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Discord.Rest
|
|||||||
? (DataModel)model.Data.Value
|
? (DataModel)model.Data.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Data = new RestModalData(dataModel);
|
Data = new RestModalData(dataModel, client, Guild);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal new static async Task<RestModal> CreateAsync(DiscordRestClient client, ModelBase model, bool doApiCall)
|
internal new static async Task<RestModal> CreateAsync(DiscordRestClient client, ModelBase model, bool doApiCall)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Discord.Rest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents data sent from a <see cref="InteractionType.ModalSubmit"/> Interaction.
|
/// Represents data sent from a <see cref="InteractionType.ModalSubmit"/> Interaction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RestModalData : IComponentInteractionData, IModalInteractionData
|
public class RestModalData : IModalInteractionData
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string CustomId { get; }
|
public string CustomId { get; }
|
||||||
@@ -20,25 +20,14 @@ namespace Discord.Rest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<RestMessageComponentData> Components { get; }
|
public IReadOnlyCollection<RestMessageComponentData> Components { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public ComponentType Type => ComponentType.ModalSubmit;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IReadOnlyCollection<string> Values
|
|
||||||
=> throw new NotSupportedException("Modal interactions do not have values!");
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Value
|
|
||||||
=> throw new NotSupportedException("Modal interactions do not have value!");
|
|
||||||
|
|
||||||
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
IReadOnlyCollection<IComponentInteractionData> IModalInteractionData.Components => Components;
|
||||||
|
|
||||||
internal RestModalData(Model model)
|
internal RestModalData(Model model, BaseDiscordClient discord, IGuild guild)
|
||||||
{
|
{
|
||||||
CustomId = model.CustomId;
|
CustomId = model.CustomId;
|
||||||
Components = model.Components
|
Components = model.Components
|
||||||
.SelectMany(x => x.Components)
|
.SelectMany(x => x.Components)
|
||||||
.Select(x => new RestMessageComponentData(x))
|
.Select(x => new RestMessageComponentData(x, discord, guild))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,26 +170,28 @@ namespace Discord.Rest
|
|||||||
parsed.Url.GetValueOrDefault(),
|
parsed.Url.GetValueOrDefault(),
|
||||||
parsed.Disabled.GetValueOrDefault());
|
parsed.Disabled.GetValueOrDefault());
|
||||||
}
|
}
|
||||||
case ComponentType.SelectMenu:
|
case ComponentType.SelectMenu or ComponentType.ChannelSelect or ComponentType.RoleSelect or ComponentType.MentionableSelect or ComponentType.UserSelect:
|
||||||
{
|
{
|
||||||
var parsed = (API.SelectMenuComponent)y;
|
var parsed = (API.SelectMenuComponent)y;
|
||||||
return new SelectMenuComponent(
|
return new SelectMenuComponent(
|
||||||
parsed.CustomId,
|
parsed.CustomId,
|
||||||
parsed.Options.Select(z => new SelectMenuOption(
|
parsed.Options?.Select(z => new SelectMenuOption(
|
||||||
z.Label,
|
z.Label,
|
||||||
z.Value,
|
z.Value,
|
||||||
z.Description.GetValueOrDefault(),
|
z.Description.GetValueOrDefault(),
|
||||||
z.Emoji.IsSpecified
|
z.Emoji.IsSpecified
|
||||||
? z.Emoji.Value.Id.HasValue
|
? z.Emoji.Value.Id.HasValue
|
||||||
? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
|
? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
|
||||||
: new Emoji(z.Emoji.Value.Name)
|
: new Emoji(z.Emoji.Value.Name)
|
||||||
: null,
|
: null,
|
||||||
z.Default.ToNullable())).ToList(),
|
z.Default.ToNullable())).ToList(),
|
||||||
parsed.Placeholder.GetValueOrDefault(),
|
parsed.Placeholder.GetValueOrDefault(),
|
||||||
parsed.MinValues,
|
parsed.MinValues,
|
||||||
parsed.MaxValues,
|
parsed.MaxValues,
|
||||||
parsed.Disabled
|
parsed.Disabled,
|
||||||
);
|
parsed.Type,
|
||||||
|
parsed.ChannelTypes.GetValueOrDefault()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ namespace Discord.Net.Converters
|
|||||||
messageComponent = new API.ButtonComponent();
|
messageComponent = new API.ButtonComponent();
|
||||||
break;
|
break;
|
||||||
case ComponentType.SelectMenu:
|
case ComponentType.SelectMenu:
|
||||||
|
case ComponentType.ChannelSelect:
|
||||||
|
case ComponentType.MentionableSelect:
|
||||||
|
case ComponentType.RoleSelect:
|
||||||
|
case ComponentType.UserSelect:
|
||||||
messageComponent = new API.SelectMenuComponent();
|
messageComponent = new API.SelectMenuComponent();
|
||||||
break;
|
break;
|
||||||
case ComponentType.TextInput:
|
case ComponentType.TextInput:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Discord.Net.Converters;
|
|||||||
using Discord.Net.Udp;
|
using Discord.Net.Udp;
|
||||||
using Discord.Net.WebSockets;
|
using Discord.Net.WebSockets;
|
||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
|
using Discord.Utils;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
@@ -2394,7 +2395,7 @@ namespace Discord.WebSocket
|
|||||||
await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false);
|
await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
case SocketMessageComponent messageComponent:
|
case SocketMessageComponent messageComponent:
|
||||||
if (messageComponent.Data.Type == ComponentType.SelectMenu)
|
if (messageComponent.Data.Type.IsSelectType())
|
||||||
await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false);
|
await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false);
|
||||||
if (messageComponent.Data.Type == ComponentType.Button)
|
if (messageComponent.Data.Type == ComponentType.Button)
|
||||||
await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false);
|
await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Discord.WebSocket
|
|||||||
? (DataModel)model.Data.Value
|
? (DataModel)model.Data.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Data = new SocketMessageComponentData(dataModel);
|
Data = new SocketMessageComponentData(dataModel, client, client.State, client.Guilds.FirstOrDefault(x => x.Id == model.GuildId.GetValueOrDefault()), model.User.GetValueOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel, SocketUser user)
|
internal new static SocketMessageComponent Create(DiscordSocketClient client, Model model, ISocketMessageChannel channel, SocketUser user)
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
using Discord.Rest;
|
||||||
|
using Discord.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using Model = Discord.API.MessageComponentInteractionData;
|
using Model = Discord.API.MessageComponentInteractionData;
|
||||||
|
|
||||||
namespace Discord.WebSocket
|
namespace Discord.WebSocket
|
||||||
@@ -8,35 +13,84 @@ namespace Discord.WebSocket
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SocketMessageComponentData : IComponentInteractionData
|
public class SocketMessageComponentData : IComponentInteractionData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the components Custom Id that was clicked.
|
|
||||||
/// </summary>
|
|
||||||
public string CustomId { get; }
|
public string CustomId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the type of the component clicked.
|
|
||||||
/// </summary>
|
|
||||||
public ComponentType Type { get; }
|
public ComponentType Type { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the value(s) of a <see cref="SelectMenuComponent"/> interaction response.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyCollection<string> Values { get; }
|
public IReadOnlyCollection<string> Values { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc cref="IComponentInteractionData.Channels"/>
|
||||||
/// Gets the value of a <see cref="TextInputComponent"/> interaction response.
|
public IReadOnlyCollection<SocketChannel> Channels { get; }
|
||||||
/// </summary>
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Users"/>
|
||||||
|
/// <remarks>Returns <see cref="SocketUser"/> if user is cached, <see cref="RestUser"/> otherwise.</remarks>
|
||||||
|
public IReadOnlyCollection<IUser> Users { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Roles"/>
|
||||||
|
public IReadOnlyCollection<SocketRole> Roles { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IComponentInteractionData.Members"/>
|
||||||
|
public IReadOnlyCollection<SocketGuildUser> Members { get; }
|
||||||
|
|
||||||
|
#region IComponentInteractionData
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IChannel> IComponentInteractionData.Channels => Channels;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IUser> IComponentInteractionData.Users => Users;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IRole> IComponentInteractionData.Roles => Roles;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IReadOnlyCollection<IGuildUser> IComponentInteractionData.Members => Members;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
/// <inheritdoc />
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
internal SocketMessageComponentData(Model model)
|
internal SocketMessageComponentData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
|
||||||
{
|
{
|
||||||
CustomId = model.CustomId;
|
CustomId = model.CustomId;
|
||||||
Type = model.ComponentType;
|
Type = model.ComponentType;
|
||||||
Values = model.Values.GetValueOrDefault();
|
Values = model.Values.GetValueOrDefault();
|
||||||
Value = model.Value.GetValueOrDefault();
|
Value = model.Value.GetValueOrDefault();
|
||||||
|
|
||||||
|
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()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
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()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
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()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Roles = model.Resolved.Value.Roles.IsSpecified
|
||||||
|
? model.Resolved.Value.Roles.Value.Select(role => SocketRole.Create(guild, state, role.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal SocketMessageComponentData(IMessageComponent component)
|
internal SocketMessageComponentData(IMessageComponent component, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
|
||||||
{
|
{
|
||||||
CustomId = component.CustomId;
|
CustomId = component.CustomId;
|
||||||
Type = component.Type;
|
Type = component.Type;
|
||||||
@@ -45,9 +99,39 @@ namespace Discord.WebSocket
|
|||||||
? (component as API.TextInputComponent).Value.Value
|
? (component as API.TextInputComponent).Value.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Values = component.Type == ComponentType.SelectMenu
|
if (component is API.SelectMenuComponent select)
|
||||||
? (component as API.SelectMenuComponent).Values.Value
|
{
|
||||||
: null;
|
Values = select.Values.GetValueOrDefault(null);
|
||||||
|
|
||||||
|
if (select.Resolved.IsSpecified)
|
||||||
|
{
|
||||||
|
Users = select.Resolved.Value.Users.IsSpecified
|
||||||
|
? select.Resolved.Value.Users.Value.Select(user => (IUser)state.GetUser(user.Value.Id) ?? RestUser.Create(discord, user.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Members = select.Resolved.Value.Members.IsSpecified
|
||||||
|
? select.Resolved.Value.Members.Value.Select(member =>
|
||||||
|
{
|
||||||
|
member.Value.User = select.Resolved.Value.Users.Value.First(u => u.Key == member.Key).Value;
|
||||||
|
return SocketGuildUser.Create(guild, state, member.Value);
|
||||||
|
}).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Channels = select.Resolved.Value.Channels.IsSpecified
|
||||||
|
? select.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()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
Roles = select.Resolved.Value.Roles.IsSpecified
|
||||||
|
? select.Resolved.Value.Roles.Value.Select(role => SocketRole.Create(guild, state, role.Value)).ToImmutableArray()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Discord.WebSocket
|
|||||||
? (DataModel)model.Data.Value
|
? (DataModel)model.Data.Value
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Data = new SocketModalData(dataModel);
|
Data = new SocketModalData(dataModel, client, client.State, client.State.GetGuild(model.GuildId.GetValueOrDefault()), model.User.GetValueOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal new static SocketModal Create(DiscordSocketClient client, ModelBase model, ISocketMessageChannel channel, SocketUser user)
|
internal new static SocketModal Create(DiscordSocketClient client, ModelBase model, ISocketMessageChannel channel, SocketUser user)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Discord.WebSocket
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents data sent from a <see cref="InteractionType.ModalSubmit"/>.
|
/// Represents data sent from a <see cref="InteractionType.ModalSubmit"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SocketModalData : IDiscordInteractionData, IModalInteractionData
|
public class SocketModalData : IModalInteractionData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="Modal"/>'s Custom Id.
|
/// Gets the <see cref="Modal"/>'s Custom Id.
|
||||||
@@ -22,12 +22,12 @@ namespace Discord.WebSocket
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<SocketMessageComponentData> Components { get; }
|
public IReadOnlyCollection<SocketMessageComponentData> Components { get; }
|
||||||
|
|
||||||
internal SocketModalData(Model model)
|
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)
|
.SelectMany(x => x.Components)
|
||||||
.Select(x => new SocketMessageComponentData(x))
|
.Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,9 @@ namespace Discord.WebSocket
|
|||||||
parsed.Placeholder.GetValueOrDefault(),
|
parsed.Placeholder.GetValueOrDefault(),
|
||||||
parsed.MinValues,
|
parsed.MinValues,
|
||||||
parsed.MaxValues,
|
parsed.MaxValues,
|
||||||
parsed.Disabled
|
parsed.Disabled,
|
||||||
|
parsed.Type,
|
||||||
|
parsed.ChannelTypes.GetValueOrDefault()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user