| @@ -1,9 +1,9 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <ItemGroup> | |||
| <!-- <ItemGroup> | |||
| <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.205" PrivateAssets="all" /> | |||
| </ItemGroup> | |||
| <PropertyGroup> | |||
| <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> | |||
| </PropertyGroup> | |||
| </PropertyGroup> --> | |||
| </Project> | |||
| @@ -11,13 +11,44 @@ namespace Discord | |||
| /// </summary> | |||
| public enum ApplicationCommandOptionType : byte | |||
| { | |||
| /// <summary> | |||
| /// A sub command | |||
| /// </summary> | |||
| SubCommand = 1, | |||
| /// <summary> | |||
| /// A group of sub commands | |||
| /// </summary> | |||
| SubCommandGroup = 2, | |||
| /// <summary> | |||
| /// A <see langword="string"/> of text | |||
| /// </summary> | |||
| String = 3, | |||
| /// <summary> | |||
| /// An <see langword="int"/> | |||
| /// </summary> | |||
| Integer = 4, | |||
| /// <summary> | |||
| /// A <see langword="bool"/> | |||
| /// </summary> | |||
| Boolean = 5, | |||
| /// <summary> | |||
| /// A <see cref="IGuildUser"/> | |||
| /// </summary> | |||
| User = 6, | |||
| /// <summary> | |||
| /// A <see cref="IGuildChannel"/> | |||
| /// </summary> | |||
| Channel = 7, | |||
| /// <summary> | |||
| /// A <see cref="IRole"/> | |||
| /// </summary> | |||
| Role = 8 | |||
| } | |||
| } | |||
| @@ -6,6 +6,10 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Provides properties that are used to modify a <see cref="IApplicationCommand" /> with the specified changes. | |||
| /// </summary> | |||
| /// <see cref="Ia"/> | |||
| public class ApplicationCommandProperties | |||
| { | |||
| public string Name { get; set; } | |||
| @@ -31,6 +31,16 @@ namespace Discord | |||
| /// </summary> | |||
| string Description { get; } | |||
| /// <summary> | |||
| /// Modifies this command | |||
| /// </summary> | |||
| /// <param name="func">The delegate containing the properties to modify the command with.</param> | |||
| /// <param name="options">The options to be used when sending the request.</param> | |||
| /// <returns> | |||
| /// A task that represents the asynchronous modification operation. | |||
| /// </returns> | |||
| Task ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null); | |||
| IEnumerable<IApplicationCommandOption>? Options { get; } | |||
| } | |||
| } | |||
| @@ -6,10 +6,24 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents data of an Interaction Command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata"/> | |||
| /// </summary> | |||
| public interface IApplicationCommandInteractionData | |||
| { | |||
| /// <summary> | |||
| /// The snowflake id of this command | |||
| /// </summary> | |||
| ulong Id { get; } | |||
| /// <summary> | |||
| /// The name of this command | |||
| /// </summary> | |||
| string Name { get; } | |||
| IEnumerable<IApplicationCommandInteractionDataOption> Options { get; } | |||
| /// <summary> | |||
| /// The params + values from the user | |||
| /// </summary> | |||
| IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | |||
| } | |||
| } | |||
| @@ -6,11 +6,25 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a option group for a command, see <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondataoption"/> | |||
| /// </summary> | |||
| public interface IApplicationCommandInteractionDataOption | |||
| { | |||
| /// <summary> | |||
| /// The name of the parameter | |||
| /// </summary> | |||
| string Name { get; } | |||
| ApplicationCommandOptionType Value { get; } | |||
| IEnumerable<IApplicationCommandInteractionDataOption> Options { get; } | |||
| /// <summary> | |||
| /// The value of the pair | |||
| /// </summary> | |||
| ApplicationCommandOptionType? Value { get; } | |||
| /// <summary> | |||
| /// Present if this option is a group or subcommand | |||
| /// </summary> | |||
| IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; } | |||
| } | |||
| } | |||
| @@ -6,6 +6,9 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Specifies choices for command group | |||
| /// </summary> | |||
| public interface IApplicationCommandOptionChoice | |||
| { | |||
| /// <summary> | |||
| @@ -16,12 +16,40 @@ namespace Discord | |||
| /// id of the interaction | |||
| /// </summary> | |||
| ulong Id { get; } | |||
| /// <summary> | |||
| /// The type of this <see cref="IDiscordInteraction"/> | |||
| /// </summary> | |||
| InteractionType Type { get; } | |||
| /// <summary> | |||
| /// The command data payload | |||
| /// </summary> | |||
| IApplicationCommandInteractionData? Data { get; } | |||
| /// <summary> | |||
| /// The guild it was sent from | |||
| /// </summary> | |||
| ulong GuildId { get; } | |||
| /// <summary> | |||
| /// The channel it was sent from | |||
| /// </summary> | |||
| ulong ChannelId { get; } | |||
| IGuildUser Member { get; } | |||
| /// <summary> | |||
| /// Guild member id for the invoking user | |||
| /// </summary> | |||
| ulong MemberId { get; } | |||
| /// <summary> | |||
| /// A continuation token for responding to the interaction | |||
| /// </summary> | |||
| string Token { get; } | |||
| /// <summary> | |||
| /// read-only property, always 1 | |||
| /// </summary> | |||
| int Version { get; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// The response type for an <see cref="IDiscordInteraction"/> | |||
| /// </summary> | |||
| public enum InteractionResponseType : byte | |||
| { | |||
| /// <summary> | |||
| /// ACK a Ping | |||
| /// </summary> | |||
| Pong = 1, | |||
| /// <summary> | |||
| /// ACK a command without sending a message, eating the user's input | |||
| /// </summary> | |||
| Acknowledge = 2, | |||
| /// <summary> | |||
| /// Respond with a message, eating the user's input | |||
| /// </summary> | |||
| ChannelMessage = 3, | |||
| /// <summary> | |||
| /// respond with a message, showing the user's input | |||
| /// </summary> | |||
| ChannelMessageWithSource = 4, | |||
| /// <summary> | |||
| /// ACK a command without sending a message, showing the user's input | |||
| /// </summary> | |||
| ACKWithSource = 5 | |||
| } | |||
| } | |||
| @@ -6,9 +6,19 @@ using System.Threading.Tasks; | |||
| namespace Discord | |||
| { | |||
| /// <summary> | |||
| /// Represents a type of Interaction from discord. | |||
| /// </summary> | |||
| public enum InteractionType : byte | |||
| { | |||
| /// <summary> | |||
| /// A ping from discord | |||
| /// </summary> | |||
| Ping = 1, | |||
| /// <summary> | |||
| /// An <see cref="IApplicationCommand"/> sent from discord | |||
| /// </summary> | |||
| ApplicationCommand = 2 | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| using Newtonsoft.Json; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API | |||
| { | |||
| internal class InteractionApplicationCommandCallbackData | |||
| { | |||
| [JsonProperty("tts")] | |||
| public Optional<bool> TTS { get; set; } | |||
| [JsonProperty("content")] | |||
| public string Content { get; set; } | |||
| [JsonProperty("embeds")] | |||
| public Optional<Embed[]> Embeds { get; set; } | |||
| [JsonProperty("allowed_mentions")] | |||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Discord.API | |||
| { | |||
| internal class InteractionFollowupMessage | |||
| { | |||
| public string Content { get; set; } | |||
| public Optional<string> Username { get; set; } | |||
| public Optional<string> AvatarUrl { get; set; } | |||
| public Optional<bool> TTS { get; set; } | |||
| public Optional<Stream> File { get; set; } | |||
| public Embed[] Embeds { get; set; } | |||
| } | |||
| } | |||
| @@ -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 InteractionResponse | |||
| { | |||
| [JsonProperty("type")] | |||
| public InteractionResponseType Type { get; set; } | |||
| [JsonProperty("data")] | |||
| public Optional<InteractionApplicationCommandCallbackData> Data { get; set; } | |||
| } | |||
| } | |||
| @@ -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 ModifyInteractionResponseParams | |||
| { | |||
| [JsonProperty("content")] | |||
| public string Content { get; set; } | |||
| [JsonProperty("embeds")] | |||
| public Optional<Embed[]> Embeds { get; set; } | |||
| [JsonProperty("allowed_mentions")] | |||
| public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
| } | |||
| } | |||
| @@ -803,13 +803,9 @@ namespace Discord.API | |||
| 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; } | |||
| return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<ApplicationCommand> EditGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||
| public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotNull(command, nameof(command)); | |||
| Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); | |||
| @@ -817,28 +813,13 @@ namespace Discord.API | |||
| 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; } | |||
| return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/commands/{commandId}", command, options: options).ConfigureAwait(false); | |||
| } | |||
| 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; } | |||
| } | |||
| => await SendAsync("DELETE", $"applications/{this.CurrentUserId}/commands/{commandId}", options: options).ConfigureAwait(false); | |||
| 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; } | |||
| } | |||
| => await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", options: options).ConfigureAwait(false); | |||
| public async Task<ApplicationCommand> CreateGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotNull(command, nameof(command)); | |||
| @@ -847,13 +828,9 @@ namespace Discord.API | |||
| 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; } | |||
| return await SendJsonAsync<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task<ApplicationCommand> EditGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) | |||
| public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) | |||
| { | |||
| Preconditions.NotNull(command, nameof(command)); | |||
| Preconditions.AtMost(command.Name.Length, 32, nameof(command.Name)); | |||
| @@ -861,19 +838,27 @@ namespace Discord.API | |||
| 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; } | |||
| return await SendJsonAsync<ApplicationCommand>("PATCH", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, options: options).ConfigureAwait(false); | |||
| } | |||
| public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) | |||
| => await SendAsync<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); | |||
| //Interaction Responses | |||
| public async Task CreateInteractionResponse(InteractionResponse response, string interactionId, string interactionToken, RequestOptions options = null) | |||
| { | |||
| try | |||
| { | |||
| await SendAsync<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false); | |||
| } | |||
| catch (HttpException ex) { return; } | |||
| if(response.Data.IsSpecified) | |||
| Preconditions.AtMost(response.Data.Value.Content.Length, 2000, nameof(response.Data.Value.Content)); | |||
| await SendJsonAsync("POST", $"/interactions/{interactionId}/{interactionToken}/callback", response, 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); | |||
| public async Task DeleteInteractionResponse(string interactionToken, RequestOptions options = null) | |||
| => await SendAsync("DELETE", $"/webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original", options: options); | |||
| public async Task CreateInteractionFollowupMessage() | |||
| { | |||
| } | |||
| //Guilds | |||
| @@ -27,7 +27,7 @@ namespace Discord.Rest | |||
| : Optional<API.ApplicationCommandOption[]>.Unspecified, | |||
| }; | |||
| return await client.ApiClient.EditGlobalApplicationCommandAsync(apiArgs, command.Id, options); | |||
| return await client.ApiClient.ModifyGlobalApplicationCommandAsync(apiArgs, command.Id, options); | |||
| } | |||
| } | |||
| } | |||
| @@ -1780,7 +1780,7 @@ namespace Discord.WebSocket | |||
| break; | |||
| case "INTERACTION_CREATE": | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INVITE_DELETE)").ConfigureAwait(false); | |||
| await _gatewayLogger.DebugAsync("Received Dispatch (INTERACTION_CREATE)").ConfigureAwait(false); | |||
| var data = (payload as JToken).ToObject<API.Gateway.InteractionCreated>(_serializer); | |||
| if (State.GetChannel(data.ChannelId) is SocketGuildChannel channel) | |||
| @@ -3,32 +3,55 @@ using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.Gateway.InteractionCreated; | |||
| 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 SocketGuild Guild | |||
| => Discord.GetGuild(GuildId); | |||
| public SocketTextChannel Channel | |||
| => Guild.GetTextChannel(ChannelId); | |||
| public SocketGuildUser Member | |||
| => Guild.GetUser(MemberId); | |||
| public InteractionType Type { get; private set; } | |||
| public IApplicationCommandInteractionData Data { get; private set; } | |||
| public string Token { get; private set; } | |||
| public int Version { get; private set; } | |||
| public DateTimeOffset CreatedAt { get; } | |||
| public int Version { get; } | |||
| public ulong GuildId { get; private set; } | |||
| public ulong ChannelId { get; private set; } | |||
| public ulong MemberId { get; private set; } | |||
| public DateTimeOffset CreatedAt { get; } | |||
| public SocketInteraction(DiscordSocketClient client, ulong id) | |||
| internal SocketInteraction(DiscordSocketClient client, ulong id) | |||
| : base(client, id) | |||
| { | |||
| } | |||
| internal static SocketInteraction Create(DiscordSocketClient client, Model model) | |||
| { | |||
| var entitiy = new SocketInteraction(client, model.Id); | |||
| entitiy.Update(model); | |||
| return entitiy; | |||
| } | |||
| internal void Update(Model model) | |||
| { | |||
| this.Data = model.Data.IsSpecified | |||
| ? SocketInteractionData.Create(this.Discord, model.Data.Value) | |||
| : null; | |||
| this.GuildId = model.GuildId; | |||
| this.ChannelId = model.ChannelId; | |||
| this.Token = model.Token; | |||
| this.Version = model.Version; | |||
| this.MemberId = model.Member.User.Id; | |||
| this.Type = model.Type; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.ApplicationCommandInteractionData; | |||
| namespace Discord.WebSocket.Entities.Interaction | |||
| { | |||
| public class SocketInteractionData : SocketEntity<ulong>, IApplicationCommandInteractionData | |||
| { | |||
| public string Name { get; private set; } | |||
| public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; } | |||
| internal SocketInteractionData(DiscordSocketClient client, ulong id) | |||
| : base(client, id) | |||
| { | |||
| } | |||
| internal static SocketInteractionData Create(DiscordSocketClient client, Model model) | |||
| { | |||
| var entity = new SocketInteractionData(client, model.Id); | |||
| entity.Update(model); | |||
| return entity; | |||
| } | |||
| internal void Update(Model model) | |||
| { | |||
| this.Name = model.Name; | |||
| this.Options = model.Options.IsSpecified | |||
| ? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray() | |||
| : null; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Collections.Immutable; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Model = Discord.API.ApplicationCommandInteractionDataOption; | |||
| namespace Discord.WebSocket.Entities.Interaction | |||
| { | |||
| public class SocketInteractionDataOption : IApplicationCommandInteractionDataOption | |||
| { | |||
| public string Name { get; private set; } | |||
| public ApplicationCommandOptionType? Value { get; private set; } | |||
| public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; } | |||
| internal SocketInteractionDataOption(Model model) | |||
| { | |||
| this.Name = Name; | |||
| this.Value = model.Value.IsSpecified ? model.Value.Value : null; | |||
| this.Options = model.Options.IsSpecified | |||
| ? model.Options.Value.Select(x => new SocketInteractionDataOption(x)).ToImmutableArray() | |||
| : null; | |||
| } | |||
| } | |||
| } | |||