Browse Source

Added APPLICATION_COMMAND_DELETE, APPLICATION_COMMAND_UPDATE, and APPLICATION_COMMAND_CREATE gateway events.

Added SocketApplicationCommands, Added method in SocketGuild to fetch that guilds ApplicationCommands.

Tested all rest routes and fixed them accordingly.

Did more testing and I think its ready to go
pull/1717/head
quin lynch 4 years ago
parent
commit
09f6f3439f
29 changed files with 737 additions and 128 deletions
  1. +72
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs
  2. +49
    -0
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs
  3. +3
    -24
      src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs
  4. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs
  5. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs
  6. +1
    -1
      src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs
  7. +30
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs
  8. +23
    -0
      src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs
  9. +1
    -1
      src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs
  10. +3
    -3
      src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs
  11. +21
    -0
      src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs
  12. +5
    -5
      src/Discord.Net.Rest/ClientHelper.cs
  13. +85
    -30
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  14. +8
    -4
      src/Discord.Net.Rest/DiscordRestClient.cs
  15. +40
    -21
      src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs
  16. +5
    -2
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs
  17. +1
    -1
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs
  18. +5
    -2
      src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs
  19. +1
    -1
      src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs
  20. +1
    -0
      src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs
  21. +30
    -0
      src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs
  22. +121
    -28
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  23. +55
    -1
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  24. +12
    -0
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  25. +55
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommand.cs
  26. +32
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandChoice.cs
  27. +70
    -0
      src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandOption.cs
  28. +3
    -1
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs
  29. +3
    -1
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs

+ 72
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a <see cref="IApplicationCommandOption"/> for making slash commands.
/// </summary>
public class ApplicationCommandOptionProperties
{
private string _name;
private string _description;

/// <summary>
/// The name of this option.
/// </summary>
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;
}
}

/// <summary>
/// The description of this option.
/// </summary>
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;
}
}

/// <summary>
/// The type of this option.
/// </summary>
public ApplicationCommandOptionType Type { get; set; }

/// <summary>
/// The first required option for the user to complete. only one option can be default.
/// </summary>
public bool? Default { get; set; }

/// <summary>
/// <see langword="true"/> if this option is required for this command, otherwise <see langword="false"/>.
/// </summary>
public bool? Required { get; set; }

/// <summary>
/// choices for string and int types for the user to pick from
/// </summary>
public List<ApplicationCommandOptionChoiceProperties> Choices { get; set; }

/// <summary>
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// </summary>
public List<ApplicationCommandOptionProperties> Options { get; set; }

}
}

+ 49
- 0
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a choice for a <see cref="IApplicationCommandInteractionDataOption"/>. This class is used when making new commands
/// </summary>
public class ApplicationCommandOptionChoiceProperties
{
private string _name;
private object _value;
/// <summary>
/// The name of this choice
/// </summary>
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?
/// <summary>
/// The value of this choice
/// </summary>
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;
}
}
}
}

+ 3
- 24
src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs View File

@@ -11,41 +11,20 @@ namespace Discord
/// </summary>
public class ApplicationCommandProperties
{
private string _name { get; set; }
private string _description { get; set; }

/// <summary>
/// Gets or sets the name of this command.
/// </summary>
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<string> Name { get; set; }

/// <summary>
/// Gets or sets the discription of this command.
/// </summary>
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<string> Description { get; set; }

/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<IApplicationCommandOption>> Options { get; set; }
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }
}
}

+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs View File

@@ -22,7 +22,7 @@ namespace Discord
/// This objects type can be any one of the option types in <see cref="ApplicationCommandOptionType"/>
/// </note>
/// </summary>
object? Value { get; }
object Value { get; }

/// <summary>
/// Present if this option is a group or subcommand.


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs View File

@@ -42,7 +42,7 @@ namespace Discord
IReadOnlyCollection<IApplicationCommandOptionChoice>? Choices { get; }

/// <summary>
/// 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.
/// </summary>
IReadOnlyCollection<IApplicationCommandOption>? Options { get; }
}


