diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
index 7fa28c8d0..990fe2e5e 100644
--- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
@@ -11,19 +11,41 @@ namespace Discord
///
public class ApplicationCommandProperties
{
+ private string _name { get; set; }
+ private string _description { get; set; }
+
///
/// Gets or sets the name of this command.
///
- public string Name { get; set; }
+ public string Name
+ {
+ get => _name;
+ set
+ {
+ if(value.Length > 32)
+ throw new ArgumentException("Name length must be less than or equal to 32");
+ _name = value;
+ }
+ }
///
/// Gets or sets the discription of this command.
///
- public string Description { get; set; }
+ public string Description
+ {
+ get => _description;
+ set
+ {
+ if (value.Length > 100)
+ throw new ArgumentException("Description length must be less than or equal to 100");
+ _description = value;
+ }
+ }
+
///
/// Gets or sets the options for this command.
///
- public Optional> Options { get; set; }
+ public Optional> Options { get; set; }
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
index 122f2c599..c094efbd8 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs
@@ -34,16 +34,13 @@ namespace Discord
///
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
///
- IEnumerable? Options { get; }
+ IReadOnlyCollection Options { get; }
///
- /// Modifies this command.
+ /// Deletes this command
///
- /// The delegate containing the properties to modify the command with.
/// The options to be used when sending the request.
- ///
- /// A task that represents the asynchronous modification operation.
- ///
- Task ModifyAsync(Action func, RequestOptions options = null);
+ ///
+ Task DeleteAsync(RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
index 14e25a26e..960d1571d 100644
--- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
@@ -39,11 +39,11 @@ namespace Discord
///
/// Choices for string and int types for the user to pick from.
///
- IEnumerable? Choices { get; }
+ IReadOnlyCollection? Choices { get; }
///
/// if the option is a subcommand or subcommand group type, this nested options will be the parameters.
///
- IEnumerable? Options { get; }
+ IReadOnlyCollection? Options { get; }
}
}
diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
index 9ae240e43..f42837450 100644
--- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
+++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs
@@ -1,3 +1,4 @@
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,10 +9,15 @@ namespace Discord.API
{
internal class ApplicationCommand
{
+ [JsonProperty("id")]
public ulong Id { get; set; }
+ [JsonProperty("application_id")]
public ulong ApplicationId { get; set; }
+ [JsonProperty("name")]
public string Name { get; set; }
+ [JsonProperty("description")]
public string Description { get; set; }
- public ApplicationCommand[] Options { get; set; }
+ [JsonProperty("options")]
+ public Optional Options { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs
index 8910e999a..d39fcd7a2 100644
--- a/src/Discord.Net.Rest/ClientHelper.cs
+++ b/src/Discord.Net.Rest/ClientHelper.cs
@@ -201,5 +201,24 @@ namespace Discord.Rest
}
};
}
+
+ public static async Task GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options)
+ {
+ var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false);
+
+ if (!response.Any())
+ return null;
+
+ return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray();
+ }
+ public static async Task GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options)
+ {
+ var response = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, options).ConfigureAwait(false);
+
+ if (!response.Any())
+ return null;
+
+ return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray();
+ }
}
}
diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs
index 48c40fdfa..79fb1a5c2 100644
--- a/src/Discord.Net.Rest/DiscordRestClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestClient.cs
@@ -107,6 +107,14 @@ namespace Discord.Rest
=> ClientHelper.GetVoiceRegionAsync(this, id, options);
public Task GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options);
+ public Task CreateGobalCommand(Action func, RequestOptions options = null)
+ => InteractionHelper.CreateGlobalCommand(this, func, options);
+ public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null)
+ => InteractionHelper.CreateGuildCommand(this, guildId, func, options);
+ public Task GetGlobalApplicationCommands(RequestOptions options = null)
+ => ClientHelper.GetGlobalApplicationCommands(this, options);
+ public Task GetGuildApplicationCommands(ulong guildId, RequestOptions options = null)
+ => ClientHelper.GetGuildApplicationCommands(this, guildId, options);
//IDiscordClient
///
diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
index a275a7d0a..341087cb4 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
@@ -1,4 +1,5 @@
using Discord.API;
+using Discord.API.Rest;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,10 +13,131 @@ namespace Discord.Rest
internal static async Task SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args,
string token, IMessageChannel channel, RequestOptions options = null)
{
- var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options);
+ var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options).ConfigureAwait(false);
var entity = RestUserMessage.Create(client, channel, client.CurrentUser, model);
return entity;
}
+
+ // Global commands
+ internal static async Task CreateGlobalCommand(BaseDiscordClient client,
+ Action func, RequestOptions options = null)
+ {
+ var args = new ApplicationCommandProperties();
+ func(args);
+
+ if (args.Options.IsSpecified)
+ {
+ if (args.Options.Value.Count > 10)
+ throw new ArgumentException("Option count must be 10 or less");
+ }
+
+ var model = new ApplicationCommandParams()
+ {
+ Name = args.Name,
+ Description = args.Description,
+ Options = args.Options.IsSpecified
+ ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray()
+ : Optional.Unspecified
+ };
+
+ var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false);
+ return RestGlobalCommand.Create(client, cmd);
+ }
+
+ internal static async Task ModifyGlobalCommand(BaseDiscordClient client, RestGlobalCommand command,
+ Action func, RequestOptions options = null)
+ {
+ ApplicationCommandProperties args = new ApplicationCommandProperties();
+ func(args);
+
+ if (args.Options.IsSpecified)
+ {
+ if (args.Options.Value.Count > 10)
+ throw new ArgumentException("Option count must be 10 or less");
+ }
+
+ var model = new Discord.API.Rest.ApplicationCommandParams()
+ {
+ Name = args.Name,
+ Description = args.Description,
+ Options = args.Options.IsSpecified
+ ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray()
+ : Optional.Unspecified
+ };
+
+ var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false);
+ command.Update(msg);
+ return command;
+ }
+
+
+ internal static async Task DeleteGlobalCommand(BaseDiscordClient client, RestGlobalCommand command, RequestOptions options = null)
+ {
+ Preconditions.NotNull(command, nameof(command));
+ Preconditions.NotEqual(command.Id, 0, nameof(command.Id));
+
+ await client.ApiClient.DeleteGlobalApplicationCommandAsync(command.Id, options).ConfigureAwait(false);
+ }
+
+ // Guild Commands
+ internal static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId,
+ Action func, RequestOptions options = null)
+ {
+ var args = new ApplicationCommandProperties();
+ func(args);
+
+ if (args.Options.IsSpecified)
+ {
+ if (args.Options.Value.Count > 10)
+ throw new ArgumentException("Option count must be 10 or less");
+ }
+
+ var model = new ApplicationCommandParams()
+ {
+ Name = args.Name,
+ Description = args.Description,
+ Options = args.Options.IsSpecified
+ ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray()
+ : Optional.Unspecified
+ };
+
+ var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false);
+ return RestGuildCommand.Create(client, cmd, guildId);
+ }
+
+ internal static async Task ModifyGuildCommand(BaseDiscordClient client, RestGuildCommand command,
+ Action func, RequestOptions options = null)
+ {
+ ApplicationCommandProperties args = new ApplicationCommandProperties();
+ func(args);
+
+ if (args.Options.IsSpecified)
+ {
+ if (args.Options.Value.Count > 10)
+ throw new ArgumentException("Option count must be 10 or less");
+ }
+
+ var model = new Discord.API.Rest.ApplicationCommandParams()
+ {
+ Name = args.Name,
+ Description = args.Description,
+ Options = args.Options.IsSpecified
+ ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray()
+ : Optional.Unspecified
+ };
+
+ var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.Id, command.GuildId, options).ConfigureAwait(false);
+ command.Update(msg);
+ return command;
+ }
+
+ internal static async Task DeleteGuildCommand(BaseDiscordClient client, RestGuildCommand command, RequestOptions options = null)
+ {
+ Preconditions.NotNull(command, nameof(command));
+ Preconditions.NotEqual(command.Id, 0, nameof(command.Id));
+
+ await client.ApiClient.DeleteGuildApplicationCommandAsync(command.Id, command.GuildId, options).ConfigureAwait(false);
+ }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
new file mode 100644
index 000000000..c52d69619
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ApplicationCommand;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a rest implementation of the
+ ///
+ public abstract class RestApplicationCommand : RestEntity, IApplicationCommand
+ {
+ public ulong ApplicationId { get; private set; }
+
+ public string Name { get; private set; }
+
+ public string Description { get; private set; }
+
+ public IReadOnlyCollection Options { get; private set; }
+
+ public RestApplicationCommandType CommandType { get; private set; }
+
+ public DateTimeOffset CreatedAt
+ => SnowflakeUtils.FromSnowflake(this.Id);
+
+ internal RestApplicationCommand(BaseDiscordClient client, ulong id)
+ : base(client, id)
+ {
+
+ }
+
+ internal static RestApplicationCommand Create(BaseDiscordClient client, Model model, RestApplicationCommandType type, ulong guildId = 0)
+ {
+ if (type == RestApplicationCommandType.GlobalCommand)
+ return RestGlobalCommand.Create(client, model);
+
+ if (type == RestApplicationCommandType.GuildCommand)
+ return RestGuildCommand.Create(client, model, guildId);
+
+ return null;
+ }
+
+ internal virtual void Update(Model model)
+ {
+ this.ApplicationId = model.ApplicationId;
+ this.Name = model.Name;
+
+ this.Options = model.Options.IsSpecified
+ ? model.Options.Value.Select(x => RestApplicationCommandOption.Create(x)).ToImmutableArray()
+ : null;
+ }
+
+ public virtual Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs
new file mode 100644
index 000000000..211d2fe12
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ApplicationCommandOptionChoice;
+
+namespace Discord.Rest
+{
+ public class RestApplicationCommandChoice : IApplicationCommandOptionChoice
+ {
+ public string Name { get; }
+
+ public string Value { get; }
+
+ internal RestApplicationCommandChoice(Model model)
+ {
+ this.Name = model.Name;
+ this.Value = model.Value;
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
new file mode 100644
index 000000000..946319112
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ApplicationCommandOption;
+
+namespace Discord.Rest
+{
+ public class RestApplicationCommandOption : IApplicationCommandOption
+ {
+ public ApplicationCommandOptionType Type { get; private set; }
+
+ public string Name { get; private set; }
+
+ public string Description { get; private set; }
+
+ public bool? Default { get; private set; }
+
+ public bool? Required { get; private set; }
+
+ public IReadOnlyCollection Choices { get; private set; }
+
+ public IReadOnlyCollection Options { get; private set; }
+
+ internal RestApplicationCommandOption() { }
+
+ internal static RestApplicationCommandOption Create(Model model)
+ {
+ var options = new RestApplicationCommandOption();
+ options.Update(model);
+ return options;
+ }
+
+ internal void Update(Model model)
+ {
+ this.Type = model.Type;
+ this.Name = model.Name;
+ this.Description = model.Description;
+
+ if (model.Default.IsSpecified)
+ this.Default = model.Default.Value;
+
+ if (model.Required.IsSpecified)
+ this.Required = model.Required.Value;
+
+ this.Options = model.Options.IsSpecified
+ ? model.Options.Value.Select(x => Create(x)).ToImmutableArray()
+ : null;
+
+ this.Choices = model.Choices.IsSpecified
+ ? model.Choices.Value.Select(x => new RestApplicationCommandChoice(x)).ToImmutableArray()
+ : null;
+ }
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs
new file mode 100644
index 000000000..7ea7cd9f0
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandType.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord.Rest
+{
+ public enum RestApplicationCommandType
+ {
+ GlobalCommand,
+ GuildCommand
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs
new file mode 100644
index 000000000..d44e6819d
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ApplicationCommand;
+
+namespace Discord.Rest
+{
+ ///
+ /// Represents a global Slash command
+ ///
+ public class RestGlobalCommand : RestApplicationCommand
+ {
+ internal RestGlobalCommand(BaseDiscordClient client, ulong id)
+ : base(client, id)
+ {
+
+ }
+
+ internal static RestGlobalCommand Create(BaseDiscordClient client, Model model)
+ {
+ var entity = new RestGlobalCommand(client, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+ public override async Task DeleteAsync(RequestOptions options = null)
+ => await InteractionHelper.DeleteGlobalCommand(Discord, this).ConfigureAwait(false);
+
+ ///
+ /// Modifies this .
+ ///
+ /// The delegate containing the properties to modify the command with.
+ /// The options to be used when sending the request.
+ ///
+ /// The modified command
+ ///
+ public async Task ModifyAsync(Action func, RequestOptions options = null)
+ => await InteractionHelper.ModifyGlobalCommand(Discord, this, func, options).ConfigureAwait(false);
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs
new file mode 100644
index 000000000..5bf386051
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Model = Discord.API.ApplicationCommand;
+
+namespace Discord.Rest
+{
+ public class RestGuildCommand : RestApplicationCommand
+ {
+ public ulong GuildId { get; set; }
+ internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId)
+ : base(client, id)
+ {
+ this.GuildId = guildId;
+ }
+
+ internal static RestGuildCommand Create(BaseDiscordClient client, Model model, ulong guildId)
+ {
+ var entity = new RestGuildCommand(client, model.Id, guildId);
+ entity.Update(model);
+ return entity;
+ }
+
+ public override async Task DeleteAsync(RequestOptions options = null)
+ => await InteractionHelper.DeleteGuildCommand(Discord, this).ConfigureAwait(false);
+
+ ///
+ /// Modifies this .
+ ///
+ /// The delegate containing the properties to modify the command with.
+ /// The options to be used when sending the request.
+ ///
+ /// The modified command
+ ///
+ public async Task ModifyAsync(Action func, RequestOptions options = null)
+ => await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false);
+ }
+}
diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
index b93f6d33d..171094ade 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs
@@ -110,20 +110,20 @@ namespace Discord.WebSocket
///
///
///
- /// Discord interactions will not go thru in chat until the client responds to them. With this option set to
- /// the client will automatically acknowledge the interaction with .
- /// see the docs on
- /// responding to interactions for more info
+ /// Discord interactions will not appear in chat until the client responds to them. With this option set to
+ /// , the client will automatically acknowledge the interaction with .
+ /// See the docs on
+ /// responding to interactions for more info.
///
///
- /// With this option set to you will have to acknowledge the interaction with
- /// ,
- /// only after the interaction is captured the origional slash command message will be visible.
+ /// With this option set to , you will have to acknowledge the interaction with
+ /// .
+ /// Only after the interaction is acknowledged, the origional slash command message will be visible.
///
///
/// Please note that manually acknowledging the interaction with a message reply will not provide any return data.
- /// By autmatically acknowledging the interaction without sending the message will allow for follow up responses to
- /// be used, follow up responses return the message data sent.
+ /// Automatically acknowledging the interaction without sending the message will allow for follow up responses to
+ /// be used; follow up responses return the message data sent.
///
///
public bool AlwaysAcknowledgeInteractions { get; set; } = true;
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
index 1eb8fdca0..f51d0a8f8 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
@@ -95,10 +95,10 @@ namespace Discord.WebSocket
}
///
- /// Responds to an Interaction, eating its input
+ /// Responds to an Interaction.
///
- /// If you have set to , this method
- /// will be obsolete and will use
+ /// If you have set to , You should use
+ /// instead.
///
///
/// The text of the message to be sent
@@ -111,10 +111,11 @@ namespace Discord.WebSocket
/// The sent as the response. If this is the first acknowledgement, it will return null;
///
/// Message content is too long, length must be less or equal to .
+ /// The parameters provided were invalid or the token was invalid
public async Task RespondAsync(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType Type = InteractionResponseType.ChannelMessageWithSource, AllowedMentions allowedMentions = null, RequestOptions options = null)
{
- if (Type == InteractionResponseType.ACKWithSource || Type == InteractionResponseType.ACKWithSource || Type == InteractionResponseType.Pong)
+ if (Type == InteractionResponseType.Pong)
throw new InvalidOperationException($"Cannot use {Type} on a send message function");
if (!IsValidToken)