@@ -26,5 +26,10 @@ namespace Discord | |||
/// Gets or sets the options for this command. | |||
/// </summary> | |||
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> | |||
/// The id of the interaction. | |||
/// </summary> | |||
ulong Id { get; } | |||
new ulong Id { get; } | |||
/// <summary> | |||
/// The type of this <see cref="IDiscordInteraction"/>. | |||
@@ -11,7 +11,7 @@ namespace Discord | |||
/// </summary> | |||
/// <remarks> | |||
/// 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. | |||
/// You can read more about Response types <see href="https://discord.com/developers/docs/interactions/slash-commands#interaction-response">Here</see> | |||
/// </remarks> | |||
@@ -22,13 +22,13 @@ namespace Discord | |||
/// </summary> | |||
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> | |||
/// ACK a command without sending a message, eating the user's input. | |||
/// </summary> | |||
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> | |||
/// Respond with a message, showing the user's input. | |||
/// </summary> | |||
@@ -26,5 +26,10 @@ namespace Discord | |||
/// Gets or sets the options for this command. | |||
/// </summary> | |||
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. | |||
/// </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> | |||
/// 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="discordCode">The Discord status code returned.</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)) | |||
{ | |||
HttpCode = httpCode; | |||
Request = request; | |||
DiscordCode = discordCode; | |||
Reason = reason; | |||
Error = errors; | |||
} | |||
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 | |||
{ | |||
[JsonProperty("content")] | |||
public string Content { get; set; } | |||
public Optional<string> Content { get; set; } | |||
[JsonProperty("embeds")] | |||
public Optional<Embed[]> Embeds { get; set; } | |||
@@ -211,6 +211,7 @@ namespace Discord.Rest | |||
return response.Select(x => RestGlobalCommand.Create(client, x)).ToArray(); | |||
} | |||
public static async Task<IReadOnlyCollection<RestGuildCommand>> GetGuildApplicationCommands(BaseDiscordClient client, ulong guildId, RequestOptions options) | |||
{ | |||
var response = await client.ApiClient.GetGuildApplicationCommandAsync(guildId, options).ConfigureAwait(false); | |||
@@ -804,7 +804,21 @@ namespace Discord.API | |||
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) | |||
{ | |||
@@ -823,7 +837,21 @@ namespace Discord.API | |||
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) | |||
{ | |||
@@ -852,7 +880,21 @@ namespace Discord.API | |||
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) | |||
{ | |||
@@ -873,7 +915,21 @@ namespace Discord.API | |||
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) | |||
{ | |||
@@ -894,6 +950,14 @@ namespace Discord.API | |||
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) | |||
{ | |||
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); | |||
} | |||
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.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); | |||
@@ -942,6 +1007,56 @@ namespace Discord.API | |||
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 | |||
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 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) | |||
{ | |||
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; | |||
} | |||
@@ -44,7 +57,10 @@ namespace Discord.Rest | |||
Description = args.Description, | |||
Options = args.Options.IsSpecified | |||
? 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); | |||
@@ -68,7 +84,10 @@ namespace Discord.Rest | |||
Description = args.Description, | |||
Options = args.Options.IsSpecified | |||
? 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); | |||
@@ -119,7 +138,10 @@ namespace Discord.Rest | |||
Description = args.Description, | |||
Options = args.Options.IsSpecified | |||
? 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); | |||
@@ -143,7 +165,10 @@ namespace Discord.Rest | |||
Description = args.Description, | |||
Options = args.Options.IsSpecified | |||
? 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); | |||
@@ -158,5 +183,60 @@ namespace Discord.Rest | |||
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> | |||
public async Task<RestGuildCommand> ModifyAsync(Action<ApplicationCommandProperties> func, RequestOptions options = null) | |||
=> 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: | |||
int? code = null; | |||
string reason = null; | |||
object errors = null; | |||
if (response.Stream != null) | |||
{ | |||
try | |||
@@ -109,11 +110,12 @@ namespace Discord.Net.Queue | |||
var json = JToken.Load(jsonReader); | |||
try { code = json.Value<int>("code"); } catch { }; | |||
try { reason = json.Value<string>("message"); } catch { }; | |||
try { errors = json.Value<object>("errors"); } catch { }; | |||
} | |||
} | |||
catch { } | |||
} | |||
throw new HttpException(response.StatusCode, request, code, reason); | |||
throw new HttpException(response.StatusCode, request, code, reason, errors); | |||
} | |||
} | |||
else | |||
@@ -111,7 +111,7 @@ namespace Discord.WebSocket | |||
/// <remarks> | |||
/// <para> | |||
/// 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 | |||
/// responding to interactions for more info. | |||
/// </para> | |||
@@ -28,8 +28,7 @@ namespace Discord.WebSocket | |||
/// <summary> | |||
/// The <see cref="SocketGuildUser"/> who triggered this interaction. | |||
/// </summary> | |||
public SocketGuildUser User | |||
=> Guild.GetUser(UserId); | |||
public SocketGuildUser User { get; private set; } | |||
/// <summary> | |||
/// The type of this interaction. | |||
@@ -87,6 +86,9 @@ namespace Discord.WebSocket | |||
this.Version = model.Version; | |||
this.UserId = model.Member.User.Id; | |||
this.Type = model.Type; | |||
if (this.User == null) | |||
this.User = SocketGuildUser.Create(this.Guild, Discord.State, model.Member); // Change from getter. | |||
} | |||
private bool CheckToken() | |||
{ | |||