From 48fb1b5df484d06ee0947935101ddb83f70ad234 Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Sun, 25 Dec 2022 15:41:15 +0300
Subject: [PATCH] [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
*
* update Modal.cs
Co-authored-by: cat
Co-authored-by: Cenngo
---
docs/guides/int_framework/intro.md | 3 +
.../MessageComponents/ComponentBuilder.cs | 114 +++++++++++++----
.../MessageComponents/ComponentType.cs | 19 ++-
.../IComponentInteractionData.cs | 24 +++-
.../MessageComponents/SelectMenuComponent.cs | 14 ++-
.../Entities/Interactions/Modals/Modal.cs | 4 +-
.../Utils/ChannelTypeUtils.cs | 14 +++
src/Discord.Net.Core/Utils/ComponentType.cs | 8 ++
.../DefaultArrayComponentConverter.cs | 51 ++++++--
.../API/Common/ActionRowComponent.cs | 4 +
.../Common/MessageComponentInteractionData.cs | 4 +
...MessageComponentInteractionDataResolved.cs | 19 +++
.../API/Common/SelectMenuComponent.cs | 9 +-
.../MessageComponents/RestMessageComponent.cs | 2 +-
.../RestMessageComponentData.cs | 96 +++++++++++++-
.../Entities/Interactions/Modals/RestModal.cs | 2 +-
.../Interactions/Modals/RestModalData.cs | 17 +--
.../Entities/Messages/RestMessage.cs | 18 +--
.../Converters/MessageComponentConverter.cs | 4 +
.../DiscordSocketClient.cs | 3 +-
.../SocketMessageComponent.cs | 2 +-
.../SocketMessageComponentData.cs | 118 +++++++++++++++---
.../Interaction/Modals/SocketModal.cs | 4 +-
.../Interaction/Modals/SocketModalData.cs | 6 +-
.../Entities/Messages/SocketMessage.cs | 6 +-
25 files changed, 470 insertions(+), 95 deletions(-)
create mode 100644 src/Discord.Net.Core/Utils/ChannelTypeUtils.cs
create mode 100644 src/Discord.Net.Core/Utils/ComponentType.cs
create mode 100644 src/Discord.Net.Rest/API/Common/MessageComponentInteractionDataResolved.cs
diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md
index 21ea365de..4bee07e94 100644
--- a/docs/guides/int_framework/intro.md
+++ b/docs/guides/int_framework/intro.md
@@ -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.
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)]
> [!NOTE]
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
index fd8798ed3..29ff80cf2 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
@@ -1,7 +1,7 @@
+using Discord.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
-using Discord.Utils;
namespace Discord
{
@@ -92,9 +92,11 @@ namespace Discord
/// The max values of the placeholder.
/// Whether or not the menu is disabled.
/// The row to add the menu to.
+ /// The type of the select menu.
+ /// Menus valid channel types (only for )
///
- public ComponentBuilder WithSelectMenu(string customId, List options,
- string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false, int row = 0)
+ public ComponentBuilder WithSelectMenu(string customId, List options = null,
+ 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()
.WithCustomId(customId)
@@ -102,7 +104,9 @@ namespace Discord
.WithPlaceholder(placeholder)
.WithMaxValues(maxValues)
.WithMinValues(minValues)
- .WithDisabled(disabled),
+ .WithDisabled(disabled)
+ .WithType(type)
+ .WithChannelTypes(channelTypes),
row);
}
@@ -118,7 +122,7 @@ namespace Discord
public ComponentBuilder WithSelectMenu(SelectMenuBuilder menu, int row = 0)
{
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.");
var builtMenu = menu.Build();
@@ -278,9 +282,7 @@ namespace Discord
{
if (_actionRows?.SelectMany(x => x.Components)?.Any(x => x.Type == ComponentType.TextInput) ?? false)
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
? new MessageComponent(_actionRows.Select(x => x.Build()).ToList())
: MessageComponent.Empty;
@@ -356,10 +358,13 @@ namespace Discord
/// The placeholder of the menu.
/// The min values of the placeholder.
/// The max values of the placeholder.
- /// Whether or not the menu is disabled.
- /// The current builder.
- public ActionRowBuilder WithSelectMenu(string customId, List options,
- string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false)
+ /// Whether or not the menu is disabled.
+ /// The type of the select menu.
+ /// Menus valid channel types (only for )
+ /// The current builder.
+ public ActionRowBuilder WithSelectMenu(string customId, List options = null,
+ string placeholder = null, int minValues = 1, int maxValues = 1, bool disabled = false,
+ ComponentType type = ComponentType.SelectMenu, ChannelType[] channelTypes = null)
{
return WithSelectMenu(new SelectMenuBuilder()
.WithCustomId(customId)
@@ -367,7 +372,9 @@ namespace Discord
.WithPlaceholder(placeholder)
.WithMaxValues(maxValues)
.WithMinValues(minValues)
- .WithDisabled(disabled));
+ .WithDisabled(disabled)
+ .WithType(type)
+ .WithChannelTypes(channelTypes));
}
///
@@ -378,7 +385,7 @@ namespace Discord
/// The current builder.
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.");
var builtMenu = menu.Build();
@@ -431,10 +438,10 @@ namespace Discord
{
var builtButton = button.Build();
- if(Components.Count >= 5)
+ if (Components.Count >= 5)
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");
AddComponent(builtButton);
@@ -458,11 +465,15 @@ namespace Discord
case ComponentType.ActionRow:
return false;
case ComponentType.Button:
- if (Components.Any(x => x.Type == ComponentType.SelectMenu))
+ if (Components.Any(x => x.Type.IsSelectType()))
return false;
else
return Components.Count < 5;
case ComponentType.SelectMenu:
+ case ComponentType.ChannelSelect:
+ case ComponentType.MentionableSelect:
+ case ComponentType.RoleSelect:
+ case ComponentType.UserSelect:
return Components.Count == 0;
default:
return false;
@@ -759,6 +770,18 @@ namespace Discord
};
}
+ ///
+ /// Gets or sets the type of the current select menu.
+ ///
+ /// Type must be a select menu type.
+ public ComponentType Type
+ {
+ get => _type;
+ set => _type = value.IsSelectType()
+ ? value
+ : throw new ArgumentException("Type must be a select menu type.", nameof(value));
+ }
+
///
/// Gets or sets the placeholder text of the current select menu.
///
@@ -815,8 +838,6 @@ namespace Discord
{
if (value != null)
Preconditions.AtMost(value.Count, MaxOptionCount, nameof(Options));
- else
- throw new ArgumentNullException(nameof(value), $"{nameof(Options)} cannot be null.");
_options = value;
}
@@ -827,11 +848,17 @@ namespace Discord
///
public bool IsDisabled { get; set; }
+ ///
+ /// Gets or sets the menu's channel types (only valid on s).
+ ///
+ public List ChannelTypes { get; set; }
+
private List _options = new List();
private int _minValues = 1;
private int _maxValues = 1;
private string _placeholder;
private string _customId;
+ private ComponentType _type = ComponentType.SelectMenu;
///
/// Creates a new instance of a .
@@ -862,7 +889,9 @@ namespace Discord
/// The max values of this select menu.
/// The min values of this select menu.
/// Disabled this select menu or not.
- public SelectMenuBuilder(string customId, List options, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false)
+ /// The of this select menu.
+ /// The types of channels this menu can select (only valid on s)
+ public SelectMenuBuilder(string customId, List options = null, string placeholder = null, int maxValues = 1, int minValues = 1, bool isDisabled = false, ComponentType type = ComponentType.SelectMenu, List channelTypes = null)
{
CustomId = customId;
Options = options;
@@ -870,6 +899,8 @@ namespace Discord
IsDisabled = isDisabled;
MaxValues = maxValues;
MinValues = minValues;
+ Type = type;
+ ChannelTypes = channelTypes ?? new();
}
///
@@ -990,6 +1021,47 @@ namespace Discord
return this;
}
+ ///
+ /// Sets the menu's current type.
+ ///
+ /// The type of the menu.
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder WithType(ComponentType type)
+ {
+ Type = type;
+ return this;
+ }
+
+ ///
+ /// Sets the menus valid channel types (only for s).
+ ///
+ /// The valid channel types of the menu.
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder WithChannelTypes(List channelTypes)
+ {
+ ChannelTypes = channelTypes;
+ return this;
+ }
+
+ ///
+ /// Sets the menus valid channel types (only for s).
+ ///
+ /// The valid channel types of the menu.
+ ///
+ /// The current builder.
+ ///
+ public SelectMenuBuilder WithChannelTypes(params ChannelType[] channelTypes)
+ {
+ ChannelTypes = channelTypes is null
+ ? ChannelTypeUtils.AllChannelTypes()
+ : channelTypes.ToList();
+ return this;
+ }
+
///
/// Builds a
///
@@ -998,7 +1070,7 @@ namespace Discord
{
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);
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
index 1d63ee829..0ad3f741a 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
@@ -26,8 +26,23 @@ namespace Discord
TextInput = 4,
///
- /// An interaction sent when a model is submitted.
+ /// A select menu for picking from users.
///
- ModalSubmit = 5,
+ UserSelect = 5,
+
+ ///
+ /// A select menu for picking from roles.
+ ///
+ RoleSelect = 6,
+
+ ///
+ /// A select menu for picking from roles and users.
+ ///
+ MentionableSelect = 7,
+
+ ///
+ /// A select menu for picking from channels.
+ ///
+ ChannelSelect = 8,
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs
index 039b6b41f..3a6526ee2 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs
@@ -18,12 +18,32 @@ namespace Discord
ComponentType Type { get; }
///
- /// Gets the value(s) of a interaction response.
+ /// Gets the value(s) of a interaction response. if select type is different.
///
IReadOnlyCollection Values { get; }
///
- /// Gets the value of a interaction response.
+ /// Gets the channels(s) of a interaction response. if select type is different.
+ ///
+ IReadOnlyCollection Channels { get; }
+
+ ///
+ /// Gets the user(s) of a or interaction response. if select type is different.
+ ///
+ IReadOnlyCollection Users { get; }
+
+ ///
+ /// Gets the roles(s) of a or interaction response. if select type is different.
+ ///
+ IReadOnlyCollection Roles { get; }
+
+ ///
+ /// Gets the guild member(s) of a or interaction response. if type select is different.
+ ///
+ IReadOnlyCollection Members { get; }
+
+ ///
+ /// Gets the value of a interaction response.
///
public string Value { get; }
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
index 229c1e148..eccdd18c6 100644
--- a/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +10,7 @@ namespace Discord
public class SelectMenuComponent : IMessageComponent
{
///
- public ComponentType Type => ComponentType.SelectMenu;
+ public ComponentType Type { get; }
///
public string CustomId { get; }
@@ -39,6 +40,11 @@ namespace Discord
///
public bool IsDisabled { get; }
+ ///
+ /// Gets the allowed channel types for this modal
+ ///
+ public IReadOnlyCollection ChannelTypes { get; }
+
///
/// Turns this select menu into a builder.
///
@@ -52,9 +58,9 @@ namespace Discord
Placeholder,
MaxValues,
MinValues,
- IsDisabled);
+ IsDisabled, Type, ChannelTypes.ToList());
- internal SelectMenuComponent(string customId, List options, string placeholder, int minValues, int maxValues, bool disabled)
+ internal SelectMenuComponent(string customId, List options, string placeholder, int minValues, int maxValues, bool disabled, ComponentType type, IEnumerable channelTypes = null)
{
CustomId = customId;
Options = options;
@@ -62,6 +68,8 @@ namespace Discord
MinValues = minValues;
MaxValues = maxValues;
IsDisabled = disabled;
+ Type = type;
+ ChannelTypes = channelTypes?.ToArray() ?? Array.Empty();
}
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs
index a0fde5ea3..a435d33ef 100644
--- a/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/Modals/Modal.cs
@@ -7,12 +7,12 @@ using System.Threading.Tasks;
namespace Discord
{
///
- /// Represents a modal interaction.
+ /// Represents a modal interaction.
///
public class Modal : IMessageComponent
{
///
- public ComponentType Type => ComponentType.ModalSubmit;
+ public ComponentType Type => throw new NotSupportedException("Modals do not have a component type.");
///
/// Gets the title of the modal.
diff --git a/src/Discord.Net.Core/Utils/ChannelTypeUtils.cs b/src/Discord.Net.Core/Utils/ChannelTypeUtils.cs
new file mode 100644
index 000000000..4dd764508
--- /dev/null
+++ b/src/Discord.Net.Core/Utils/ChannelTypeUtils.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Discord.Utils;
+
+public static class ChannelTypeUtils
+{
+ public static List AllChannelTypes()
+ => new List()
+ {
+ 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
+ };
+}
diff --git a/src/Discord.Net.Core/Utils/ComponentType.cs b/src/Discord.Net.Core/Utils/ComponentType.cs
new file mode 100644
index 000000000..c7d42c512
--- /dev/null
+++ b/src/Discord.Net.Core/Utils/ComponentType.cs
@@ -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;
+}
diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
index 87fc431c5..5efdd537a 100644
--- a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
+++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Threading.Tasks;
namespace Discord.Interactions
@@ -17,27 +19,56 @@ namespace Discord.Interactions
throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter)} cannot be used to convert a non-array type.");
_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 ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services)
{
- var results = new List();
+ var objs = new List
public IReadOnlyCollection Components { get; }
- ///
- public ComponentType Type => ComponentType.ModalSubmit;
-
- ///
- public IReadOnlyCollection Values
- => throw new NotSupportedException("Modal interactions do not have values!");
-
- ///
- public string Value
- => throw new NotSupportedException("Modal interactions do not have value!");
-
IReadOnlyCollection IModalInteractionData.Components => Components;
- internal RestModalData(Model model)
+ internal RestModalData(Model model, BaseDiscordClient discord, IGuild guild)
{
CustomId = model.CustomId;
Components = model.Components
.SelectMany(x => x.Components)
- .Select(x => new RestMessageComponentData(x))
+ .Select(x => new RestMessageComponentData(x, discord, guild))
.ToArray();
}
}
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
index 69e038fd2..8b6b44e39 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs
@@ -170,26 +170,28 @@ namespace Discord.Rest
parsed.Url.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;
return new SelectMenuComponent(
parsed.CustomId,
- parsed.Options.Select(z => new SelectMenuOption(
+ parsed.Options?.Select(z => new SelectMenuOption(
z.Label,
z.Value,
z.Description.GetValueOrDefault(),
z.Emoji.IsSpecified
- ? z.Emoji.Value.Id.HasValue
- ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
- : new Emoji(z.Emoji.Value.Name)
- : null,
+ ? z.Emoji.Value.Id.HasValue
+ ? new Emote(z.Emoji.Value.Id.Value, z.Emoji.Value.Name, z.Emoji.Value.Animated.GetValueOrDefault())
+ : new Emoji(z.Emoji.Value.Name)
+ : null,
z.Default.ToNullable())).ToList(),
parsed.Placeholder.GetValueOrDefault(),
parsed.MinValues,
parsed.MaxValues,
- parsed.Disabled
- );
+ parsed.Disabled,
+ parsed.Type,
+ parsed.ChannelTypes.GetValueOrDefault()
+ );
}
default:
return null;
diff --git a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs
index 36542d83b..7888219bc 100644
--- a/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs
+++ b/src/Discord.Net.Rest/Net/Converters/MessageComponentConverter.cs
@@ -30,6 +30,10 @@ namespace Discord.Net.Converters
messageComponent = new API.ButtonComponent();
break;
case ComponentType.SelectMenu:
+ case ComponentType.ChannelSelect:
+ case ComponentType.MentionableSelect:
+ case ComponentType.RoleSelect:
+ case ComponentType.UserSelect:
messageComponent = new API.SelectMenuComponent();
break;
case ComponentType.TextInput:
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index f2239010f..cb982889c 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -5,6 +5,7 @@ using Discord.Net.Converters;
using Discord.Net.Udp;
using Discord.Net.WebSockets;
using Discord.Rest;
+using Discord.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@@ -2394,7 +2395,7 @@ namespace Discord.WebSocket
await TimedInvokeAsync(_slashCommandExecuted, nameof(SlashCommandExecuted), slashCommand).ConfigureAwait(false);
break;
case SocketMessageComponent messageComponent:
- if (messageComponent.Data.Type == ComponentType.SelectMenu)
+ if (messageComponent.Data.Type.IsSelectType())
await TimedInvokeAsync(_selectMenuExecuted, nameof(SelectMenuExecuted), messageComponent).ConfigureAwait(false);
if (messageComponent.Data.Type == ComponentType.Button)
await TimedInvokeAsync(_buttonExecuted, nameof(ButtonExecuted), messageComponent).ConfigureAwait(false);
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
index 2a1a67d04..286629ceb 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
@@ -35,7 +35,7 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: 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)
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs
index c7f6c5106..0099ec77c 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs
@@ -1,4 +1,9 @@
+using Discord.Rest;
+using Discord.Utils;
+using System;
+using System.Linq;
using System.Collections.Generic;
+using System.Collections.Immutable;
using Model = Discord.API.MessageComponentInteractionData;
namespace Discord.WebSocket
@@ -8,35 +13,84 @@ namespace Discord.WebSocket
///
public class SocketMessageComponentData : IComponentInteractionData
{
- ///
- /// Gets the components Custom Id that was clicked.
- ///
+ ///
public string CustomId { get; }
- ///
- /// Gets the type of the component clicked.
- ///
+ ///
public ComponentType Type { get; }
- ///
- /// Gets the value(s) of a interaction response.
- ///
+ ///
public IReadOnlyCollection Values { get; }
- ///
- /// Gets the value of a interaction response.
- ///
+ ///
+ public IReadOnlyCollection Channels { get; }
+
+ ///
+ /// Returns if user is cached, otherwise.
+ public IReadOnlyCollection Users { get; }
+
+ ///
+ public IReadOnlyCollection Roles { get; }
+
+ ///
+ public IReadOnlyCollection Members { get; }
+
+ #region IComponentInteractionData
+
+ ///
+ IReadOnlyCollection IComponentInteractionData.Channels => Channels;
+
+ ///
+ IReadOnlyCollection IComponentInteractionData.Users => Users;
+
+ ///
+ IReadOnlyCollection IComponentInteractionData.Roles => Roles;
+
+ ///
+ IReadOnlyCollection IComponentInteractionData.Members => Members;
+
+ #endregion
+ ///
public string Value { get; }
- internal SocketMessageComponentData(Model model)
+ internal SocketMessageComponentData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
{
CustomId = model.CustomId;
Type = model.ComponentType;
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 => (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;
Type = component.Type;
@@ -45,9 +99,39 @@ namespace Discord.WebSocket
? (component as API.TextInputComponent).Value.Value
: null;
- Values = component.Type == ComponentType.SelectMenu
- ? (component as API.SelectMenuComponent).Values.Value
- : null;
+ if (component is API.SelectMenuComponent select)
+ {
+ 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;
+ }
+ }
}
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
index 647544b48..7b4466af3 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
@@ -27,8 +27,8 @@ namespace Discord.WebSocket
var dataModel = model.Data.IsSpecified
? (DataModel)model.Data.Value
: 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)
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs
index df8be2fe8..ec1fe9622 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs
@@ -10,7 +10,7 @@ namespace Discord.WebSocket
///
/// Represents data sent from a .
///
- public class SocketModalData : IDiscordInteractionData, IModalInteractionData
+ public class SocketModalData : IModalInteractionData
{
///
/// Gets the 's Custom Id.
@@ -22,12 +22,12 @@ namespace Discord.WebSocket
///
public IReadOnlyCollection Components { get; }
- internal SocketModalData(Model model)
+ internal SocketModalData(Model model, DiscordSocketClient discord, ClientState state, SocketGuild guild, API.User dmUser)
{
CustomId = model.CustomId;
Components = model.Components
.SelectMany(x => x.Components)
- .Select(x => new SocketMessageComponentData(x))
+ .Select(x => new SocketMessageComponentData(x, discord, state, guild, dmUser))
.ToArray();
}
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
index 3cd67beb5..40a645afb 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
@@ -118,7 +118,7 @@ namespace Discord.WebSocket
///
/// Collection of WebSocket-based users.
///
- public IReadOnlyCollection MentionedUsers => _userMentions;
+ public IReadOnlyCollection MentionedUsers => _userMentions;
///
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);
@@ -226,7 +226,9 @@ namespace Discord.WebSocket
parsed.Placeholder.GetValueOrDefault(),
parsed.MinValues,
parsed.MaxValues,
- parsed.Disabled
+ parsed.Disabled,
+ parsed.Type,
+ parsed.ChannelTypes.GetValueOrDefault()
);
}
default: