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