@@ -26,5 +26,10 @@ namespace Discord | |||||
/// Gets or sets the options for this command. | /// Gets or sets the options for this command. | ||||
/// </summary> | /// </summary> | ||||
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | ||||
/// <summary> | |||||
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/> | |||||
/// </summary> | |||||
public Optional<bool> DefaultPermission { get; set; } | |||||
} | } | ||||
} | } |
@@ -18,7 +18,7 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// The id of the interaction. | /// The id of the interaction. | ||||
/// </summary> | /// </summary> | ||||
ulong Id { get; } | |||||
new ulong Id { get; } | |||||
/// <summary> | /// <summary> | ||||
/// The type of this <see cref="IDiscordInteraction"/>. | /// The type of this <see cref="IDiscordInteraction"/>. | ||||
@@ -11,7 +11,7 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using <see cref="ChannelMessageWithSource"/> | /// After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using <see cref="ChannelMessageWithSource"/> | ||||
/// or you can choose to send a deferred response with <see cref="ACKWithSource"/>. If choosing a deferred response, the user will see a loading state for the interaction, | |||||
/// or you can choose to send a deferred response with <see cref="DeferredChannelMessageWithSource"/>. If choosing a deferred response, the user will see a loading state for the interaction, | |||||
/// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response. | /// and you'll have up to 15 minutes to edit the original deferred response using Edit Original Interaction Response. | ||||
/// You can read more about Response types <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-response">Here</see> | /// You can read more about Response types <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-response">Here</see> | ||||
/// </remarks> | /// </remarks> | ||||
@@ -22,13 +22,13 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
Pong = 1, | Pong = 1, | ||||
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or ACKWithSource", true)] | |||||
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)] | |||||
/// <summary> | /// <summary> | ||||
/// ACK a command without sending a message, eating the user's input. | /// ACK a command without sending a message, eating the user's input. | ||||
/// </summary> | /// </summary> | ||||
Acknowledge = 2, | Acknowledge = 2, | ||||
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or ACKWithSource", true)] | |||||
[Obsolete("This response type has been depricated by discord. Either use ChannelMessageWithSource or DeferredChannelMessageWithSource", true)] | |||||
/// <summary> | /// <summary> | ||||
/// Respond with a message, showing the user's input. | /// Respond with a message, showing the user's input. | ||||
/// </summary> | /// </summary> | ||||
@@ -26,5 +26,10 @@ namespace Discord | |||||
/// Gets or sets the options for this command. | /// Gets or sets the options for this command. | ||||
/// </summary> | /// </summary> | ||||
public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | public Optional<List<ApplicationCommandOptionProperties>> Options { get; set; } | ||||
/// <summary> | |||||
/// Whether the command is enabled by default when the app is added to a guild. Default is <see langword="true"/> | |||||
/// </summary> | |||||
public Optional<bool> DefaultPermission { get; set; } | |||||
} | } | ||||
} | } |
@@ -0,0 +1,62 @@ | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Application command permissions allow you to enable or disable commands for specific users or roles within a guild. | |||||
/// </summary> | |||||
public class ApplicationCommandPermission | |||||
{ | |||||
/// <summary> | |||||
/// The id of the role or user. | |||||
/// </summary> | |||||
public ulong Id { get; } | |||||
/// <summary> | |||||
/// The target of this permission. | |||||
/// </summary> | |||||
public PermissionTarget Type { get; } | |||||
/// <summary> | |||||
/// <see langword="true"/> to allow, otherwise <see langword="false"/>. | |||||
/// </summary> | |||||
public bool Value { get; } | |||||
internal ApplicationCommandPermission() { } | |||||
/// <summary> | |||||
/// Creates a new <see cref="ApplicationCommandPermission"/>. | |||||
/// </summary> | |||||
/// <param name="targetId">The id you want to target this permission value for.</param> | |||||
/// <param name="targetType">The type of the <b>targetId</b> parameter.</param> | |||||
/// <param name="allow">The value of this permission.</param> | |||||
public ApplicationCommandPermission(ulong targetId, PermissionTarget targetType, bool allow) | |||||
{ | |||||
this.Id = targetId; | |||||
this.Type = targetType; | |||||
this.Value = allow; | |||||
} | |||||
/// <summary> | |||||
/// Creates a new <see cref="ApplicationCommandPermission"/> targeting <see cref="PermissionTarget.User"/>. | |||||
/// </summary> | |||||
/// <param name="target">The user you want to target this permission value for.</param> | |||||
/// <param name="allow">The value of this permission.</param> | |||||
public ApplicationCommandPermission(IUser target, bool allow) | |||||
{ | |||||
this.Id = target.Id; | |||||
this.Value = allow; | |||||
this.Type = PermissionTarget.User; | |||||
} | |||||
/// <summary> | |||||
/// Creates a new <see cref="ApplicationCommandPermission"/> targeting <see cref="PermissionTarget.Role"/>. | |||||
/// </summary> | |||||
/// <param name="target">The role you want to target this permission value for.</param> | |||||
/// <param name="allow">The value of this permission.</param> | |||||
public ApplicationCommandPermission(IRole target, bool allow) | |||||
{ | |||||
this.Id = target.Id; | |||||
this.Value = allow; | |||||
this.Type = PermissionTarget.Role; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Returned when fetching the permissions for a command in a guild. | |||||
/// </summary> | |||||
public class GuildApplicationCommandPermissions | |||||
{ | |||||
/// <summary> | |||||
/// The id of the command. | |||||
/// </summary> | |||||
public ulong Id { get; } | |||||
/// <summary> | |||||
/// The id of the application the command belongs to. | |||||
/// </summary> | |||||
public ulong ApplicationId { get; } | |||||
/// <summary> | |||||
/// The id of the guild. | |||||
/// </summary> | |||||
public ulong GuildId { get; } | |||||
/// <summary> | |||||
/// The permissions for the command in the guild. | |||||
/// </summary> | |||||
public IReadOnlyCollection<ApplicationCommandPermission> Permissions { get; } | |||||
internal GuildApplicationCommandPermissions(ulong id, ulong appId, ulong guildId, List<ApplicationCommandPermission> permissions) | |||||
{ | |||||
this.Id = id; | |||||
this.ApplicationId = appId; | |||||
this.GuildId = guildId; | |||||
this.Permissions = permissions.ToReadOnlyCollection(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,68 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.Net | |||||
{ | |||||
public class ApplicationCommandException : Exception | |||||
{ | |||||
/// <summary> | |||||
/// Gets the JSON error code returned by Discord. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A | |||||
/// <see href="https://discord.com/developers/docs/topics/opcodes-and-status-codes#json">JSON error code</see> | |||||
/// from Discord, or <c>null</c> if none. | |||||
/// </returns> | |||||
public int? DiscordCode { get; } | |||||
/// <summary> | |||||
/// Gets the reason of the exception. | |||||
/// </summary> | |||||
public string Reason { get; } | |||||
/// <summary> | |||||
/// Gets the request object used to send the request. | |||||
/// </summary> | |||||
public IRequest Request { get; } | |||||
/// <summary> | |||||
/// The error object returned from discord. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// Note: This object can be null if discord didn't provide it. | |||||
/// </remarks> | |||||
public object Error { get; } | |||||
/// <summary> | |||||
/// The request json used to create the application command. This is useful for checking your commands for any format errors. | |||||
/// </summary> | |||||
public string RequestJson { get; } | |||||
/// <summary> | |||||
/// The underlying <see cref="HttpException"/> that caused this exception to be thrown. | |||||
/// </summary> | |||||
public HttpException InnerHttpException { get; } | |||||
/// <summary> | |||||
/// Initializes a new instance of the <see cref="ApplicationCommandException" /> class. | |||||
/// </summary> | |||||
/// <param name="request">The request that was sent prior to the exception.</param> | |||||
/// <param name="requestJson"></param> | |||||
/// <param name="httpError"></param> | |||||
/// <param name="discordCode">The Discord status code returned.</param> | |||||
/// <param name="reason">The reason behind the exception.</param> | |||||
/// <param name="errors"></param> | |||||
public ApplicationCommandException(string requestJson, HttpException httpError) | |||||
: base("The application command failed to be created!", httpError) | |||||
{ | |||||
Request = httpError.Request; | |||||
DiscordCode = httpError.DiscordCode; | |||||
Reason = httpError.Reason; | |||||
Error = httpError.Error; | |||||
RequestJson = requestJson; | |||||
InnerHttpException = httpError; | |||||
} | |||||
} | |||||
} |
@@ -34,6 +34,13 @@ namespace Discord.Net | |||||
/// Gets the request object used to send the request. | /// Gets the request object used to send the request. | ||||
/// </summary> | /// </summary> | ||||
public IRequest Request { get; } | public IRequest Request { get; } | ||||
/// <summary> | |||||
/// The error object returned from discord. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// Note: This object can be null if discord didn't provide it. | |||||
/// </remarks> | |||||
public object Error { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Initializes a new instance of the <see cref="HttpException" /> class. | /// Initializes a new instance of the <see cref="HttpException" /> class. | ||||
@@ -42,13 +49,14 @@ namespace Discord.Net | |||||
/// <param name="request">The request that was sent prior to the exception.</param> | /// <param name="request">The request that was sent prior to the exception.</param> | ||||
/// <param name="discordCode">The Discord status code returned.</param> | /// <param name="discordCode">The Discord status code returned.</param> | ||||
/// <param name="reason">The reason behind the exception.</param> | /// <param name="reason">The reason behind the exception.</param> | ||||
public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null) | |||||
public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null, object errors = null) | |||||
: base(CreateMessage(httpCode, discordCode, reason)) | : base(CreateMessage(httpCode, discordCode, reason)) | ||||
{ | { | ||||
HttpCode = httpCode; | HttpCode = httpCode; | ||||
Request = request; | Request = request; | ||||
DiscordCode = discordCode; | DiscordCode = discordCode; | ||||
Reason = reason; | Reason = reason; | ||||
Error = errors; | |||||
} | } | ||||
private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null) | private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null) | ||||
@@ -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 | |||||
{ | |||||
public class ApplicationCommandPermissions | |||||
{ | |||||
[JsonProperty("id")] | |||||
public ulong Id { get; set; } | |||||
[JsonProperty("type")] | |||||
public PermissionTarget Type { get; set; } | |||||
[JsonProperty("permission")] | |||||
public bool Permission { get; set; } | |||||
} | |||||
} |
@@ -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 | |||||
{ | |||||
public class GuildApplicationCommandPermission | |||||
{ | |||||
[JsonProperty("id")] | |||||
public ulong Id { get; } | |||||
[JsonProperty("application_id")] | |||||
public ulong ApplicationId { get; } | |||||
[JsonProperty("guild_id")] | |||||
public ulong GuildId { get; } | |||||
[JsonProperty("permissions")] | |||||
public API.ApplicationCommandPermissions[] Permissions { 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.Rest | |||||
{ | |||||
internal class ModifyGuildApplicationCommandPermissions | |||||
{ | |||||
[JsonProperty("id")] | |||||
public ulong Id { get; set; } | |||||
[JsonProperty("permissions")] | |||||
public ApplicationCommandPermission[] Permissions { get; set; } | |||||
} | |||||
} |
@@ -10,7 +10,7 @@ namespace Discord.API.Rest | |||||
internal class ModifyInteractionResponseParams | internal class ModifyInteractionResponseParams | ||||
{ | { | ||||
[JsonProperty("content")] | [JsonProperty("content")] | ||||
public string Content { get; set; } | |||||
public Optional<string> Content { get; set; } | |||||
[JsonProperty("embeds")] | [JsonProperty("embeds")] | ||||
public Optional<Embed[]> Embeds { get; set; } | public Optional<Embed[]> Embeds { get; set; } | ||||
@@ -211,6 +211,7 @@ namespace Discord.Rest | |||||
return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); | return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); | ||||
} | } | ||||
public static async Task<IReadOnlyCollection<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); | var response = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, options).ConfigureAwait(false); | ||||
@@ -804,7 +804,21 @@ namespace Discord.API | |||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
try | |||||
{ | |||||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/commands", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
catch (HttpException x) | |||||
{ | |||||
if (x.HttpCode == HttpStatusCode.BadRequest) | |||||
{ | |||||
var json = (x.Request as JsonRestRequest).Json; | |||||
throw new ApplicationCommandException(json, x); | |||||
} | |||||
// Re-throw the http exception | |||||
throw; | |||||
} | |||||
} | } | ||||
public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | public async Task<ApplicationCommand> ModifyGlobalApplicationCommandAsync(ModifyApplicationCommandParams command, ulong commandId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -823,7 +837,21 @@ namespace Discord.API | |||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
try | |||||
{ | |||||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/commands/{commandId}", command, new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
catch (HttpException x) | |||||
{ | |||||
if (x.HttpCode == HttpStatusCode.BadRequest) | |||||
{ | |||||
var json = (x.Request as JsonRestRequest).Json; | |||||
throw new ApplicationCommandException(json, x); | |||||
} | |||||
// Re-throw the http exception | |||||
throw; | |||||
} | |||||
} | } | ||||
public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) | public async Task DeleteGlobalApplicationCommandAsync(ulong commandId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -852,7 +880,21 @@ namespace Discord.API | |||||
var bucket = new BucketIds(guildId: guildId); | var bucket = new BucketIds(guildId: guildId); | ||||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); | |||||
try | |||||
{ | |||||
return await SendJsonAsync<ApplicationCommand>("POST", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands", command, bucket, options: options).ConfigureAwait(false); | |||||
} | |||||
catch (HttpException x) | |||||
{ | |||||
if (x.HttpCode == HttpStatusCode.BadRequest) | |||||
{ | |||||
var json = (x.Request as JsonRestRequest).Json; | |||||
throw new ApplicationCommandException(json, x); | |||||
} | |||||
// Re-throw the http exception | |||||
throw; | |||||
} | |||||
} | } | ||||
public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) | public async Task<ApplicationCommand> ModifyGuildApplicationCommandAsync(ModifyApplicationCommandParams command, ulong guildId, ulong commandId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -873,7 +915,21 @@ namespace Discord.API | |||||
var bucket = new BucketIds(guildId: guildId); | var bucket = new BucketIds(guildId: guildId); | ||||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); | |||||
try | |||||
{ | |||||
return await SendJsonAsync<ApplicationCommand>("PATCH", () => $"applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}", command, bucket, options: options).ConfigureAwait(false); | |||||
} | |||||
catch (HttpException x) | |||||
{ | |||||
if (x.HttpCode == HttpStatusCode.BadRequest) | |||||
{ | |||||
var json = (x.Request as JsonRestRequest).Json; | |||||
throw new ApplicationCommandException(json, x); | |||||
} | |||||
// Re-throw the http exception | |||||
throw; | |||||
} | |||||
} | } | ||||
public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) | public async Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -894,6 +950,14 @@ namespace Discord.API | |||||
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<Message> GetInteractionResponse(string interactionToken, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotNullOrEmpty(interactionToken, nameof(interactionToken)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
return await SendAsync<Message>("GET", $"webhooks/{this.CurrentUserId}/{interactionToken}/messages/@original").ConfigureAwait(false); | |||||
} | |||||
public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) | public async Task ModifyInteractionResponse(ModifyInteractionResponseParams args, string interactionToken, RequestOptions options = null) | ||||
{ | { | ||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
@@ -920,13 +984,14 @@ namespace Discord.API | |||||
return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), 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) | |||||
public async Task<Message> ModifyInteractionFollowupMessage(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) | |||||
{ | { | ||||
Preconditions.NotNull(args, nameof(args)); | Preconditions.NotNull(args, nameof(args)); | ||||
Preconditions.NotEqual(id, 0, nameof(id)); | Preconditions.NotEqual(id, 0, nameof(id)); | ||||
if (args.Content?.Length > DiscordConfig.MaxMessageSize) | |||||
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); | |||||
if(args.Content.IsSpecified) | |||||
if (args.Content.Value.Length > DiscordConfig.MaxMessageSize) | |||||
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); | |||||
options = RequestOptions.CreateOrClone(options); | options = RequestOptions.CreateOrClone(options); | ||||
@@ -942,6 +1007,56 @@ namespace Discord.API | |||||
await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false); | await SendAsync("DELETE", () => $"webhooks/{CurrentUserId}/{token}/messages/{id}", new BucketIds(), options: options).ConfigureAwait(false); | ||||
} | } | ||||
// Application Command permissions | |||||
public async Task<GuildApplicationCommandPermission[]> GetGuildApplicationCommandPermissions(ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
return await SendAsync<GuildApplicationCommandPermission[]>("GET", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/permissions", new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildApplicationCommandPermission> GetGuildApplicationCommandPermission(ulong guildId, ulong commandId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(commandId, 0, nameof(commandId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
return await SendAsync<GuildApplicationCommandPermission>("GET", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task ModifyApplicationCommandPermissions(ApplicationCommandPermissions[] permissions, ulong guildId, ulong commandId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(commandId, 0, nameof(commandId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/{commandId}/permissions", permissions, new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task BatchModifyApplicationCommandPermissions(ModifyGuildApplicationCommandPermissions[] permissions, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotNull(permissions, nameof(permissions)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands/premissions", permissions, new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task BulkOverrideGuildApplicationCommand(API.ApplicationCommand[] commands, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotNull(commands, nameof(commands)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
await SendJsonAsync("PUT", () => $"/applications/{this.CurrentUserId}/guilds/{guildId}/commands", commands, new BucketIds(), options: options).ConfigureAwait(false); | |||||
} | |||||
//Guilds | //Guilds | ||||
public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) | public async Task<Guild> GetGuildAsync(ulong guildId, bool withCounts, RequestOptions options = null) | ||||
{ | { | ||||
@@ -10,12 +10,25 @@ namespace Discord.Rest | |||||
{ | { | ||||
internal static class InteractionHelper | internal static class InteractionHelper | ||||
{ | { | ||||
internal static async Task<RestUserMessage> SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args, | |||||
internal static async Task<RestInteractionMessage> SendInteractionResponse(BaseDiscordClient client, IMessageChannel channel, InteractionResponse response, | |||||
ulong interactionId, string interactionToken, RequestOptions options = null) | |||||
{ | |||||
await client.ApiClient.CreateInteractionResponse(response, interactionId, interactionToken, options).ConfigureAwait(false); | |||||
// get the original message | |||||
var msg = await client.ApiClient.GetInteractionResponse(interactionToken).ConfigureAwait(false); | |||||
var entity = RestInteractionMessage.Create(client, msg, interactionToken, channel); | |||||
return entity; | |||||
} | |||||
internal static async Task<RestFollowupMessage> SendFollowupAsync(BaseDiscordClient client, API.Rest.CreateWebhookMessageParams args, | |||||
string token, IMessageChannel channel, RequestOptions options = null) | string token, IMessageChannel channel, RequestOptions options = null) | ||||
{ | { | ||||
var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options).ConfigureAwait(false); | var model = await client.ApiClient.CreateInteractionFollowupMessage(args, token, options).ConfigureAwait(false); | ||||
var entity = RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||||
RestFollowupMessage entity = RestFollowupMessage.Create(client, model, token, channel); | |||||
return entity; | return entity; | ||||
} | } | ||||
@@ -44,7 +57,10 @@ namespace Discord.Rest | |||||
Description = args.Description, | Description = args.Description, | ||||
Options = args.Options.IsSpecified | Options = args.Options.IsSpecified | ||||
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified, | |||||
DefaultPermission = args.DefaultPermission.IsSpecified | |||||
? args.DefaultPermission.Value | |||||
: Optional<bool>.Unspecified | |||||
}; | }; | ||||
var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); | var cmd = await client.ApiClient.CreateGlobalApplicationCommandAsync(model, options).ConfigureAwait(false); | ||||
@@ -68,7 +84,10 @@ namespace Discord.Rest | |||||
Description = args.Description, | Description = args.Description, | ||||
Options = args.Options.IsSpecified | Options = args.Options.IsSpecified | ||||
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified, | |||||
DefaultPermission = args.DefaultPermission.IsSpecified | |||||
? args.DefaultPermission.Value | |||||
: Optional<bool>.Unspecified | |||||
}; | }; | ||||
var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); | var msg = await client.ApiClient.ModifyGlobalApplicationCommandAsync(model, command.Id, options).ConfigureAwait(false); | ||||
@@ -119,7 +138,10 @@ namespace Discord.Rest | |||||
Description = args.Description, | Description = args.Description, | ||||
Options = args.Options.IsSpecified | Options = args.Options.IsSpecified | ||||
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified, | |||||
DefaultPermission = args.DefaultPermission.IsSpecified | |||||
? args.DefaultPermission.Value | |||||
: Optional<bool>.Unspecified | |||||
}; | }; | ||||
var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); | var cmd = await client.ApiClient.CreateGuildApplicationCommandAsync(model, guildId, options).ConfigureAwait(false); | ||||
@@ -143,7 +165,10 @@ namespace Discord.Rest | |||||
Description = args.Description, | Description = args.Description, | ||||
Options = args.Options.IsSpecified | Options = args.Options.IsSpecified | ||||
? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ? args.Options.Value.Select(x => new Discord.API.ApplicationCommandOption(x)).ToArray() | ||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified | |||||
: Optional<Discord.API.ApplicationCommandOption[]>.Unspecified, | |||||
DefaultPermission = args.DefaultPermission.IsSpecified | |||||
? args.DefaultPermission.Value | |||||
: Optional<bool>.Unspecified | |||||
}; | }; | ||||
var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); | var msg = await client.ApiClient.ModifyGuildApplicationCommandAsync(model, command.GuildId, command.Id, options).ConfigureAwait(false); | ||||
@@ -158,5 +183,60 @@ namespace Discord.Rest | |||||
await client.ApiClient.DeleteGuildApplicationCommandAsync(command.GuildId, command.Id, options).ConfigureAwait(false); | await client.ApiClient.DeleteGuildApplicationCommandAsync(command.GuildId, command.Id, options).ConfigureAwait(false); | ||||
} | } | ||||
internal static async Task<Discord.API.Message> ModifyFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, Action<MessageProperties> func, | |||||
RequestOptions options = null) | |||||
{ | |||||
var args = new MessageProperties(); | |||||
func(args); | |||||
bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); | |||||
bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : message.Embeds.Any(); | |||||
if (!hasText && !hasEmbed) | |||||
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||||
var apiArgs = new API.Rest.ModifyInteractionResponseParams | |||||
{ | |||||
Content = args.Content, | |||||
Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>() | |||||
}; | |||||
return await client.ApiClient.ModifyInteractionFollowupMessage(apiArgs, message.Id, message.Token, options).ConfigureAwait(false); | |||||
} | |||||
internal static async Task DeleteFollowupMessage(BaseDiscordClient client, RestFollowupMessage message, RequestOptions options = null) | |||||
=> await client.ApiClient.DeleteInteractionFollowupMessage(message.Id, message.Token, options); | |||||
internal static async Task<Discord.API.Message> ModifyInteractionResponse(BaseDiscordClient client, RestInteractionMessage message, Action<MessageProperties> func, | |||||
RequestOptions options = null) | |||||
{ | |||||
var args = new MessageProperties(); | |||||
func(args); | |||||
bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : !string.IsNullOrEmpty(message.Content); | |||||
bool hasEmbed = args.Embed.IsSpecified ? args.Embed.Value != null : message.Embeds.Any(); | |||||
if (!hasText && !hasEmbed) | |||||
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||||
var apiArgs = new API.Rest.ModifyInteractionResponseParams | |||||
{ | |||||
Content = args.Content, | |||||
Embeds = args.Embed.IsSpecified ? new API.Embed[] { args.Embed.Value.ToModel() } : Optional.Create<API.Embed[]>() | |||||
}; | |||||
return await client.ApiClient.ModifyInteractionFollowupMessage(apiArgs, message.Id, message.Token, options).ConfigureAwait(false); | |||||
} | |||||
internal static async Task DeletedInteractionResponse(BaseDiscordClient client, RestInteractionMessage message, RequestOptions options = null) | |||||
=> await client.ApiClient.DeleteInteractionFollowupMessage(message.Id, message.Token, options); | |||||
// Guild permissions | |||||
internal static async Task<IReadOnlyCollection<Discord.GuildApplicationCommandPermissions>> GetCommandGuildPermissions(BaseDiscordClient client, | |||||
RestGuildCommand command) | |||||
{ | |||||
// TODO | |||||
return null; | |||||
} | |||||
} | } | ||||
} | } |
@@ -45,5 +45,8 @@ namespace Discord.Rest | |||||
/// </returns> | /// </returns> | ||||
public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null) | public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null) | ||||
=> await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false); | => await InteractionHelper.ModifyGuildCommand(Discord, this, func, options).ConfigureAwait(false); | ||||
public async Task<IReadOnlyCollection<Discord.GuildApplicationCommandPermissions>> GetCommandPermissions() | |||||
=> await InteractionHelper.GetCommandGuildPermissions(Discord, this); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,82 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Model = Discord.API.Message; | |||||
namespace Discord.Rest | |||||
{ | |||||
/// <summary> | |||||
/// Represents a REST-based follow up message sent by a bot responding to a slash command. | |||||
/// </summary> | |||||
public class RestFollowupMessage : RestUserMessage | |||||
{ | |||||
// Token used to delete/modify this followup message | |||||
internal string Token { get; } | |||||
internal RestFollowupMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel) | |||||
: base(discord, id, channel, author, MessageSource.Bot) | |||||
{ | |||||
this.Token = token; | |||||
} | |||||
internal static RestFollowupMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel) | |||||
{ | |||||
var entity = new RestFollowupMessage(discord, model.Id, model.Author.IsSpecified ? RestUser.Create(discord, model.Author.Value) : discord.CurrentUser, token, channel); | |||||
entity.Update(model); | |||||
return entity; | |||||
} | |||||
internal new void Update(Model model) | |||||
{ | |||||
base.Update(model); | |||||
} | |||||
/// <summary> | |||||
/// Deletes this object and all of it's childern. | |||||
/// </summary> | |||||
/// <returns>A task that represents the asynchronous delete operation.</returns> | |||||
public Task DeleteAsync() | |||||
=> InteractionHelper.DeleteFollowupMessage(Discord, this); | |||||
/// <summary> | |||||
/// Modifies this interaction followup message. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method modifies this message with the specified properties. To see an example of this | |||||
/// method and what properties are available, please refer to <see cref="MessageProperties"/>. | |||||
/// </remarks> | |||||
/// <example> | |||||
/// <para>The following example replaces the content of the message with <c>Hello World!</c>.</para> | |||||
/// <code language="cs"> | |||||
/// await msg.ModifyAsync(x => x.Content = "Hello World!"); | |||||
/// </code> | |||||
/// </example> | |||||
/// <param name="func">A delegate containing the properties to modify the message 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> | |||||
/// <exception cref="InvalidOperationException">The token used to modify/delete this message expired.</exception> | |||||
/// /// <exception cref="Discord.Net.HttpException">Somthing went wrong during the request.</exception> | |||||
public new async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) | |||||
{ | |||||
try | |||||
{ | |||||
var model = await InteractionHelper.ModifyFollowupMessage(Discord, this, func, options).ConfigureAwait(false); | |||||
this.Update(model); | |||||
} | |||||
catch (Discord.Net.HttpException x) | |||||
{ | |||||
if(x.HttpCode == System.Net.HttpStatusCode.NotFound) | |||||
{ | |||||
throw new InvalidOperationException("The token of this message has expired!", x); | |||||
} | |||||
throw; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,81 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Model = Discord.API.Message; | |||||
namespace Discord.Rest | |||||
{ | |||||
/// <summary> | |||||
/// Represents the initial REST-based response to a slash command. | |||||
/// </summary> | |||||
public class RestInteractionMessage : RestUserMessage | |||||
{ | |||||
// Token used to delete/modify this followup message | |||||
internal string Token { get; } | |||||
internal RestInteractionMessage(BaseDiscordClient discord, ulong id, IUser author, string token, IMessageChannel channel) | |||||
: base(discord, id, channel, author, MessageSource.Bot) | |||||
{ | |||||
this.Token = token; | |||||
} | |||||
internal static RestInteractionMessage Create(BaseDiscordClient discord, Model model, string token, IMessageChannel channel) | |||||
{ | |||||
var entity = new RestInteractionMessage(discord, model.Id, model.Author.IsSpecified ? RestUser.Create(discord, model.Author.Value) : discord.CurrentUser, token, channel); | |||||
entity.Update(model); | |||||
return entity; | |||||
} | |||||
internal new void Update(Model model) | |||||
{ | |||||
base.Update(model); | |||||
} | |||||
/// <summary> | |||||
/// Deletes this object and all of it's childern. | |||||
/// </summary> | |||||
/// <returns>A task that represents the asynchronous delete operation.</returns> | |||||
public Task DeleteAsync() | |||||
=> InteractionHelper.DeletedInteractionResponse(Discord, this); | |||||
/// <summary> | |||||
/// Modifies this interaction response | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This method modifies this message with the specified properties. To see an example of this | |||||
/// method and what properties are available, please refer to <see cref="MessageProperties"/>. | |||||
/// </remarks> | |||||
/// <example> | |||||
/// <para>The following example replaces the content of the message with <c>Hello World!</c>.</para> | |||||
/// <code language="cs"> | |||||
/// await msg.ModifyAsync(x => x.Content = "Hello World!"); | |||||
/// </code> | |||||
/// </example> | |||||
/// <param name="func">A delegate containing the properties to modify the message 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> | |||||
/// <exception cref="InvalidOperationException">The token used to modify/delete this message expired.</exception> | |||||
/// /// <exception cref="Discord.Net.HttpException">Somthing went wrong during the request.</exception> | |||||
public new async Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null) | |||||
{ | |||||
try | |||||
{ | |||||
var model = await InteractionHelper.ModifyInteractionResponse(Discord, this, func, options).ConfigureAwait(false); | |||||
this.Update(model); | |||||
} | |||||
catch (Discord.Net.HttpException x) | |||||
{ | |||||
if (x.HttpCode == System.Net.HttpStatusCode.NotFound) | |||||
{ | |||||
throw new InvalidOperationException("The token of this message has expired!", x); | |||||
} | |||||
throw; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -99,6 +99,7 @@ namespace Discord.Net.Queue | |||||
default: | default: | ||||
int? code = null; | int? code = null; | ||||
string reason = null; | string reason = null; | ||||
object errors = null; | |||||
if (response.Stream != null) | if (response.Stream != null) | ||||
{ | { | ||||
try | try | ||||
@@ -109,11 +110,12 @@ namespace Discord.Net.Queue | |||||
var json = JToken.Load(jsonReader); | var json = JToken.Load(jsonReader); | ||||
try { code = json.Value<int>("code"); } catch { }; | try { code = json.Value<int>("code"); } catch { }; | ||||
try { reason = json.Value<string>("message"); } catch { }; | try { reason = json.Value<string>("message"); } catch { }; | ||||
try { errors = json.Value<object>("errors"); } catch { }; | |||||
} | } | ||||
} | } | ||||
catch { } | catch { } | ||||
} | } | ||||
throw new HttpException(response.StatusCode, request, code, reason); | |||||
throw new HttpException(response.StatusCode, request, code, reason, errors); | |||||
} | } | ||||
} | } | ||||
else | else | ||||
@@ -111,7 +111,7 @@ namespace Discord.WebSocket | |||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
/// Discord interactions will not appear in chat until the client responds to them. With this option set to | /// Discord interactions will not appear in chat until the client responds to them. With this option set to | ||||
/// <see langword="true"/>, the client will automatically acknowledge the interaction with <see cref="InteractionResponseType.ACKWithSource"/>. | |||||
/// <see langword="true"/>, the client will automatically acknowledge the interaction with <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>. | |||||
/// See <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-interactionresponsetype">the docs</see> on | /// See <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-interactionresponsetype">the docs</see> on | ||||
/// responding to interactions for more info. | /// responding to interactions for more info. | ||||
/// </para> | /// </para> | ||||
@@ -28,8 +28,7 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// The <see cref="SocketGuildUser"/> who triggered this interaction. | /// The <see cref="SocketGuildUser"/> who triggered this interaction. | ||||
/// </summary> | /// </summary> | ||||
public SocketGuildUser User | |||||
=> Guild.GetUser(UserId); | |||||
public SocketGuildUser User { get; private set; } | |||||
/// <summary> | /// <summary> | ||||
/// The type of this interaction. | /// The type of this interaction. | ||||
@@ -87,6 +86,9 @@ namespace Discord.WebSocket | |||||
this.Version = model.Version; | this.Version = model.Version; | ||||
this.UserId = model.Member.User.Id; | this.UserId = model.Member.User.Id; | ||||
this.Type = model.Type; | this.Type = model.Type; | ||||
if (this.User == null) | |||||
this.User = SocketGuildUser.Create(this.Guild, Discord.State, model.Member); // Change from getter. | |||||
} | } | ||||
private bool CheckToken() | private bool CheckToken() | ||||
{ | { | ||||