+ 1
- 1
src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs View File

@@ -19,7 +19,7 @@ namespace Discord
/// <summary>
/// value of the choice.
/// </summary>
string Value { get; }
object Value { get; }

}
}

+ 30
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// A class used to create slash commands
/// </summary>
public class SlashCommandCreationProperties
{
/// <summary>
/// The name of this command.
/// </summary>
public string Name { get; set; }

/// <summary>
/// The discription of this command.
/// </summary>
public string Description { get; set; }


/// <summary>
/// Gets or sets the options for this command.
/// </summary>
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; }
}
}

+ 23
- 0
src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs View File

@@ -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<ApplicationCommandOptionChoice[]>.Unspecified;

this.Options = option.Options != null
? option.Options.Select(x => new ApplicationCommandOption(x)).ToArray()
: Optional<ApplicationCommandOption[]>.Unspecified;

this.Required = option.Required.Value;
this.Default = option.Default.HasValue
? option.Default.Value
: Optional<bool>.Unspecified;

this.Name = option.Name;
this.Type = option.Type;
this.Description = option.Description;
}
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Common/ApplicationCommandOptionChoice.cs View File

@@ -13,6 +13,6 @@ namespace Discord.API
public string Name { get; set; }

[JsonProperty("value")]
public string Value { get; set; }
public object Value { get; set; }
}
}

src/Discord.Net.Rest/API/Rest/ApplicationCommandParams.cs → src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs View File

@@ -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<ApplicationCommandOption[]> 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;

+ 21
- 0
src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs View File

@@ -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<string> Name { get; set; }

[JsonProperty("description")]
public Optional<string> Description { get; set; }

[JsonProperty("options")]
public Optional<ApplicationCommandOption[]> Options { get; set; }
}
}

+ 5
- 5
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -202,23 +202,23 @@ namespace Discord.Rest
};
}

public static async Task<RestGlobalCommand[]> GetGlobalApplicationCommands(BaseDiscordClient client, RequestOptions options)
public static async Task<IReadOnlyCollection<RestGlobalCommand>> 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<RestGuildCommand[]> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options)
public static async Task<IReadOnlyCollection<RestGuildCommand>> 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();
}
}
}

+ 85
- 30
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

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

