| @@ -1,6 +1,6 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <Import Project="../../Discord.Net.targets" /> | |||
| <Import Project="../../StyleAnalyzer.targets"/> | |||
| <Import Project="../../StyleAnalyzer.targets" /> | |||
| <PropertyGroup> | |||
| <AssemblyName>Discord.Net.Commands</AssemblyName> | |||
| <RootNamespace>Discord.Commands</RootNamespace> | |||
| @@ -0,0 +1,23 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// The option type of the Slash command parameter, See <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype"/> | |||
| /// </summary> | |||
| public enum ApplicationCommandOptionType : byte | |||
| { | |||
| SubCommand = 1, | |||
| SubCommandGroup = 2, | |||
| String = 3, | |||
| Integer = 4, | |||
| Boolean = 5, | |||
| User = 6, | |||
| Channel = 7, | |||
| Role = 8 | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public class ApplicationCommandProperties | |||
| { | |||
| public string Name { get; set; } | |||
| public string Description { get; set; } | |||
| public Optional<IEnumerable<IApplicationCommandOption>> Options { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// The base command model that belongs to an application. see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommand"/> | |||
| /// </summary> | |||
| public interface IApplicationCommand : ISnowflakeEntity | |||
| { | |||
| /// <summary> | |||
| /// Gets the unique id of the command | |||
| /// </summary> | |||
| ulong Id { get; } | |||
| /// <summary> | |||
| /// Gets the unique id of the parent application | |||
| /// </summary> | |||
| ulong ApplicationId { get; } | |||
| /// <summary> | |||
| /// The name of the command | |||
| /// </summary> | |||
| string Name { get; } | |||
| /// <summary> | |||
| /// The description of the command | |||
| /// </summary> | |||
| string Description { get; } | |||
| IEnumerable<IApplicationCommandOption>? Options { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public interface IApplicationCommandInteractionData | |||
| { | |||
| ulong Id { get; } | |||
| string Name { get; } | |||
| IEnumerable<IApplicationCommandInteractionDataOption> Options { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public interface IApplicationCommandInteractionDataOption | |||
| { | |||
| string Name { get; } | |||
| ApplicationCommandOptionType Value { get; } | |||
| IEnumerable<IApplicationCommandInteractionDataOption> Options { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Options for the <see cref="IApplicationCommand"/>, see <see href="https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption"/> | |||
| /// </summary> | |||
| public interface IApplicationCommandOption | |||
| { | |||
| /// <summary> | |||
| /// The type of this <see cref="IApplicationCommandOption"/> | |||
| /// </summary> | |||
| ApplicationCommandOptionType Type { get; } | |||
| /// <summary> | |||
| /// The name of this command option, 1-32 character name. | |||
| /// </summary> | |||
| string Name { get; } | |||
| /// <summary> | |||
| /// The discription of this command option, 1-100 character description | |||
| /// </summary> | |||
| string Description { get; } | |||
| /// <summary> | |||
| /// the first required option for the user to complete--only one option can be default | |||
| /// </summary> | |||
| bool? Default { get; } | |||
| /// <summary> | |||
| /// if the parameter is required or optional--default <see langword="false"/> | |||
| /// </summary> | |||
| bool? Required { get; } | |||
| /// <summary> | |||
| /// choices for string and int types for the user to pick from | |||
| /// </summary> | |||
| IEnumerable<IApplicationCommandOptionChoice>? Choices { get; } | |||
| /// <summary> | |||
| /// if the option is a subcommand or subcommand group type, this nested options will be the parameters | |||
| /// </summary> | |||
| IEnumerable<IApplicationCommandOption>? Options { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public interface IApplicationCommandOptionChoice | |||
| { | |||
| /// <summary> | |||
| /// 1-100 character choice name | |||
| /// </summary> | |||
| string Name { get; } | |||
| /// <summary> | |||
| /// value of the choice | |||
| /// </summary> | |||
| string Value { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// 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 <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction"/> | |||
| /// </summary> | |||
| public interface IDiscordInteraction : ISnowflakeEntity | |||
| { | |||
| /// <summary> | |||
| /// id of the interaction | |||
| /// </summary> | |||
| ulong Id { get; } | |||
| InteractionType Type { get; } | |||
| IApplicationCommandInteractionData? Data { get; } | |||
| ulong GuildId { get; } | |||
| ulong ChannelId { get; } | |||
| IGuildUser Member { get; } | |||
| string Token { get; } | |||
| int Version { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| public enum InteractionType : byte | |||
| { | |||
| Ping = 1, | |||
| ApplicationCommand = 2 | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API | |||
| { | |||
| internal class ApplicationCommand | |||
| { | |||
| public ulong Id { get; set; } | |||
| public ulong ApplicationId { get; set; } | |||
| public string Name { get; set; } | |||
| public string Description { get; set; } | |||
| public IEnumerable<ApplicationCommand> | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| internal class ApplicationCommandInteractionData | |||
| { | |||
| [JsonProperty("id")] | |||
| public ulong Id { get; set; } | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("options")] | |||
| public Optional<IEnumerable<ApplicationCommandInteractionDataOption>> | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| internal class ApplicationCommandInteractionDataOption | |||
| { | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("value")] | |||
| public Optional<ApplicationCommandOptionType> Value { get; set; } | |||
| [JsonProperty("options")] | |||
| public Optional<IEnumerable<ApplicationCommandInteractionDataOption>> Options { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API | |||
| { | |||
| internal class ApplicationCommandOption | |||
| { | |||
| [JsonProperty("type")] | |||
| public ApplicationCommandOptionType Type { get; set; } | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("description")] | |||
| public string Description { get; set; } | |||
| [JsonProperty("default")] | |||
| public Optional<bool> Default { get; set; } | |||
| [JsonProperty("required")] | |||
| public Optional<bool> Required { get; set; } | |||
| [JsonProperty("choices")] | |||
| public Optional<ApplicationCommandOptionChoice[]> Choices { get; set; } | |||
| [JsonProperty("options")] | |||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||
| public ApplicationCommandOption() { } | |||
| public ApplicationCommandOption(IApplicationCommandOption cmd) | |||
| { | |||
| this.Choices = cmd.Choices.Select(x => new ApplicationCommandOptionChoice() | |||
| { | |||
| Name = x.Name, | |||
| Value = x.Value | |||
| }).ToArray(); | |||
| this.Options = cmd.Options.Select(x => new ApplicationCommandOption(x)).ToArray(); | |||
| this.Required = cmd.Required.HasValue | |||
| ? cmd.Required.Value | |||
| : Optional<bool>.Unspecified; | |||
| this.Default = cmd.Default.HasValue | |||
| ? cmd.Default.Value | |||
| : Optional<bool>.Unspecified; | |||
| this.Name = cmd.Name; | |||
| this.Type = cmd.Type; | |||
| this.Description = cmd.Description; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API | |||
| { | |||
| internal class ApplicationCommandOptionChoice | |||
| { | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("value")] | |||
| public string Value { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| using Discord.API; | |||
| 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 ApplicationCommandParams | |||
| { | |||
| [JsonProperty("name")] | |||
| public string Name { get; set; } | |||
| [JsonProperty("description")] | |||
| public string Description { get; set; } | |||
| [JsonProperty("options")] | |||
| public Optional<ApplicationCommandOption[]> Options { get; set; } | |||
| public ApplicationCommandParams() { } | |||
| public ApplicationCommandParams(string name, string description, ApplicationCommandOption[] options = null) | |||
| { | |||
| this.Name = name; | |||
| this.Description = description; | |||
| this.Options = Optional.Create<ApplicationCommandOption[]>(options); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <Import Project="../../Discord.Net.targets" /> | |||
| <Import Project="../../StyleAnalyzer.targets"/> | |||
| <Import Project="../../StyleAnalyzer.targets" /> | |||
| <PropertyGroup> | |||
| <AssemblyName>Discord.Net.Rest</AssemblyName> | |||
| <RootNamespace>Discord.Rest</RootNamespace> | |||
| @@ -47,7 +47,6 @@ namespace Discord.API | |||
| internal ulong? CurrentUserId { get; set; } | |||
| public RateLimitPrecision RateLimitPrecision { get; private set; } | |||
| internal bool UseSystemClock { get; set; } | |||
| internal JsonSerializer Serializer => _serializer; | |||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
| @@ -786,6 +785,97 @@ namespace Discord.API | |||
| await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); | |||
| } | |||
| //Interactions | |||
| public async Task<ApplicationCommand[]> GetGlobalApplicationCommandsAsync(RequestOptions options = null) | |||
| { | |||
| try | |||
| { | |||
| return await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/commands", options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task<ApplicationCommand> CreateGlobalApplicationCommandAsync(ApplicationCommandParams command, 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)); | |||
| try | |||
| { | |||
| return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task<ApplicationCommand> EditGlobalApplicationCommandAsync(ApplicationCommandParams 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)); | |||
| try | |||
| { | |||
| return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) | |||
| { | |||
| try | |||
| { | |||
| await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return; } | |||
| } | |||
| public async Task<ApplicationCommand[]> GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = null) | |||
| { | |||
| try | |||
| { | |||
| return await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task<ApplicationCommand> CreateGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, 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)); | |||
| try | |||
| { | |||
| return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task<ApplicationCommand> EditGuildApplicationCommandAsync(ApplicationCommandParams 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)); | |||
| try | |||
| { | |||
| return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return null; } | |||
| } | |||
| public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) | |||
| { | |||
| try | |||
| { | |||
| await SendAsync<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return; } | |||
| } | |||
| //Guilds | |||
| public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) | |||
| { | |||
| @@ -0,0 +1,33 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.ApplicationCommand; | |||
| namespace Discord.Rest | |||
| { | |||
| internal static class ApplicationCommandHelper | |||
| { | |||
| public static async Task<Model> ModifyAsync(IApplicationCommand command, BaseDiscordClient client, | |||
| Action<ApplicationCommandProperties> func, RequestOptions options) | |||
| { | |||
| if (func == null) | |||
| throw new ArgumentNullException(nameof(func)); | |||
| var args = new ApplicationCommandProperties(); | |||
| func(args); | |||
| var apiArgs = new Discord.API.Rest.ApplicationCommandParams() | |||
| { | |||
| Description = args.Description, | |||
| Name = args.Name, | |||
| Options = args.Options.IsSpecified | |||
| ? args.Options.Value.Select(x => new API.ApplicationCommandOption(x)).ToArray() | |||
| : Optional<API.ApplicationCommandOption[]>.Unspecified, | |||
| }; | |||
| return await client.ApiClient.EditGlobalApplicationCommandAsync(apiArgs, command.Id, options); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| using Discord.API; | |||
| 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 InteractionCreated | |||
| { | |||
| [JsonProperty("id")] | |||
| public ulong Id { get; set; } | |||
| [JsonProperty("type")] | |||
| public InteractionType Type { get; set; } | |||
| [JsonProperty("data")] | |||
| public Optional<ApplicationCommandInteractionData> Data { get; set; } | |||
| [JsonProperty("guild_id")] | |||
| public ulong GuildId { get; set; } | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("member")] | |||
| public GuildMember Member { get; set; } | |||
| [JsonProperty("token")] | |||
| public string Token { get; set; } | |||
| [JsonProperty("version")] | |||
| public int Version { get; set; } | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <Import Project="../../Discord.Net.targets" /> | |||
| <Import Project="../../StyleAnalyzer.targets"/> | |||
| <Import Project="../../StyleAnalyzer.targets" /> | |||
| <PropertyGroup> | |||
| <AssemblyName>Discord.Net.WebSocket</AssemblyName> | |||
| <RootNamespace>Discord.WebSocket</RootNamespace> | |||
| @@ -1778,7 +1778,33 @@ namespace Discord.WebSocket | |||
| } | |||
| } | |||
| break; | |||
| case "INTERACTION_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer); | |||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||
| { | |||
| var guild = channel.Guild; | |||
| if (!guild.IsSynced) | |||
| { | |||
| await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| if(data.Type == InteractionType.ApplicationCommand) | |||
| { | |||
| // TODO: call command | |||
| } | |||
| } | |||
| else | |||
| { | |||
| await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
| return; | |||
| } | |||
| } | |||
| break; | |||
| //Ignored (User only) | |||
| case "CHANNEL_PINS_ACK": | |||
| await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | |||
| @@ -0,0 +1,34 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.WebSocket.Entities.Interaction | |||
| { | |||
| public class SocketInteraction : SocketEntity<ulong>, IDiscordInteraction | |||
| { | |||
| public ulong Id { get; } | |||
| public InteractionType Type { get; } | |||
| public IApplicationCommandInteractionData Data { get; } | |||
| public ulong GuildId { get; } | |||
| public ulong ChannelId { get; } | |||
| public IGuildUser Member { get; } | |||
| public string Token { get; } | |||
| public int Version { get; } | |||
| public DateTimeOffset CreatedAt { get; } | |||
| public SocketInteraction(DiscordSocketClient client, ulong id) | |||
| : base(client, id) | |||
| { | |||
| } | |||
| } | |||
| } | |||