From a3b257394490391a7b47858925ad81c43189d058 Mon Sep 17 00:00:00 2001 From: drobbins329 Date: Tue, 31 Aug 2021 18:16:05 -0400 Subject: [PATCH 1/2] Fix Guild Owner and Admin GuildPermissions.All (#132) * Fix Guild Owner and Admin GuildPermissions.All added bits for new permissions * added _ spacing back --- src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 9503e5b3b..6c03ed0ad 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -12,7 +12,7 @@ namespace Discord /// Gets a that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); /// Gets a that grants all guild permissions. - public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111111_1111111111111_111111); + public static readonly GuildPermissions All = new GuildPermissions(0b11111111_11111_1111111_1111111111111_11111); /// Gets a packed value representing all the permissions in this . public ulong RawValue { get; } From 562e509f576de32c7c7fa100a5bf3642bdf1e719 Mon Sep 17 00:00:00 2001 From: exsersewo Date: Tue, 31 Aug 2021 23:18:32 +0100 Subject: [PATCH 2/2] Allow file uploading on followup (#131) --- .../API/Rest/CreateWebhookMessageParams.cs | 46 +++++++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 7 +- .../Discord.Net.WebSocket.xml | 48 +++++++++++ .../SocketMessageComponent.cs | 81 +++++++++++++++++++ .../SocketBaseCommand/SocketCommandBase.cs | 81 +++++++++++++++++++ .../Entities/Interaction/SocketInteraction.cs | 39 +++++++++ 6 files changed, 300 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 1806d487c..47f5bb2a9 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -1,11 +1,18 @@ #pragma warning disable CS1591 +using Discord.Net.Converters; +using Discord.Net.Rest; using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Text; namespace Discord.API.Rest { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateWebhookMessageParams { + private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + [JsonProperty("content")] public string Content { get; set; } @@ -32,5 +39,44 @@ namespace Discord.API.Rest [JsonProperty("components")] public Optional Components { get; set; } + + [JsonProperty("file")] + public Optional File { get; set; } + + public IReadOnlyDictionary ToDictionary() + { + var d = new Dictionary(); + + if (File.IsSpecified) + { + d["file"] = File.Value; + } + + var payload = new Dictionary(); + + payload["content"] = Content; + + if (IsTTS.IsSpecified) + payload["tts"] = IsTTS.Value.ToString(); + if (Nonce.IsSpecified) + payload["nonce"] = Nonce.Value; + if (Username.IsSpecified) + payload["username"] = Username.Value; + if (AvatarUrl.IsSpecified) + payload["avatar_url"] = AvatarUrl.Value; + if (Embeds.IsSpecified) + payload["embeds"] = Embeds.Value; + if (AllowedMentions.IsSpecified) + payload["allowed_mentions"] = AllowedMentions.Value; + + var json = new StringBuilder(); + using (var text = new StringWriter(json)) + using (var writer = new JsonTextWriter(text)) + _serializer.Serialize(writer, payload); + + d["payload_json"] = json.ToString(); + + return d; + } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 6a5daaf8d..ead0c0e8a 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1286,7 +1286,7 @@ namespace Discord.API public async Task CreateInteractionFollowupMessage(CreateWebhookMessageParams args, string token, RequestOptions options = null) { - if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.File.IsSpecified) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) @@ -1294,7 +1294,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); + if (!args.File.IsSpecified) + return await SendJsonAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); + else + return await SendMultipartAsync("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); } public async Task ModifyInteractionFollowupMessage(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml index eb05b12e2..e253692db 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.xml @@ -3911,6 +3911,12 @@ + + + + + + Defers an interaction and responds with type 5 () @@ -4090,6 +4096,12 @@ + + + + + + Acknowledges this interaction with the . @@ -4194,6 +4206,42 @@ The sent message. + + + Sends a followup message for this interaction. + + The text of the message to be sent + The file to upload + The file name of the attachment + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + The sent message. + + + + + Sends a followup message for this interaction. + + The text of the message to be sent + The file to upload + The file name of the attachment + A array of embeds to send with this response. Max 10 + if the message should be read out by a text-to-speech reader, otherwise . + if the response should be hidden to everyone besides the invoker of the command, otherwise . + The allowed mentions for this response. + The request options for this response. + A to be sent with this response + A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + + The sent message. + + Gets the original response for this interaction. diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs index cc91b098f..006c70758 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/Message Components/SocketMessageComponent.cs @@ -5,6 +5,8 @@ using Model = Discord.API.Interaction; using DataModel = Discord.API.MessageComponentInteractionData; using Discord.Rest; using System.Collections.Generic; +using Discord.Net.Rest; +using System.IO; namespace Discord.WebSocket { @@ -243,6 +245,85 @@ namespace Discord.WebSocket return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } + /// + public override async Task FollowupWithFileAsync( + string text = null, + Stream fileStream = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + public override async Task FollowupWithFileAsync( + string text = null, + string filePath = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + /// /// Defers an interaction and responds with type 5 () /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs index 158368b8e..4161a8473 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs @@ -1,6 +1,8 @@ +using Discord.Net.Rest; using Discord.Rest; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -146,6 +148,85 @@ namespace Discord.WebSocket return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); } + /// + public override async Task FollowupWithFileAsync( + string text = null, + Stream fileStream = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNull(fileStream, nameof(fileStream), "File Stream must have data"); + Preconditions.NotNullOrWhitespace(fileName, nameof(fileName), "File Name must not be empty or null"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + + /// + public override async Task FollowupWithFileAsync( + string text = null, + string filePath = null, + string fileName = null, + Embed[] embeds = null, + bool isTTS = false, + bool ephemeral = false, + AllowedMentions allowedMentions = null, + RequestOptions options = null, + MessageComponent component = null, + Embed embed = null) + { + if (!IsValidToken) + throw new InvalidOperationException("Interaction token is no longer valid"); + + if (embeds == null && embed != null) + embeds = new[] { embed }; + Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); + Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); + Preconditions.AtMost(embeds?.Length ?? 0, 10, nameof(embeds), "A max of 10 embeds are allowed."); + Preconditions.NotNullOrWhitespace(filePath, nameof(filePath), "Path must exist"); + + var args = new API.Rest.CreateWebhookMessageParams + { + Content = text, + AllowedMentions = allowedMentions?.ToModel() ?? Optional.Unspecified, + IsTTS = isTTS, + Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional.Unspecified, + Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional.Unspecified, + File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional.Unspecified + }; + + if (ephemeral) + args.Flags = MessageFlags.Ephemeral; + + return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); + } + /// /// Acknowledges this interaction with the . /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index 9b42ed0e2..8a13738e2 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; using Model = Discord.API.Interaction; using DataModel = Discord.API.ApplicationCommandInteractionData; +using System.IO; namespace Discord.WebSocket { @@ -145,6 +146,44 @@ namespace Discord.WebSocket public abstract Task FollowupAsync(string text = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent + /// The file to upload + /// The file name of the attachment + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(string text = null, Stream fileStream = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + + /// + /// Sends a followup message for this interaction. + /// + /// The text of the message to be sent + /// The file to upload + /// The file name of the attachment + /// A array of embeds to send with this response. Max 10 + /// if the message should be read out by a text-to-speech reader, otherwise . + /// if the response should be hidden to everyone besides the invoker of the command, otherwise . + /// The allowed mentions for this response. + /// The request options for this response. + /// A to be sent with this response + /// A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored. + /// + /// The sent message. + /// + public abstract Task FollowupWithFileAsync(string text = null, string filePath = null, string fileName = null, Embed[] embeds = null, bool isTTS = false, bool ephemeral = false, + AllowedMentions allowedMentions = null, RequestOptions options = null, MessageComponent component = null, Embed embed = null); + /// /// Gets the original response for this interaction. ///