/// <exception cref="ArgumentException">Unknown OAuth token type.</exception>
@@ -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<ApplicationCommand[]> GetGlobalApplicationCommandsAsync(RequestOptions options = null)
=> await SendAsync<ApplicationCommand[]>("GET", $"applications/{this.CurrentUserId}/commands", options: options).ConfigureAwait(false);
{
options = RequestOptions.CreateOrClone(options);

return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{this.CurrentUserId}/commands", new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task<ApplicationCommand> CreateGlobalApplicationCommandAsync(ApplicationCommandParams command, RequestOptions options = null)

public async Task<ApplicationCommand> 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<ApplicationCommand>("POST", $"applications/{this.CurrentUserId}/commands", command, options: options).ConfigureAwait(false);
options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false);
}
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ApplicationCommandParams command, ulong commandId, RequestOptions options = null)
public async Task<ApplicationCommand> 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<ApplicationCommand>("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<ApplicationCommand>("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<ApplicationCommand[]> GetGuildApplicationCommandAsync(ulong guildId, RequestOptions options = 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)
{
options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(guildId: guildId);

return await SendAsync<ApplicationCommand[]>("GET", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", bucket, options: options).ConfigureAwait(false);
}
public async Task<ApplicationCommand> 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<ApplicationCommand>("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<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false);
}
public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null)
public async Task<ApplicationCommand> 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<ApplicationCommand>("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<ApplicationCommand>("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<ApplicationCommand>("DELETE", $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", options: options).ConfigureAwait(false);
{
options = RequestOptions.CreateOrClone(options);

var bucket = new BucketIds(guildId: guildId);

await SendAsync<ApplicationCommand>("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<Message> CreateInteractionFollowupMessage(CreateWebhookMessageParams args, string token, RequestOptions options = null)
{
@@ -862,7 +917,7 @@ namespace Discord.API

options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<Message>("POST", $"webhooks/{CurrentUserId}/{token}?wait=true", args, options: options).ConfigureAwait(false);
return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false);
}

public async Task<Message> ModifyInteractionFollowupMessage(CreateWebhookMessageParams args, ulong id, string token, RequestOptions options = null)
@@ -875,7 +930,7 @@ namespace Discord.API

options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<Message>("PATCH", $"webhooks/{CurrentUserId}/{token}/messages/{id}", args, options: options).ConfigureAwait(false);
return await SendJsonAsync<Message>("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


+ 8
- 4
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -107,13 +107,17 @@ namespace Discord.Rest
=> ClientHelper.GetVoiceRegionAsync(this, id, options);
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options);
public Task<RestGlobalCommand> CreateGobalCommand(Action<ApplicationCommandProperties> func, RequestOptions options = null)
public Task<RestGlobalCommand> CreateGlobalCommand(SlashCommandCreationProperties properties, RequestOptions options = null)
=> InteractionHelper.CreateGlobalCommand(this, properties, options);
public Task<RestGlobalCommand> CreateGlobalCommand(Action<SlashCommandCreationProperties> func, RequestOptions options = null)
=> InteractionHelper.CreateGlobalCommand(this, func, options);
public Task<RestGuildCommand> CreateGuildCommand(Action<ApplicationCommandProperties> func, ulong guildId, RequestOptions options = null)
public Task<RestGuildCommand> CreateGuildCommand(SlashCommandCreationProperties properties, ulong guildId, RequestOptions options = null)
=> InteractionHelper.CreateGuildCommand(this, guildId, properties, options);
public Task<RestGuildCommand> CreateGuildCommand(Action<SlashCommandCreationProperties> func, ulong guildId, RequestOptions options = null)
=> InteractionHelper.CreateGuildCommand(this, guildId, func, options);
public Task<RestGlobalCommand[]> GetGlobalApplicationCommands(RequestOptions options = null)
public Task<IReadOnlyCollection<RestGlobalCommand>> GetGlobalApplicationCommands(RequestOptions options = null)
=> ClientHelper.GetGlobalApplicationCommands(this, options);
public Task<RestGuildCommand[]> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null)
public Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(ulong guildId, RequestOptions options = null)
=> ClientHelper.GetGuildApplicationCommands(this, guildId, options);

//IDiscordClient


+ 40
- 21
src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs View File

@@ -21,30 +21,35 @@ namespace Discord.Rest
// Global commands
internal static async Task<RestGlobalCommand> CreateGlobalCommand(BaseDiscordClient client,
Action<ApplicationCommandProperties> func, RequestOptions options = null)
Action<SlashCommandCreationProperties> 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<RestGlobalCommand> 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<ApplicationCommandOption[]>.Unspecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
};

var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false);
return RestGlobalCommand.Create(client, cmd);
}

internal static async Task<RestGlobalCommand> ModifyGlobalCommand(BaseDiscordClient client, RestGlobalCommand command,
Action<ApplicationCommandProperties> 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<ApplicationCommandOption[]>.Unspecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.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<RestGuildCommand> CreateGuildCommand(BaseDiscordClient client, ulong guildId,
Action<ApplicationCommandProperties> func, RequestOptions options = null)
Action<SlashCommandCreationProperties> 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<RestGuildCommand> 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<ApplicationCommandOption[]>.Unspecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified
};

var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false);
return RestGuildCommand.Create(client, cmd, guildId);
}

internal static async Task<RestGuildCommand> ModifyGuildCommand(BaseDiscordClient client, RestGuildCommand command,
Action<ApplicationCommandProperties> 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<ApplicationCommandOption[]>.Unspecified
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray()
: Optional<Discord.API.ApplicationCommandOption[]>.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);
}
}
}

+ 5
- 2
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs View File

@@ -19,9 +19,9 @@ namespace Discord.Rest

public string Description { get; private set; }

