@@ -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) | |||
{ | |||
} | |||
} | |||
} |