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; }
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 f427513ff..2a3ee4ba3 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.
///