public IReadOnlyCollection<IApplicationCommandOption> Options { get; private set; }
public IReadOnlyCollection<RestApplicationCommandOption> 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<IApplicationCommandOption> IApplicationCommand.Options => Options;

public virtual Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
}
}

+ 1
- 1
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandChoice.cs View File

@@ -11,7 +11,7 @@ namespace Discord.Rest
{
public string Name { get; }

public string Value { get; }
public object Value { get; }

internal RestApplicationCommandChoice(Model model)
{


+ 5
- 2
src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommandOption.cs View File

@@ -20,9 +20,9 @@ namespace Discord.Rest

public bool? Required { get; private set; }

public IReadOnlyCollection<IApplicationCommandOptionChoice> Choices { get; private set; }
public IReadOnlyCollection<RestApplicationCommandChoice> Choices { get; private set; }

public IReadOnlyCollection<IApplicationCommandOption> Options { get; private set; }
public IReadOnlyCollection<RestApplicationCommandOption> 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> IApplicationCommandOption.Options => Options;
IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
}
}

+ 1
- 1
src/Discord.Net.Rest/Entities/Interactions/RestGlobalCommand.cs View File

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


+ 1
- 0
src/Discord.Net.Rest/Entities/Interactions/RestGuildCommand.cs View File

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



+ 30
- 0
src/Discord.Net.WebSocket/API/Gateway/ApplicationCommandCreatedUpdatedEvent.cs View File

@@ -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<Discord.API.ApplicationCommandOption> Options { get; set; }
}
}

+ 121
- 28
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

