diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
index 9d85c917a..666d1b2e7 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
@@ -9,8 +9,9 @@ namespace Discord
{
private string _name;
private object _value;
+
///
- /// The name of this choice.
+ /// Gets the name of this choice.
///
public string Name
{
@@ -24,9 +25,9 @@ namespace Discord
}
///
- /// The value of this choice.
+ /// Gets the value of this choice.
///
- /// Discord only accepts int and string as the input.
+ /// Discord only accepts int, double/floats, and string as the input.
///
///
public object Value
diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
index 25ef212c5..b3a4b8e92 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
@@ -13,7 +13,7 @@ namespace Discord
public Optional Name { get; set; }
///
- /// Whether the command is enabled by default when the app is added to a guild. Default is
+ /// Gets whether the command is enabled by default when the app is added to a guild. Default is .
///
public Optional DefaultPermission { get; set; }
diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs
index 4f7d3cb5f..eb22a9d27 100644
--- a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs
@@ -6,7 +6,7 @@ namespace Discord
public class AutocompleteOption
{
///
- /// Gets the type of this option
+ /// Gets the type of this option.
///
public ApplicationCommandOptionType Type { get; }
diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs
index a4333ceb2..78f2a7774 100644
--- a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs
@@ -45,15 +45,13 @@ namespace Discord
public object Value
{
get => _value;
- set => _value = value switch
+ set
{
- string str => str,
- int integer => integer,
- long lng => lng,
- double number => number,
- null => throw new ArgumentNullException(nameof(value), $"{nameof(Value)} cannot be null."),
- _ => throw new ArgumentException($"Type {value.GetType().Name} cannot be set as a value! Only string, int, and double allowed!")
- };
+ if (value is not string || !value.IsNumericType())
+ throw new ArgumentException($"{nameof(value)} must be a numeric type or a string!");
+
+ _value = value;
+ }
}
///
diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandBuilder.cs
rename to src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Context Menus/MessageCommandProperties.cs
rename to src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandProperties.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandBuilder.cs
rename to src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Context Menus/UserCommandProperties.cs
rename to src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandProperties.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
index faf724cd2..72045a52a 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
@@ -15,27 +15,27 @@ namespace Discord
ulong ApplicationId { get; }
///
- /// The type of the command.
+ /// Gets the type of the command.
///
ApplicationCommandType Type { get; }
///
- /// The name of the command.
+ /// Gets the name of the command.
///
string Name { get; }
///
- /// The description of the command.
+ /// Gets the description of the command.
///
string Description { get; }
///
- /// Whether the command is enabled by default when the app is added to a guild.
+ /// Gets whether the command is enabled by default when the app is added to a guild.
///
bool IsDefaultPermission { get; }
///
- /// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
+ /// Gets a collection of options for this application command.
///
IReadOnlyCollection Options { get; }
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
index c74eba8bb..3e04c2cc3 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs
@@ -5,20 +5,20 @@ namespace Discord
///
/// Represents data of an Interaction Command, see .
///
- public interface IApplicationCommandInteractionData
+ public interface IApplicationCommandInteractionData : IDiscordInteractionData
{
///
- /// The snowflake id of this command.
+ /// Gets the snowflake id of this command.
///
ulong Id { get; }
///
- /// The name of this command.
+ /// Gets the name of this command.
///
string Name { get; }
///
- /// The params + values from the user.
+ /// Gets the params + values from the user.
///
IReadOnlyCollection Options { get; }
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
index 28e37fbb6..b174ef203 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
@@ -3,30 +3,30 @@ using System.Collections.Generic;
namespace Discord
{
///
- /// Represents a option group for a command, see .
+ /// Represents a option group for a command.
///
public interface IApplicationCommandInteractionDataOption
{
///
- /// The name of the parameter.
+ /// Gets the name of the parameter.
///
string Name { get; }
///
- /// The value of the pair.
+ /// Gets the value of the pair.
///
- /// This objects type can be any one of the option types in
+ /// This objects type can be any one of the option types in .
///
///
object Value { get; }
///
- /// The type of this data's option.
+ /// Gets the type of this data's option.
///
ApplicationCommandOptionType Type { get; }
///
- /// Present if this option is a group or subcommand.
+ /// Gets the options for this command.
///
IReadOnlyCollection Options { get; }
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
index cef5b1af9..440c4bd6b 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
@@ -3,57 +3,57 @@ using System.Collections.Generic;
namespace Discord
{
///
- /// Options for the , see The docs.
+ /// Options for the .
///
public interface IApplicationCommandOption
{
///
- /// The type of this .
+ /// Gets the type of this .
///
ApplicationCommandOptionType Type { get; }
///
- /// The name of this command option, 1-32 character name.
+ /// Gets the name of this command option.
///
string Name { get; }
///
- /// The description of this command option, 1-100 character description.
+ /// Gets the description of this command option.
///
string Description { get; }
///
- /// The first required option for the user to complete--only one option can be default.
+ /// Gets whether or not this is the first required option for the user to complete.
///
bool? IsDefault { get; }
///
- /// If the parameter is required or optional, default is .
+ /// Gets whether or not the parameter is required or optional.
///
bool? IsRequired { get; }
///
- /// The smallest number value the user can input.
+ /// Gets the smallest number value the user can input.
///
double? MinValue { get; }
///
- /// The largest number value the user can input.
+ /// Gets the largest number value the user can input.
///
double? MaxValue { get; }
///
- /// Choices for string and int types for the user to pick from.
+ /// Gets the choices for string and int types for the user to pick from.
///
IReadOnlyCollection Choices { get; }
///
- /// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
+ /// Gets the sub-options for this command option.
///
IReadOnlyCollection Options { get; }
///
- /// The allowed channel types for this option.
+ /// Gets the allowed channel types for this option.
///
IReadOnlyCollection ChannelTypes { get; }
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
index ac03eb37c..631706c6f 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
@@ -6,12 +6,12 @@ namespace Discord
public interface IApplicationCommandOptionChoice
{
///
- /// 1-100 character choice name.
+ /// Gets the choice name.
///
string Name { get; }
///
- /// value of the choice.
+ /// Gets the value of the choice.
///
object Value { get; }
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
index 925ae08fc..d9e250118 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
@@ -4,36 +4,32 @@ using System.Threading.Tasks;
namespace Discord
{
///
- /// Represents a discord interaction
- ///
- /// An interaction is the base "thing" that is sent when a user invokes a command, and is the same for Slash Commands
- /// and other future interaction types. see .
- ///
+ /// Represents a discord interaction.
///
public interface IDiscordInteraction : ISnowflakeEntity
{
///
- /// The id of the interaction.
+ /// Gets the id of the interaction.
///
new ulong Id { get; }
///
- /// The type of this .
+ /// Gets the type of this .
///
InteractionType Type { get; }
///
- /// Represents the data sent within this interaction.
+ /// Gets the data sent within this interaction.
///
IDiscordInteractionData Data { get; }
///
- /// A continuation token for responding to the interaction.
+ /// Gets the continuation token for responding to the interaction.
///
string Token { get; }
///
- /// read-only property, always 1.
+ /// Gets the version of the interaction, always 1.
///
int Version { get; }
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/ActionRowComponent.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/ActionRowComponent.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonComponent.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonComponent.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/ButtonStyle.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/ButtonStyle.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentBuilder.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentBuilder.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/ComponentType.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/ComponentType.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/IMessageComponent.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/IMessageComponent.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/MessageComponent.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/MessageComponent.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuComponent.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuComponent.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs b/src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Message Components/SelectMenuOption.cs
rename to src/Discord.Net.Core/Entities/Interactions/MessageComponents/SelectMenuOption.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandBuilder.cs
rename to src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs
diff --git a/src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs
similarity index 100%
rename from src/Discord.Net.Core/Entities/Interactions/Slash Commands/SlashCommandProperties.cs
rename to src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandProperties.cs
diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs
index 847ba6835..93183161b 100644
--- a/src/Discord.Net.Rest/DiscordRestClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestClient.cs
@@ -1,8 +1,13 @@
//using Discord.Rest.Entities.Interactions;
+using Discord.Net;
+using Discord.Net.Converters;
+using Discord.Net.ED25519;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
+using System.Text;
using System.Threading.Tasks;
namespace Discord.Rest
@@ -14,12 +19,13 @@ namespace Discord.Rest
{
#region DiscordRestClient
private RestApplication _applicationInfo;
+ internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include };
///
/// Gets the logged-in user.
///
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
-
+
///
public DiscordRestClient() : this(new DiscordRestConfig()) { }
///
@@ -31,7 +37,7 @@ namespace Discord.Rest
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api) { }
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
- => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock);
+ => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock);
internal override void Dispose(bool disposing)
{
@@ -60,6 +66,70 @@ namespace Discord.Rest
return Task.Delay(0);
}
+ #region Rest interactions
+
+ public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
+ => IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
+ public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
+ {
+ var key = HexConverter.HexToByteArray(publicKey);
+ var sig = HexConverter.HexToByteArray(signature);
+ var tsp = Encoding.UTF8.GetBytes(timestamp);
+
+ var message = new List();
+ message.AddRange(tsp);
+ message.AddRange(body);
+
+ return IsValidHttpInteraction(key, sig, message.ToArray());
+ }
+
+ private bool IsValidHttpInteraction(byte[] publicKey, byte[] signature, byte[] message)
+ {
+ return Ed25519.Verify(signature, message, publicKey);
+ }
+
+ ///
+ /// Creates a from a http message.
+ ///
+ /// The public key of your application
+ /// The signature sent with the interaction.
+ /// The timestamp sent with the interaction.
+ /// The body of the http message.
+ ///
+ /// A that represents the incoming http interaction.
+ ///
+ /// Thrown when the signature doesn't match the public key.
+ public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body)
+ => ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
+
+ ///
+ /// Creates a from a http message.
+ ///
+ /// The public key of your application
+ /// The signature sent with the interaction.
+ /// The timestamp sent with the interaction.
+ /// The body of the http message.
+ ///
+ /// A that represents the incoming http interaction.
+ ///
+ /// Thrown when the signature doesn't match the public key.
+ public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body)
+ {
+ if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
+ {
+ throw new BadSignatureException();
+ }
+
+ using (var textReader = new StringReader(Encoding.UTF8.GetString(body)))
+ using (var jsonReader = new JsonTextReader(textReader))
+ {
+ var model = Serializer.Deserialize(jsonReader);
+ return await RestInteraction.CreateAsync(this, model);
+ }
+ }
+
+ #endregion
+
public async Task GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
new file mode 100644
index 000000000..165f80978
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/CommandBase/RestCommandBase.cs
@@ -0,0 +1,354 @@
+using Discord.Net.Rest;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DataModel = Discord.API.ApplicationCommandInteractionData;
+using Model = Discord.API.Interaction;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a REST-based base command interaction.
+ ///
+ public class RestCommandBase : RestInteraction
+ {
+ ///
+ /// Gets the name of the invoked command.
+ ///
+ public string CommandName
+ => Data.Name;
+
+ ///
+ /// Gets the id of the invoked command.
+ ///
+ public ulong CommandId
+ => Data.Id;
+
+ ///
+ /// The data associated with this interaction.
+ ///
+ internal new RestCommandBaseData Data { get; private set; }
+
+
+ internal override bool _hasResponded { get; set; }
+
+ private object _lock = new object();
+
+ internal RestCommandBase(DiscordRestClient client, Model model)
+ : base(client, model.Id)
+ {
+ }
+
+ internal new static async Task CreateAsync(DiscordRestClient client, Model model)
+ {
+ var entity = new RestCommandBase(client, model);
+ await entity.UpdateAsync(client, model).ConfigureAwait(false);
+ return entity;
+ }
+
+ internal override async Task UpdateAsync(DiscordRestClient client, Model model)
+ {
+ await base.UpdateAsync(client, model);
+
+ var data = model.Data.IsSpecified
+ ? (DataModel)model.Data.Value
+ : null;
+
+ if(Data == null)
+ {
+ Data = await RestCommandBaseData.CreateAsync(client, data, Guild, Channel).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Responds to an Interaction with type .
+ ///
+ /// The text of the message to be sent.
+ /// A array of embeds to send with this response. Max 10.
+ /// if the message should be read out by a text-to-speech reader, otherwise .
+ /// if the response should be hidden to everyone besides the invoker of the command, otherwise .
+ /// The allowed mentions for this response.
+ /// The request options for this response.
+ /// A to be sent with this response.
+ /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.
+ /// Message content is too long, length must be less or equal to .
+ /// The parameters provided were invalid or the token was invalid.
+ ///
+ /// A string that contains json to write back to the incoming http request.
+ ///
+ public override string Respond(
+ string text = null,
+ Embed[] embeds = null,
+ bool isTTS = false,
+ bool ephemeral = false,
+ AllowedMentions allowedMentions = null,
+ RequestOptions options = null,
+ MessageComponent component = null,
+ Embed embed = null)
+ {
+ if (!IsValidToken)
+ throw new InvalidOperationException("Interaction token is no longer valid");
+
+ if (!InteractionHelper.CanSendResponse(this))
+ throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
+
+ embeds ??= Array.Empty