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