@@ -12,7 +12,7 @@ namespace Discord | |||
/// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions for webhook users. </summary> | |||
public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | |||
/// <summary> Gets a <see cref="GuildPermissions"/> that grants all guild permissions. </summary> | |||
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111111_1111111111111_111111); | |||
public static readonly GuildPermissions All = new GuildPermissions(0b11111111_11111_1111111_1111111111111_11111); | |||
/// <summary> Gets a packed value representing all the permissions in this <see cref="GuildPermissions"/>. </summary> | |||
public ulong RawValue { get; } | |||
@@ -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<API.ActionRowComponent[]> Components { get; set; } | |||
[JsonProperty("file")] | |||
public Optional<MultipartFile> File { get; set; } | |||
public IReadOnlyDictionary<string, object> ToDictionary() | |||
{ | |||
var d = new Dictionary<string, object>(); | |||
if (File.IsSpecified) | |||
{ | |||
d["file"] = File.Value; | |||
} | |||
var payload = new Dictionary<string, object>(); | |||
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; | |||
} | |||
} | |||
} |
@@ -1286,7 +1286,7 @@ namespace Discord.API | |||
public async Task<Message> 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<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
if (!args.File.IsSpecified) | |||
return await SendJsonAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args, new BucketIds(), options: options).ConfigureAwait(false); | |||
else | |||
return await SendMultipartAsync<Message>("POST", () => $"webhooks/{CurrentUserId}/{token}?wait=true", args.ToDictionary(), new BucketIds(), options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Message> ModifyInteractionFollowupMessage(ModifyInteractionResponseParams args, ulong id, string token, RequestOptions options = null) | |||
@@ -3911,6 +3911,12 @@ | |||
<member name="M:Discord.WebSocket.SocketMessageComponent.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketMessageComponent.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketMessageComponent.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketMessageComponent.DeferLoadingAsync(System.Boolean,Discord.RequestOptions)"> | |||
<summary> | |||
Defers an interaction and responds with type 5 (<see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>) | |||
@@ -4090,6 +4096,12 @@ | |||
<member name="M:Discord.WebSocket.SocketCommandBase.FollowupAsync(System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketCommandBase.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketCommandBase.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<inheritdoc/> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketCommandBase.DeferAsync(System.Boolean,Discord.RequestOptions)"> | |||
<summary> | |||
Acknowledges this interaction with the <see cref="F:Discord.InteractionResponseType.DeferredChannelMessageWithSource"/>. | |||
@@ -4194,6 +4206,42 @@ | |||
The sent message. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketInteraction.FollowupWithFileAsync(System.String,System.IO.Stream,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<summary> | |||
Sends a followup message for this interaction. | |||
</summary> | |||
<param name="text">The text of the message to be sent</param> | |||
<param name="fileStream">The file to upload</param> | |||
<param name="fileName">The file name of the attachment</param> | |||
<param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
<param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
<param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
<param name="allowedMentions">The allowed mentions for this response.</param> | |||
<param name="options">The request options for this response.</param> | |||
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> | |||
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
<returns> | |||
The sent message. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketInteraction.FollowupWithFileAsync(System.String,System.String,System.String,Discord.Embed[],System.Boolean,System.Boolean,Discord.AllowedMentions,Discord.RequestOptions,Discord.MessageComponent,Discord.Embed)"> | |||
<summary> | |||
Sends a followup message for this interaction. | |||
</summary> | |||
<param name="text">The text of the message to be sent</param> | |||
<param name="filePath">The file to upload</param> | |||
<param name="fileName">The file name of the attachment</param> | |||
<param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
<param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
<param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
<param name="allowedMentions">The allowed mentions for this response.</param> | |||
<param name="options">The request options for this response.</param> | |||
<param name="component">A <see cref="T:Discord.MessageComponent"/> to be sent with this response</param> | |||
<param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
<returns> | |||
The sent message. | |||
</returns> | |||
</member> | |||
<member name="M:Discord.WebSocket.SocketInteraction.GetOriginalResponseAsync(Discord.RequestOptions)"> | |||
<summary> | |||
Gets the original response for this interaction. | |||
@@ -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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified | |||
}; | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified | |||
}; | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
} | |||
/// <summary> | |||
/// Defers an interaction and responds with type 5 (<see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>) | |||
/// </summary> | |||
@@ -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); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
File = fileStream is not null ? new MultipartFile(fileStream, fileName) : Optional<MultipartFile>.Unspecified | |||
}; | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
} | |||
/// <inheritdoc/> | |||
public override async Task<RestFollowupMessage> 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<API.AllowedMentions>.Unspecified, | |||
IsTTS = isTTS, | |||
Embeds = embeds?.Select(x => x.ToModel()).ToArray() ?? Optional<API.Embed[]>.Unspecified, | |||
Components = component?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Optional<API.ActionRowComponent[]>.Unspecified, | |||
File = !string.IsNullOrEmpty(filePath) ? new MultipartFile(new MemoryStream(File.ReadAllBytes(filePath), false), fileName) : Optional<MultipartFile>.Unspecified | |||
}; | |||
if (ephemeral) | |||
args.Flags = MessageFlags.Ephemeral; | |||
return await InteractionHelper.SendFollowupAsync(Discord.Rest, args, Token, Channel, options); | |||
} | |||
/// <summary> | |||
/// Acknowledges this interaction with the <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/>. | |||
/// </summary> | |||
@@ -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<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="text">The text of the message to be sent</param> | |||
/// <param name="fileStream">The file to upload</param> | |||
/// <param name="fileName">The file name of the attachment</param> | |||
/// <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
/// <param name="allowedMentions">The allowed mentions for this response.</param> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param> | |||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Sends a followup message for this interaction. | |||
/// </summary> | |||
/// <param name="text">The text of the message to be sent</param> | |||
/// <param name="filePath">The file to upload</param> | |||
/// <param name="fileName">The file name of the attachment</param> | |||
/// <param name="embeds">A array of embeds to send with this response. Max 10</param> | |||
/// <param name="isTTS"><see langword="true"/> if the message should be read out by a text-to-speech reader, otherwise <see langword="false"/>.</param> | |||
/// <param name="ephemeral"><see langword="true"/> if the response should be hidden to everyone besides the invoker of the command, otherwise <see langword="false"/>.</param> | |||
/// <param name="allowedMentions">The allowed mentions for this response.</param> | |||
/// <param name="options">The request options for this response.</param> | |||
/// <param name="component">A <see cref="MessageComponent"/> to be sent with this response</param> | |||
/// <param name="embed">A single embed to send with this response. If this is passed alongside an array of embeds, the single embed will be ignored.</param> | |||
/// <returns> | |||
/// The sent message. | |||
/// </returns> | |||
public abstract Task<RestFollowupMessage> 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); | |||
/// <summary> | |||
/// Gets the original response for this interaction. | |||
/// </summary> | |||