@@ -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; | |||
} | |||
} | |||
} |