@@ -45,7 +45,8 @@ namespace Discord.WebSocket
/// <code language="cs" region="ChannelDestroyed"
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
/// </example>
public event Func<SocketChannel, Task> ChannelDestroyed {
public event Func<SocketChannel, Task> ChannelDestroyed
{
add { _channelDestroyedEvent.Add(value); }
remove { _channelDestroyedEvent.Remove(value); }
}
@@ -67,7 +68,8 @@ namespace Discord.WebSocket
/// <code language="cs" region="ChannelUpdated"
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
/// </example>
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated {
public event Func<SocketChannel, SocketChannel, Task> ChannelUpdated
{
add { _channelUpdatedEvent.Add(value); }
remove { _channelUpdatedEvent.Remove(value); }
}
@@ -92,7 +94,8 @@ namespace Discord.WebSocket
/// <code language="cs" region="MessageReceived"
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
/// </example>
public event Func<SocketMessage, Task> MessageReceived {
public event Func<SocketMessage, Task> MessageReceived
{
add { _messageReceivedEvent.Add(value); }
remove { _messageReceivedEvent.Remove(value); }
}
@@ -124,7 +127,8 @@ namespace Discord.WebSocket
/// <code language="cs" region="MessageDeleted"
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" />
/// </example>
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted {
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted
{
add { _messageDeletedEvent.Add(value); }
remove { _messageDeletedEvent.Remove(value); }
}
@@ -182,7 +186,8 @@ namespace Discord.WebSocket
/// <see cref="ISocketMessageChannel"/> parameter.
/// </para>
/// </remarks>
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated {
public event Func<Cacheable<IMessage, ulong>, SocketMessage, ISocketMessageChannel, Task> MessageUpdated
{
add { _messageUpdatedEvent.Add(value); }
remove { _messageUpdatedEvent.Remove(value); }
}
@@ -217,19 +222,22 @@ namespace Discord.WebSocket
/// <code language="cs" region="ReactionAdded"
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/>
/// </example>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded {
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded
{
add { _reactionAddedEvent.Add(value); }
remove { _reactionAddedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
/// <summary> Fired when a reaction is removed from a message. </summary>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved {
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved
{
add { _reactionRemovedEvent.Add(value); }
remove { _reactionRemovedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>();
/// <summary> Fired when all reactions to a message are cleared. </summary>
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared {
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared
{
add { _reactionsClearedEvent.Add(value); }
remove { _reactionsClearedEvent.Remove(value); }
}
@@ -259,19 +267,22 @@ namespace Discord.WebSocket

//Roles
/// <summary> Fired when a role is created. </summary>
public event Func<SocketRole, Task> RoleCreated {
public event Func<SocketRole, Task> RoleCreated
{
add { _roleCreatedEvent.Add(value); }
remove { _roleCreatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleCreatedEvent = new AsyncEvent<Func<SocketRole, Task>>();
/// <summary> Fired when a role is deleted. </summary>
public event Func<SocketRole, Task> RoleDeleted {
public event Func<SocketRole, Task> RoleDeleted
{
add { _roleDeletedEvent.Add(value); }
remove { _roleDeletedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketRole, Task>> _roleDeletedEvent = new AsyncEvent<Func<SocketRole, Task>>();
/// <summary> Fired when a role is updated. </summary>
public event Func<SocketRole, SocketRole, Task> RoleUpdated {
public event Func<SocketRole, SocketRole, Task> RoleUpdated
{
add { _roleUpdatedEvent.Add(value); }
remove { _roleUpdatedEvent.Remove(value); }
}
@@ -279,37 +290,43 @@ namespace Discord.WebSocket

//Guilds
/// <summary> Fired when the connected account joins a guild. </summary>
public event Func<SocketGuild, Task> JoinedGuild {
public event Func<SocketGuild, Task> JoinedGuild
{
add { _joinedGuildEvent.Add(value); }
remove { _joinedGuildEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _joinedGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when the connected account leaves a guild. </summary>
public event Func<SocketGuild, Task> LeftGuild {
public event Func<SocketGuild, Task> LeftGuild
{
add { _leftGuildEvent.Add(value); }
remove { _leftGuildEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _leftGuildEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild becomes available. </summary>
public event Func<SocketGuild, Task> GuildAvailable {
public event Func<SocketGuild, Task> GuildAvailable
{
add { _guildAvailableEvent.Add(value); }
remove { _guildAvailableEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildAvailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild becomes unavailable. </summary>
public event Func<SocketGuild, Task> GuildUnavailable {
public event Func<SocketGuild, Task> GuildUnavailable
{
add { _guildUnavailableEvent.Add(value); }
remove { _guildUnavailableEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildUnavailableEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when offline guild members are downloaded. </summary>
public event Func<SocketGuild, Task> GuildMembersDownloaded {
public event Func<SocketGuild, Task> GuildMembersDownloaded
{
add { _guildMembersDownloadedEvent.Add(value); }
remove { _guildMembersDownloadedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, Task>> _guildMembersDownloadedEvent = new AsyncEvent<Func<SocketGuild, Task>>();
/// <summary> Fired when a guild is updated. </summary>
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated {
public event Func<SocketGuild, SocketGuild, Task> GuildUpdated
{
add { _guildUpdatedEvent.Add(value); }
remove { _guildUpdatedEvent.Remove(value); }
}
@@ -317,43 +334,50 @@ namespace Discord.WebSocket

//Users
/// <summary> Fired when a user joins a guild. </summary>
public event Func<SocketGuildUser, Task> UserJoined {
public event Func<SocketGuildUser, Task> UserJoined
{
add { _userJoinedEvent.Add(value); }
remove { _userJoinedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userJoinedEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
/// <summary> Fired when a user leaves a guild. </summary>
public event Func<SocketGuildUser, Task> UserLeft {
public event Func<SocketGuildUser, Task> UserLeft
{
add { _userLeftEvent.Add(value); }
remove { _userLeftEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, Task>> _userLeftEvent = new AsyncEvent<Func<SocketGuildUser, Task>>();
/// <summary> Fired when a user is banned from a guild. </summary>
public event Func<SocketUser, SocketGuild, Task> UserBanned {
public event Func<SocketUser, SocketGuild, Task> UserBanned
{
add { _userBannedEvent.Add(value); }
remove { _userBannedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userBannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
/// <summary> Fired when a user is unbanned from a guild. </summary>
public event Func<SocketUser, SocketGuild, Task> UserUnbanned {
public event Func<SocketUser, SocketGuild, Task> UserUnbanned
{
add { _userUnbannedEvent.Add(value); }
remove { _userUnbannedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketGuild, Task>> _userUnbannedEvent = new AsyncEvent<Func<SocketUser, SocketGuild, Task>>();
/// <summary> Fired when a user is updated. </summary>
public event Func<SocketUser, SocketUser, Task> UserUpdated {
public event Func<SocketUser, SocketUser, Task> UserUpdated
{
add { _userUpdatedEvent.Add(value); }
remove { _userUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>();
/// <summary> Fired when a guild member is updated, or a member presence is updated. </summary>
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated {
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated
{
add { _guildMemberUpdatedEvent.Add(value); }
remove { _guildMemberUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>();
/// <summary> Fired when a user joins, leaves, or moves voice channels. </summary>
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated {
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated
{
add { _userVoiceStateUpdatedEvent.Add(value); }
remove { _userVoiceStateUpdatedEvent.Remove(value); }
}
@@ -366,25 +390,29 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent<Func<SocketVoiceServer, Task>> _voiceServerUpdatedEvent = new AsyncEvent<Func<SocketVoiceServer, Task>>();
/// <summary> Fired when the connected account is updated. </summary>
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated {
public event Func<SocketSelfUser, SocketSelfUser, Task> CurrentUserUpdated
{
add { _selfUpdatedEvent.Add(value); }
remove { _selfUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>();
/// <summary> Fired when a user starts typing. </summary>
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping {
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping
{
add { _userIsTypingEvent.Add(value); }
remove { _userIsTypingEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>();
/// <summary> Fired when a user joins a group channel. </summary>
public event Func<SocketGroupUser, Task> RecipientAdded {
public event Func<SocketGroupUser, Task> RecipientAdded
{
add { _recipientAddedEvent.Add(value); }
remove { _recipientAddedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGroupUser, Task>> _recipientAddedEvent = new AsyncEvent<Func<SocketGroupUser, Task>>();
/// <summary> Fired when a user is removed from a group channel. </summary>
public event Func<SocketGroupUser, Task> RecipientRemoved {
public event Func<SocketGroupUser, Task> RecipientRemoved
{
add { _recipientRemovedEvent.Add(value); }
remove { _recipientRemovedEvent.Remove(value); }
}
@@ -452,5 +480,70 @@ namespace Discord.WebSocket
}
internal readonly AsyncEvent<Func<SocketInteraction, Task>> _interactionCreatedEvent = new AsyncEvent<Func<SocketInteraction, Task>>();

/// <summary>
/// Fired when a guild application command is created.
///</summary>
///<remarks>
/// <para>
/// This event is fired when an application command is created. The event handler must return a
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
/// </para>
/// <para>
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
/// </para>
/// <note>
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
/// </note>
/// </remarks>
public event Func<SocketApplicationCommand, Task> ApplicationCommandCreated
{
add { _applicationCommandCreated.Add(value); }
remove { _applicationCommandCreated.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandCreated = new AsyncEvent<Func<SocketApplicationCommand, Task>>();

/// <summary>
/// Fired when a guild application command is updated.
/// </summary>
/// <remarks>
/// <para>
/// This event is fired when an application command is updated. The event handler must return a
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
/// </para>
/// <para>
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
/// </para>
/// <note>
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
/// </note>
/// </remarks>
public event Func<SocketApplicationCommand, Task> ApplicationCommandUpdated
{
add { _applicationCommandUpdated.Add(value); }
remove { _applicationCommandUpdated.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandUpdated = new AsyncEvent<Func<SocketApplicationCommand, Task>>();

/// <summary>
/// Fired when a guild application command is deleted.
/// </summary>
/// <remarks>
/// <para>
/// This event is fired when an application command is deleted. The event handler must return a
/// <see cref="Task"/> and accept a <see cref="SocketApplicationCommand"/> as its parameter.
/// </para>
/// <para>
/// The command that was deleted will be passed into the <see cref="SocketApplicationCommand"/> parameter.
/// </para>
/// <note>
/// <b>This event is an undocumented discord event and may break at any time, its not recommended to rely on this event</b>
/// </note>
/// </remarks>
public event Func<SocketApplicationCommand, Task> ApplicationCommandDeleted
{
add { _applicationCommandDeleted.Add(value); }
remove { _applicationCommandDeleted.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketApplicationCommand, Task>> _applicationCommandDeleted = new AsyncEvent<Func<SocketApplicationCommand, Task>>();
}
}

+ 55
- 1
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -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<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_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<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_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<API.Gateway.ApplicationCommandCreatedUpdatedEvent>(_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);


+ 12
- 0
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -1006,6 +1006,18 @@ namespace Discord.WebSocket
public Task<IReadOnlyCollection<RestWebhook>> GetWebhooksAsync(RequestOptions options = null)
=> GuildHelper.GetWebhooksAsync(this, Discord, options);

//Interactions
/// <summary>
/// Gets this guilds slash commands commands
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of application commands found within the guild.
/// </returns>
public async Task<IReadOnlyCollection<RestApplicationCommand>> GetApplicationCommandsAsync(RequestOptions options = null)
=> await Discord.Rest.GetGuildApplicationCommands(this.Id, options);

//Emotes
/// <inheritdoc />
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null)


+ 55
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommand.cs View File

@@ -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<ulong>, IApplicationCommand
{
public ulong ApplicationId { get; private set; }

public string Name { get; private set; }

public string Description { get; private set; }

public IReadOnlyCollection<SocketApplicationCommandOption> 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<SocketApplicationCommandOption>();
}

public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
IReadOnlyCollection<IApplicationCommandOption> IApplicationCommand.Options => Options;
}
}

+ 32
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandChoice.cs View File

@@ -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
{
/// <summary>
/// Represents a choice for a <see cref="SocketApplicationCommandOption"/>
/// </summary>
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;
}
}
}

+ 70
- 0
src/Discord.Net.WebSocket/Entities/Interaction/SocketApplicationCommandOption.cs View File

@@ -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
{
/// <summary>
/// Represents an option for a <see cref="SocketApplicationCommand"/>
/// </summary>
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; }

/// <summary>
/// Choices for string and int types for the user to pick from.
/// </summary>
public IReadOnlyCollection<SocketApplicationCommandChoice> Choices { get; private set; }

/// <summary>
/// If the option is a subcommand or subcommand group type, this nested options will be the parameters.
/// </summary>
public IReadOnlyCollection<SocketApplicationCommandOption> 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<SocketApplicationCommandChoice>();

this.Options = model.Options.IsSpecified
? model.Options.Value.Select(x => SocketApplicationCommandOption.Create(x)).ToImmutableArray()
: new ImmutableArray<SocketApplicationCommandOption>();
}

IReadOnlyCollection<IApplicationCommandOptionChoice> IApplicationCommandOption.Choices => Choices;
IReadOnlyCollection<IApplicationCommandOption> IApplicationCommandOption.Options => Options;
}
}

+ 3
- 1
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs View File

@@ -39,7 +39,7 @@ namespace Discord.WebSocket
/// <summary>
/// The data associated with this interaction
/// </summary>
public IApplicationCommandInteractionData Data { get; private set; }
public SocketInteractionData Data { get; private set; }

/// <summary>
/// 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;
}
}

+ 3
- 1
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionData.cs View File

@@ -11,7 +11,7 @@ namespace Discord.WebSocket
public class SocketInteractionData : SocketEntity<ulong>, IApplicationCommandInteractionData
{
public string Name { get; private set; }
public IReadOnlyCollection<IApplicationCommandInteractionDataOption> Options { get; private set; }
public IReadOnlyCollection<SocketInteractionDataOption> Options { get; private set; }

internal SocketInteractionData(DiscordSocketClient client, ulong id)
: base(client, id)
@@ -33,5 +33,7 @@ namespace Discord.WebSocket
: null;

}

IReadOnlyCollection<IApplicationCommandInteractionDataOption> IApplicationCommandInteractionData.Options => Options;
}
}

Loading…
Cancel
Save