diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs new file mode 100644 index 000000000..9b4e435cf --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a for making slash commands. + /// + public class ApplicationCommandOptionProperties + { + private string _name; + private string _description; + + /// + /// The name of this option. + /// + 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; + } + } + + /// + /// The description of this option. + /// + public string Description + { + get => _description; + set + { + if (value?.Length > 100) + throw new ArgumentException("Name length must be less than or equal to 32"); + _description = value; + } + } + + /// + /// The type of this option. + /// + public ApplicationCommandOptionType Type { get; set; } + + /// + /// The first required option for the user to complete. only one option can be default. + /// + public bool? Default { get; set; } + + /// + /// if this option is required for this command, otherwise . + /// + public bool? Required { get; set; } + + /// + /// choices for string and int types for the user to pick from + /// + public List Choices { get; set; } + + /// + /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. + /// + public List Options { get; set; } + + + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs new file mode 100644 index 000000000..99e3f66d6 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// Represents a choice for a . This class is used when making new commands + /// + public class ApplicationCommandOptionChoiceProperties + { + private string _name; + private object _value; + /// + /// The name of this choice + /// + public string Name + { + get => _name; + set + { + if(value?.Length > 100) + throw new ArgumentException("Name length must be less than or equal to 100"); + _name = value; + } + } + + // Note: discord allows strings & ints as values. how should that be handled? + // should we make this an object and then just type check it? + /// + /// The value of this choice + /// + public object Value + { + get => _value; + set + { + if(value != null) + { + if(!(value is int) && !(value is string)) + throw new ArgumentException("The value of a choice must be a string or int!"); + } + _value = value; + } + } + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs index 990fe2e5e..594a0f370 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs @@ -11,41 +11,20 @@ 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 => _name; - set - { - if(value.Length > 32) - throw new ArgumentException("Name length must be less than or equal to 32"); - _name = value; - } - } + public Optional Name { get; set; } /// /// Gets or sets the discription of this command. /// - 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; - } - } + public Optional Description { get; set; } /// /// 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/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs index 871eb4ae6..8ea8378e7 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs @@ -22,7 +22,7 @@ namespace Discord /// This objects type can be any one of the option types in /// /// - object? Value { get; } + object Value { get; } /// /// Present if this option is a group or subcommand. diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs index 960d1571d..25e3c7dfc 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs @@ -42,7 +42,7 @@ namespace Discord IReadOnlyCollection? Choices { get; } /// - /// if the option is a subcommand or subcommand group type, this nested options will be the parameters. + /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. /// IReadOnlyCollection? Options { get; } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs index 3b4c7c249..1f0540656 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs @@ -19,7 +19,7 @@ namespace Discord /// /// value of the choice. /// - string Value { get; } + object Value { get; } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs new file mode 100644 index 000000000..0facc02a1 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord +{ + /// + /// A class used to create slash commands + /// + public class SlashCommandCreationProperties + { + /// + /// The name of this command. + /// + public string Name { get; set; } + + /// + /// The discription of this command. + /// + public string Description { get; set; } + + + /// + /// Gets or sets the options for this command. + /// + public Optional> Options { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs index ad7748594..789c5549d 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs @@ -53,5 +53,28 @@ namespace Discord.API this.Type = cmd.Type; this.Description = cmd.Description; } + public ApplicationCommandOption(Discord.ApplicationCommandOptionProperties option) + { + this.Choices = option.Choices != null + ? option.Choices.Select(x => new ApplicationCommandOptionChoice() + { + Name = x.Name, + Value = x.Value + }).ToArray() + : Optional.Unspecified; + + this.Options = option.Options != null + ? option.Options.Select(x => new ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified; + + this.Required = option.Required.Value; + this.Default = option.Default.HasValue + ? option.Default.Value + : Optional.Unspecified; + + this.Name = option.Name; + this.Type = option.Type; + this.Description = option.Description; + } } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs index 00179dde1..b847fceba 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs @@ -13,6 +13,6 @@ namespace Discord.API public string Name { get; set; } [JsonProperty("value")] - public string Value { get; set; } + public object Value { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs similarity index 74% rename from src/Discord.Net.Rest/API/Rest/ApplicationCommandParams.cs rename to src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index a674093c5..4ab6f5a9d 100644 --- a/src/Discord.Net.Rest/API/Rest/ApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; namespace Discord.API.Rest { - internal class ApplicationCommandParams + internal class CreateApplicationCommandParams { [JsonProperty("name")] public string Name { get; set; } @@ -19,8 +19,8 @@ namespace Discord.API.Rest [JsonProperty("options")] public Optional Options { get; set; } - public ApplicationCommandParams() { } - public ApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null) + public CreateApplicationCommandParams() { } + public CreateApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null) { this.Name = name; this.Description = description; diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs new file mode 100644 index 000000000..141932955 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Rest +{ + internal class ModifyApplicationCommandParams + { + [JsonProperty("name")] + public Optional Name { get; set; } + + [JsonProperty("description")] + public Optional Description { 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 d39fcd7a2..4666d645d 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -202,23 +202,23 @@ namespace Discord.Rest }; } - public static async Task GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options) + public static async Task> GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options) { var response = await client.ApiClient.GetGlobalApplicationCommandsAsync(options).ConfigureAwait(false); if (!response.Any()) - return null; + return new RestGlobalCommand[0]; return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); } - public static async Task GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options) + 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 new RestGuildCommand[0].ToImmutableArray(); - return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToArray(); + return response.Select(x => RestGuildCommand.Create(client, x, guildId)).ToImmutableArray(); } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index d9da6503a..af33360f4 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -46,7 +46,7 @@ namespace Discord.API internal IRestClient RestClient { get; private set; } internal ulong? CurrentUserId { get; set; } public RateLimitPrecision RateLimitPrecision { get; private set; } - internal bool UseSystemClock { get; set; } + internal bool UseSystemClock { get; set; } internal JsonSerializer Serializer => _serializer; /// Unknown OAuth token type. @@ -58,7 +58,7 @@ namespace Discord.API DefaultRetryMode = defaultRetryMode; _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; RateLimitPrecision = rateLimitPrecision; - UseSystemClock = useSystemClock; + UseSystemClock = useSystemClock; RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); @@ -262,8 +262,8 @@ namespace Discord.API CheckState(); if (request.Options.RetryMode == null) request.Options.RetryMode = DefaultRetryMode; - if (request.Options.UseSystemClock == null) - request.Options.UseSystemClock = UseSystemClock; + if (request.Options.UseSystemClock == null) + request.Options.UseSystemClock = UseSystemClock; var stopwatch = Stopwatch.StartNew(); var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); @@ -787,9 +787,14 @@ namespace Discord.API //Interactions public async Task GetGlobalApplicationCommandsAsync(RequestOptions options = null) - => await SendAsync("GET", $"applications/{this.CurrentUserId}/commands", options: options).ConfigureAwait(false); + { + options = RequestOptions.CreateOrClone(options); + + return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false); + } - public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandParams command, RequestOptions options = null) + + public async Task CreateGlobalApplicationCommandAsync(CreateApplicationCommandParams command, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); @@ -797,24 +802,45 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false); + options = RequestOptions.CreateOrClone(options); + + return await SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); } - public async Task ModifyGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null) + public async Task ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); - Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); - Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); - Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false); + if (command.Name.IsSpecified) + { + Preconditions.AtMost(command.Name.Value.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Value.Length, 3, nameof(command.Name)); + } + if (command.Description.IsSpecified) + { + Preconditions.AtMost(command.Description.Value.Length, 100, nameof(command.Description)); + Preconditions.AtLeast(command.Description.Value.Length, 1, nameof(command.Description)); + } + + options = RequestOptions.CreateOrClone(options); + + return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) - => await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false); + { + options = RequestOptions.CreateOrClone(options); + + await SendAsync("DELETE", () => $"applications/{this.CurrentUserId}/commands/{commandId}", new BucketIds(), options: options).ConfigureAwait(false); + } public async Task GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = null) - => await SendAsync("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false); - public async Task CreateGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, RequestOptions options = null) + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await SendAsync("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", bucket, options: options).ConfigureAwait(false); + } + public async Task CreateGuildApplicationCommandAsync(CreateApplicationCommandParams command, ulong guildId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); @@ -822,20 +848,41 @@ namespace Discord.API Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - return await SendJsonAsync("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false); + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await SendJsonAsync("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); } - public async Task ModifyGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) + public async Task ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) { Preconditions.NotNull(command, nameof(command)); - Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); - Preconditions.AtLeast(command.Name.Length, 3, nameof(command.Name)); - Preconditions.AtMost(command.Description.Length, 100, nameof(command.Description)); - Preconditions.AtLeast(command.Description.Length, 1, nameof(command.Description)); - return await SendJsonAsync("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false); + if (command.Name.IsSpecified) + { + Preconditions.AtMost(command.Name.Value.Length, 32, nameof(command.Name)); + Preconditions.AtLeast(command.Name.Value.Length, 3, nameof(command.Name)); + } + if (command.Description.IsSpecified) + { + Preconditions.AtMost(command.Description.Value.Length, 100, nameof(command.Description)); + Preconditions.AtLeast(command.Description.Value.Length, 1, nameof(command.Description)); + } + + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + return await SendJsonAsync("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); } public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) - => await SendAsync("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); + { + options = RequestOptions.CreateOrClone(options); + + var bucket = new BucketIds(guildId: guildId); + + await SendAsync("DELETE", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", bucket, options: options).ConfigureAwait(false); + } //Interaction Responses public async Task CreateInteractionResponse(InteractionResponse response, ulong interactionId, string interactionToken, RequestOptions options = null) @@ -845,12 +892,20 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - await SendJsonAsync("POST", () => $"interactions/{interactionId}/{interactionToken}/callback", response, new BucketIds(), options: options); + await SendJsonAsync("POST", () => $"interactions/{interactionId}/{interactionToken}/callback", response, new BucketIds(), options: options); } public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) - => await SendJsonAsync("POST", $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", args, options: options); + { + options = RequestOptions.CreateOrClone(options); + + await SendJsonAsync("POST", () => $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", args, new BucketIds(), options: options); + } public async Task DeleteInteractionResponse(string interactionToken, RequestOptions options = null) - => await SendAsync("DELETE", $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", options: options); + { + options = RequestOptions.CreateOrClone(options); + + await SendAsync("DELETE", () => $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", new BucketIds(), options: options); + } public async Task CreateInteractionFollowupMessage(CreateWebhookMessageParams args, string token, RequestOptions options = null) { @@ -862,7 +917,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", $"webhooks/{CurrentUserId}/{token}?wait=true", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyInteractionFollowupMessage(CreateWebhookMessageParams args, ulong id, string token, RequestOptions options = null) @@ -875,7 +930,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", $"webhooks/{CurrentUserId}/{token}/messages/{id}", args, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", args, new BucketIds(), options: options).ConfigureAwait(false); } public async Task DeleteInteractionFollowupMessage(ulong id, string token, RequestOptions options = null) @@ -884,7 +939,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - await SendAsync("DELETE", $"webhooks/{CurrentUserId}/{token}/messages/{id}", options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false); } //Guilds diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 79fb1a5c2..d62a83d2d 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -107,13 +107,17 @@ 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) + public Task CreateGlobalCommand(SlashCommandCreationProperties properties, RequestOptions options = null) + => InteractionHelper.CreateGlobalCommand(this, properties, options); + public Task CreateGlobalCommand(Action func, RequestOptions options = null) => InteractionHelper.CreateGlobalCommand(this, func, options); - public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) + public Task CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null) + => InteractionHelper.CreateGuildCommand(this, guildId, properties, options); + public Task CreateGuildCommand(Action func, ulong guildId, RequestOptions options = null) => InteractionHelper.CreateGuildCommand(this, guildId, func, options); - public Task GetGlobalApplicationCommands(RequestOptions options = null) + public Task> GetGlobalApplicationCommands(RequestOptions options = null) => ClientHelper.GetGlobalApplicationCommands(this, options); - public Task GetGuildApplicationCommands(ulong guildId, RequestOptions options = null) + 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 341087cb4..46d92f815 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -21,30 +21,35 @@ namespace Discord.Rest // Global commands internal static async Task CreateGlobalCommand(BaseDiscordClient client, - Action func, RequestOptions options = null) + Action func, RequestOptions options = null) { - var args = new ApplicationCommandProperties(); + var args = new SlashCommandCreationProperties(); func(args); - + return await CreateGlobalCommand(client, args, options).ConfigureAwait(false); + } + internal static async Task CreateGlobalCommand(BaseDiscordClient client, + SlashCommandCreationProperties args, RequestOptions options = null) + { if (args.Options.IsSpecified) { if (args.Options.Value.Count > 10) throw new ArgumentException("Option count must be 10 or less"); } - var model = new ApplicationCommandParams() + + + var model = new CreateApplicationCommandParams() { Name = args.Name, Description = args.Description, Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified + ? args.Options.Value.Select(x => new Discord.API.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) { @@ -57,13 +62,13 @@ namespace Discord.Rest throw new ArgumentException("Option count must be 10 or less"); } - var model = new Discord.API.Rest.ApplicationCommandParams() + var model = new Discord.API.Rest.ModifyApplicationCommandParams() { Name = args.Name, Description = args.Description, Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified + ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified }; var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); @@ -82,30 +87,44 @@ namespace Discord.Rest // Guild Commands internal static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, - Action func, RequestOptions options = null) + Action func, RequestOptions options = null) { - var args = new ApplicationCommandProperties(); + var args = new SlashCommandCreationProperties(); func(args); + return await CreateGuildCommand(client, guildId, args, options).ConfigureAwait(false); + } + internal static async Task CreateGuildCommand(BaseDiscordClient client, ulong guildId, + SlashCommandCreationProperties args, RequestOptions options = null) + { + Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); + Preconditions.NotNullOrEmpty(args.Description, nameof(args.Description)); + + if (args.Options.IsSpecified) { if (args.Options.Value.Count > 10) throw new ArgumentException("Option count must be 10 or less"); + + foreach(var item in args.Options.Value) + { + Preconditions.NotNullOrEmpty(item.Name, nameof(item.Name)); + Preconditions.NotNullOrEmpty(item.Description, nameof(item.Description)); + } } - var model = new ApplicationCommandParams() + var model = new CreateApplicationCommandParams() { Name = args.Name, Description = args.Description, Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified + ? args.Options.Value.Select(x => new Discord.API.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) { @@ -118,16 +137,16 @@ namespace Discord.Rest throw new ArgumentException("Option count must be 10 or less"); } - var model = new Discord.API.Rest.ApplicationCommandParams() + var model = new Discord.API.Rest.ModifyApplicationCommandParams() { Name = args.Name, Description = args.Description, Options = args.Options.IsSpecified - ? args.Options.Value.Select(x => new ApplicationCommandOption(x)).ToArray() - : Optional.Unspecified + ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() + : Optional.Unspecified }; - var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.Id, command.GuildId, options).ConfigureAwait(false); + var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); command.Update(msg); return command; } @@ -137,7 +156,7 @@ namespace Discord.Rest Preconditions.NotNull(command, nameof(command)); Preconditions.NotEqual(command.Id, 0, nameof(command.Id)); - await client.ApiClient.DeleteGuildApplicationCommandAsync(command.Id, command.GuildId, options).ConfigureAwait(false); + await client.ApiClient.DeleteGuildApplicationCommandAsync(command.GuildId, command.Id, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index c52d69619..5e0852b29 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -19,9 +19,9 @@ namespace Discord.Rest public string Description { get; private set; } - public IReadOnlyCollection Options { get; private set; } + public IReadOnlyCollection Options { get; private set; } - public RestApplicationCommandType CommandType { get; private set; } + public RestApplicationCommandType CommandType { get; internal set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(this.Id); @@ -47,12 +47,15 @@ namespace Discord.Rest { this.ApplicationId = model.ApplicationId; this.Name = model.Name; + this.Description = model.Description; this.Options = model.Options.IsSpecified ? model.Options.Value.Select(x => RestApplicationCommandOption.Create(x)).ToImmutableArray() : null; } + IReadOnlyCollection IApplicationCommand.Options => Options; + 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 index 211d2fe12..f9ab50f60 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs @@ -11,7 +11,7 @@ namespace Discord.Rest { public string Name { get; } - public string Value { get; } + public object Value { get; } internal RestApplicationCommandChoice(Model model) { diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs index 946319112..120fe0c29 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs @@ -20,9 +20,9 @@ namespace Discord.Rest public bool? Required { get; private set; } - public IReadOnlyCollection Choices { get; private set; } + public IReadOnlyCollection Choices { get; private set; } - public IReadOnlyCollection Options { get; private set; } + public IReadOnlyCollection Options { get; private set; } internal RestApplicationCommandOption() { } @@ -53,5 +53,8 @@ namespace Discord.Rest ? model.Choices.Value.Select(x => new RestApplicationCommandChoice(x)).ToImmutableArray() : null; } + + IReadOnlyCollection IApplicationCommandOption.Options => Options; + IReadOnlyCollection IApplicationCommandOption.Choices => Choices; } } diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs index d44e6819d..5223c069c 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs @@ -15,7 +15,7 @@ namespace Discord.Rest internal RestGlobalCommand(BaseDiscordClient client, ulong id) : base(client, id) { - + this.CommandType = RestApplicationCommandType.GlobalCommand; } internal static RestGlobalCommand Create(BaseDiscordClient client, Model model) diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs index 5bf386051..73737dcd2 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs @@ -13,6 +13,7 @@ namespace Discord.Rest internal RestGuildCommand(BaseDiscordClient client, ulong id, ulong guildId) : base(client, id) { + this.CommandType = RestApplicationCommandType.GuildCommand; this.GuildId = guildId; } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs new file mode 100644 index 000000000..94b3470e7 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.API.Gateway +{ + internal class ApplicationCommandCreatedUpdatedEvent + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("options")] + public List Options { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 7b8a245cc..af1a9f147 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -45,7 +45,8 @@ namespace Discord.WebSocket /// /// - public event Func ChannelDestroyed { + public event Func ChannelDestroyed + { add { _channelDestroyedEvent.Add(value); } remove { _channelDestroyedEvent.Remove(value); } } @@ -67,7 +68,8 @@ namespace Discord.WebSocket /// /// - public event Func ChannelUpdated { + public event Func ChannelUpdated + { add { _channelUpdatedEvent.Add(value); } remove { _channelUpdatedEvent.Remove(value); } } @@ -92,7 +94,8 @@ namespace Discord.WebSocket /// /// - public event Func MessageReceived { + public event Func MessageReceived + { add { _messageReceivedEvent.Add(value); } remove { _messageReceivedEvent.Remove(value); } } @@ -124,7 +127,8 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, Task> MessageDeleted { + public event Func, ISocketMessageChannel, Task> MessageDeleted + { add { _messageDeletedEvent.Add(value); } remove { _messageDeletedEvent.Remove(value); } } @@ -182,7 +186,8 @@ namespace Discord.WebSocket /// parameter. /// /// - public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated + { add { _messageUpdatedEvent.Add(value); } remove { _messageUpdatedEvent.Remove(value); } } @@ -217,19 +222,22 @@ namespace Discord.WebSocket /// /// - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded + { add { _reactionAddedEvent.Add(value); } remove { _reactionAddedEvent.Remove(value); } } internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); /// Fired when a reaction is removed from a message. - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved + { add { _reactionRemovedEvent.Add(value); } remove { _reactionRemovedEvent.Remove(value); } } internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); /// Fired when all reactions to a message are cleared. - public event Func, ISocketMessageChannel, Task> ReactionsCleared { + public event Func, ISocketMessageChannel, Task> ReactionsCleared + { add { _reactionsClearedEvent.Add(value); } remove { _reactionsClearedEvent.Remove(value); } } @@ -259,19 +267,22 @@ namespace Discord.WebSocket //Roles /// Fired when a role is created. - public event Func RoleCreated { + public event Func RoleCreated + { add { _roleCreatedEvent.Add(value); } remove { _roleCreatedEvent.Remove(value); } } internal readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); /// Fired when a role is deleted. - public event Func RoleDeleted { + public event Func RoleDeleted + { add { _roleDeletedEvent.Add(value); } remove { _roleDeletedEvent.Remove(value); } } internal readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); /// Fired when a role is updated. - public event Func RoleUpdated { + public event Func RoleUpdated + { add { _roleUpdatedEvent.Add(value); } remove { _roleUpdatedEvent.Remove(value); } } @@ -279,37 +290,43 @@ namespace Discord.WebSocket //Guilds /// Fired when the connected account joins a guild. - public event Func JoinedGuild { + public event Func JoinedGuild + { add { _joinedGuildEvent.Add(value); } remove { _joinedGuildEvent.Remove(value); } } internal readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); /// Fired when the connected account leaves a guild. - public event Func LeftGuild { + public event Func LeftGuild + { add { _leftGuildEvent.Add(value); } remove { _leftGuildEvent.Remove(value); } } internal readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); /// Fired when a guild becomes available. - public event Func GuildAvailable { + public event Func GuildAvailable + { add { _guildAvailableEvent.Add(value); } remove { _guildAvailableEvent.Remove(value); } } internal readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); /// Fired when a guild becomes unavailable. - public event Func GuildUnavailable { + public event Func GuildUnavailable + { add { _guildUnavailableEvent.Add(value); } remove { _guildUnavailableEvent.Remove(value); } } internal readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); /// Fired when offline guild members are downloaded. - public event Func GuildMembersDownloaded { + public event Func GuildMembersDownloaded + { add { _guildMembersDownloadedEvent.Add(value); } remove { _guildMembersDownloadedEvent.Remove(value); } } internal readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); /// Fired when a guild is updated. - public event Func GuildUpdated { + public event Func GuildUpdated + { add { _guildUpdatedEvent.Add(value); } remove { _guildUpdatedEvent.Remove(value); } } @@ -317,43 +334,50 @@ namespace Discord.WebSocket //Users /// Fired when a user joins a guild. - public event Func UserJoined { + public event Func UserJoined + { add { _userJoinedEvent.Add(value); } remove { _userJoinedEvent.Remove(value); } } internal readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); /// Fired when a user leaves a guild. - public event Func UserLeft { + public event Func UserLeft + { add { _userLeftEvent.Add(value); } remove { _userLeftEvent.Remove(value); } } internal readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); /// Fired when a user is banned from a guild. - public event Func UserBanned { + public event Func UserBanned + { add { _userBannedEvent.Add(value); } remove { _userBannedEvent.Remove(value); } } internal readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); /// Fired when a user is unbanned from a guild. - public event Func UserUnbanned { + public event Func UserUnbanned + { add { _userUnbannedEvent.Add(value); } remove { _userUnbannedEvent.Remove(value); } } internal readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); /// Fired when a user is updated. - public event Func UserUpdated { + public event Func UserUpdated + { add { _userUpdatedEvent.Add(value); } remove { _userUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); /// Fired when a guild member is updated, or a member presence is updated. - public event Func GuildMemberUpdated { + public event Func GuildMemberUpdated + { add { _guildMemberUpdatedEvent.Add(value); } remove { _guildMemberUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); /// Fired when a user joins, leaves, or moves voice channels. - public event Func UserVoiceStateUpdated { + public event Func UserVoiceStateUpdated + { add { _userVoiceStateUpdatedEvent.Add(value); } remove { _userVoiceStateUpdatedEvent.Remove(value); } } @@ -366,25 +390,29 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _voiceServerUpdatedEvent = new AsyncEvent>(); /// Fired when the connected account is updated. - public event Func CurrentUserUpdated { + public event Func CurrentUserUpdated + { add { _selfUpdatedEvent.Add(value); } remove { _selfUpdatedEvent.Remove(value); } } internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); /// Fired when a user starts typing. - public event Func UserIsTyping { + public event Func UserIsTyping + { add { _userIsTypingEvent.Add(value); } remove { _userIsTypingEvent.Remove(value); } } internal readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); /// Fired when a user joins a group channel. - public event Func RecipientAdded { + public event Func RecipientAdded + { add { _recipientAddedEvent.Add(value); } remove { _recipientAddedEvent.Remove(value); } } internal readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); /// Fired when a user is removed from a group channel. - public event Func RecipientRemoved { + public event Func RecipientRemoved + { add { _recipientRemovedEvent.Add(value); } remove { _recipientRemovedEvent.Remove(value); } } @@ -452,5 +480,70 @@ namespace Discord.WebSocket } internal readonly AsyncEvent> _interactionCreatedEvent = new AsyncEvent>(); + /// + /// Fired when a guild application command is created. + /// + /// + /// + /// This event is fired when an application command is created. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandCreated + { + add { _applicationCommandCreated.Add(value); } + remove { _applicationCommandCreated.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandCreated = new AsyncEvent>(); + + /// + /// Fired when a guild application command is updated. + /// + /// + /// + /// This event is fired when an application command is updated. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandUpdated + { + add { _applicationCommandUpdated.Add(value); } + remove { _applicationCommandUpdated.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandUpdated = new AsyncEvent>(); + + /// + /// Fired when a guild application command is deleted. + /// + /// + /// + /// This event is fired when an application command is deleted. The event handler must return a + /// and accept a as its parameter. + /// + /// + /// The command that was deleted will be passed into the parameter. + /// + /// + /// This event is an undocumented discord event and may break at any time, its not recommended to rely on this event + /// + /// + public event Func ApplicationCommandDeleted + { + add { _applicationCommandDeleted.Add(value); } + remove { _applicationCommandDeleted.Remove(value); } + } + internal readonly AsyncEvent> _applicationCommandDeleted = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index e2029e471..115367e6f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -613,7 +613,7 @@ namespace Discord.WebSocket } else if (_connection.CancelToken.IsCancellationRequested) return; - + if (BaseConfig.AlwaysDownloadUsers) _ = DownloadUsersAsync(Guilds.Where(x => x.IsAvailable && !x.HasAllMembers)); @@ -1808,6 +1808,60 @@ namespace Discord.WebSocket } } break; + case "APPLICATION_COMMAND_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_CREATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + if(guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + await TimedInvokeAsync(_applicationCommandCreated, nameof(ApplicationCommandCreated), applicationCommand).ConfigureAwait(false); + } + break; + case "APPLICATION_COMMAND_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_UPDATE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + await TimedInvokeAsync(_applicationCommandUpdated, nameof(ApplicationCommandUpdated), applicationCommand).ConfigureAwait(false); + } + break; + case "APPLICATION_COMMAND_DELETE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (APPLICATION_COMMAND_DELETE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + + var guild = State.GetGuild(data.GuildId); + if (guild == null) + { + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + return; + } + + var applicationCommand = SocketApplicationCommand.Create(this, data); + + await TimedInvokeAsync(_applicationCommandDeleted, nameof(ApplicationCommandDeleted), applicationCommand).ConfigureAwait(false); + } + break; //Ignored (User only) case "CHANNEL_PINS_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index e9e535998..35797dc78 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1006,6 +1006,18 @@ namespace Discord.WebSocket public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); + //Interactions + /// + /// Gets this guilds slash commands commands + /// + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous get operation. The task result contains a read-only collection + /// of application commands found within the guild. + /// + public async Task> GetApplicationCommandsAsync(RequestOptions options = null) + => await Discord.Rest.GetGuildApplicationCommands(this.Id, options); + //Emotes /// public Task GetEmoteAsync(ulong id, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommand.cs new file mode 100644 index 000000000..c8cc4d3a2 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommand.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.Gateway.ApplicationCommandCreatedUpdatedEvent; + +namespace Discord.WebSocket +{ + public class SocketApplicationCommand : SocketEntity, 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 DateTimeOffset CreatedAt + => SnowflakeUtils.FromSnowflake(this.Id); + + public SocketGuild Guild + => Discord.GetGuild(this.GuildId); + private ulong GuildId { get; set; } + + internal SocketApplicationCommand(DiscordSocketClient client, ulong id) + : base(client, id) + { + + } + internal static SocketApplicationCommand Create(DiscordSocketClient client, Model model) + { + var entity = new SocketApplicationCommand(client, model.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + this.ApplicationId = model.ApplicationId; + this.Description = model.Description; + this.Name = model.Name; + this.GuildId = model.GuildId; + + this.Options = model.Options.Any() + ? model.Options.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() + : new ImmutableArray(); + } + + public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException(); + IReadOnlyCollection IApplicationCommand.Options => Options; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandChoice.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandChoice.cs new file mode 100644 index 000000000..f82f9e6c4 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandChoice.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Model = Discord.API.ApplicationCommandOptionChoice; + +namespace Discord.WebSocket +{ + /// + /// Represents a choice for a + /// + public class SocketApplicationCommandChoice : IApplicationCommandOptionChoice + { + public string Name { get; private set; } + + public object Value { get; private set; } + + internal SocketApplicationCommandChoice() { } + internal static SocketApplicationCommandChoice Create(Model model) + { + var entity = new SocketApplicationCommandChoice(); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + this.Name = model.Name; + this.Value = model.Value; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandOption.cs new file mode 100644 index 000000000..e856bce42 --- /dev/null +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandOption.cs @@ -0,0 +1,70 @@ +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.WebSocket +{ + /// + /// Represents an option for a + /// + public class SocketApplicationCommandOption : IApplicationCommandOption + { + public string Name { get; private set; } + + public ApplicationCommandOptionType Type { get; private set; } + + public string Description { get; private set; } + + public bool? Default { get; private set; } + + public bool? Required { get; private set; } + + /// + /// Choices for string and int types for the user to pick from. + /// + public IReadOnlyCollection Choices { get; private set; } + + /// + /// If the option is a subcommand or subcommand group type, this nested options will be the parameters. + /// + public IReadOnlyCollection Options { get; private set; } + + internal SocketApplicationCommandOption() { } + internal static SocketApplicationCommandOption Create(Model model) + { + var entity = new SocketApplicationCommandOption(); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + this.Name = model.Name; + this.Type = model.Type; + this.Description = model.Description; + + this.Default = model.Default.IsSpecified + ? model.Default.Value + : null; + + this.Required = model.Required.IsSpecified + ? model.Required.Value + : null; + + this.Choices = model.Choices.IsSpecified + ? model.Choices.Value.Select(x => SocketApplicationCommandChoice.Create(x)).ToImmutableArray() + : new ImmutableArray(); + + this.Options = model.Options.IsSpecified + ? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray() + : new ImmutableArray(); + } + + IReadOnlyCollection IApplicationCommandOption.Choices => Choices; + IReadOnlyCollection IApplicationCommandOption.Options => Options; + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index f51d0a8f8..f8c2c62bd 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -39,7 +39,7 @@ namespace Discord.WebSocket /// /// The data associated with this interaction /// - public IApplicationCommandInteractionData Data { get; private set; } + public SocketInteractionData Data { get; private set; } /// /// The token used to respond to this interaction @@ -209,5 +209,7 @@ namespace Discord.WebSocket await Discord.Rest.ApiClient.CreateInteractionResponse(response, this.Id, Token, options).ConfigureAwait(false); } + + IApplicationCommandInteractionData IDiscordInteraction.Data => Data; } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs index 4ff582480..b6dfd2f8e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs @@ -11,7 +11,7 @@ namespace Discord.WebSocket public class SocketInteractionData : SocketEntity, IApplicationCommandInteractionData { public string Name { get; private set; } - public IReadOnlyCollection Options { get; private set; } + public IReadOnlyCollection Options { get; private set; } internal SocketInteractionData(DiscordSocketClient client, ulong id) : base(client, id) @@ -33,5 +33,7 @@ namespace Discord.WebSocket : null; } + + IReadOnlyCollection IApplicationCommandInteractionData.Options => Options; } }