diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index dc2441f6a..7bd9fabfc 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -31,7 +31,8 @@ namespace Discord /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the message. /// A array of s to send with this response. Max 10. - /// A message flag to be applied to the sent message, only is permitted. + /// A message flag to be applied to the sent message, only + /// and is permitted. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -72,7 +73,7 @@ namespace Discord /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the file. /// A array of s to send with this response. Max 10. - /// A message flag to be applied to the sent message, only is permitted. + /// A message flag to be applied to the sent message, only and is permitted. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -110,7 +111,7 @@ namespace Discord /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the file. /// A array of s to send with this response. Max 10. - /// A message flag to be applied to the sent message, only is permitted. + /// A message flag to be applied to the sent message, only and is permitted. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -140,7 +141,7 @@ namespace Discord /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the file. /// A array of s to send with this response. Max 10. - /// A message flag to be applied to the sent message, only is permitted. + /// A message flag to be applied to the sent message, only and is permitted. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. @@ -170,7 +171,7 @@ namespace Discord /// The message components to be included with this message. Used for interactions. /// A collection of stickers to send with the file. /// A array of s to send with this response. Max 10. - /// A message flag to be applied to the sent message, only is permitted. + /// A message flag to be applied to the sent message, only and is permitted. /// /// A task that represents an asynchronous send operation for delivering the message. The task result /// contains the sent message. diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index 7b0d842db..a79c7f70b 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -6,7 +6,7 @@ namespace Discord.API.Rest internal class CreateMessageParams { [JsonProperty("content")] - public string Content { get; } + public Optional Content { get; set; } [JsonProperty("nonce")] public Optional Nonce { get; set; } @@ -31,10 +31,5 @@ namespace Discord.API.Rest [JsonProperty("flags")] public Optional Flags { get; set; } - - public CreateMessageParams(string content) - { - Content = content; - } } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 23328ba24..b8be586cf 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -816,16 +816,24 @@ namespace Discord.API endpoint = () => $"channels/{channelId}/messages?limit={limit}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); - if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && (!args.Stickers.IsSpecified || args.Stickers.Value == null || args.Stickers.Value.Length == 0)) - Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - if (args.Content?.Length > DiscordConfig.MaxMessageSize) + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Stickers.IsSpecified || args.Stickers.Value == null || args.Stickers.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.Components.IsSpecified || args.Components.Value is null || args.Components.Value.Length == 0)) + { + throw new ArgumentException("At least one of 'Content', 'Embeds', 'Stickers' or 'Components' must be specified.", nameof(args)); + } + + if (args.Content.IsSpecified && args.Content.Value is not null && 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); var ids = new BucketIds(channelId: channelId); @@ -842,10 +850,14 @@ namespace Discord.API Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); - if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) - Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.Components.IsSpecified || args.Components.Value is null || args.Components.Value.Length == 0)) + { + throw new ArgumentException("At least one of 'Content', 'Embeds' or 'Components' must be specified.", nameof(args)); + } - if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) + if (args.Content.IsSpecified && args.Content.Value is not null && 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); @@ -875,6 +887,28 @@ namespace Discord.API await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}?{WebhookQuery(false, threadId)}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + /// Message content is too long, length must be less or equal to . + /// This operation may only be called with a token. + public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, UploadWebhookFileParams args, RequestOptions options = null, ulong? threadId = null) + { + if (AuthTokenType != TokenType.Webhook) + throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + + Preconditions.NotNull(args, nameof(args)); + Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + + if (args.Embeds.IsSpecified) + Preconditions.AtMost(args.Embeds.Value.Length, 10, nameof(args.Embeds), "A max of 10 Embeds are allowed."); + if (args.Content.IsSpecified && 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); + + var ids = new BucketIds(webhookId: webhookId); + await SendMultipartAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}?{WebhookQuery(false, threadId)}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + } + /// This operation may only be called with a token. public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null, ulong? threadId = null) { @@ -897,10 +931,17 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); - if (args.Content.GetValueOrDefault(null) == null) - args.Content = ""; - else if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Stickers.IsSpecified || args.Stickers.Value == null || args.Stickers.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.MessageComponent.IsSpecified || args.MessageComponent.Value is null || args.MessageComponent.Value.Length == 0) + && (args.Files.Length == 0)) + { + throw new ArgumentException("At least one of 'Content', 'Embeds', 'Stickers', 'Attachments' or 'Components' must be specified.", nameof(args)); + } + + if (args.Content.IsSpecified && args.Content.Value is not null && 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)); var ids = new BucketIds(channelId: channelId); return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); @@ -917,19 +958,23 @@ namespace Discord.API Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); options = RequestOptions.CreateOrClone(options); - if (args.Content.GetValueOrDefault(null) == null) - args.Content = ""; - else if (args.Content.IsSpecified) + + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.MessageComponents.IsSpecified || args.MessageComponents.Value is null || args.MessageComponents.Value.Length == 0) + && (args.Files.Length == 0)) { - if (args.Content.Value == null) - args.Content = ""; - if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException("At least one of 'Content', 'Embeds', 'Stickers', 'Attachments' or 'Components' must be specified.", nameof(args)); } + if (args.Content.IsSpecified && args.Content.Value is not null && 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)); + + var ids = new BucketIds(webhookId: webhookId); return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?{WebhookQuery(true, threadId)}", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); } + public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -939,6 +984,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}", ids, options: options).ConfigureAwait(false); } + public async Task DeleteMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -967,8 +1013,10 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); Preconditions.NotNull(args, nameof(args)); + if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); @@ -980,8 +1028,10 @@ namespace Discord.API Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); Preconditions.NotNull(args, nameof(args)); + if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); @@ -1444,8 +1494,15 @@ namespace Discord.API public async Task CreateInteractionFollowupMessageAsync(CreateWebhookMessageParams args, string token, RequestOptions options = null) { - 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.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.Components.IsSpecified || args.Components.Value is null || args.Components.Value.Length == 0)) + { + throw new ArgumentException("At least one of 'Content', 'Embeds', 'File' or 'Components' must be specified.", nameof(args)); + } + + if (args.Content.IsSpecified && args.Content.Value is not null && 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)); if (args.Content.IsSpecified && 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)); @@ -1460,9 +1517,12 @@ namespace Discord.API public async Task CreateInteractionFollowupMessageAsync(UploadWebhookFileParams args, string token, RequestOptions options = null) { - if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) && !args.Files.Any()) - Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); - + if ((!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0) + && (!args.Content.IsSpecified || args.Content.Value is null || string.IsNullOrWhiteSpace(args.Content.Value)) + && (!args.MessageComponents.IsSpecified || args.MessageComponents.Value is null || args.MessageComponents.Value.Length == 0)) + { + throw new ArgumentException("At least one of 'Content', 'Embeds', 'Files' or 'Components' must be specified.", nameof(args)); + } if (args.Content.IsSpecified && 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)); diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index fbfa0f3dc..1d114cb0b 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -301,12 +301,12 @@ namespace Discord.Rest Preconditions.AtMost(stickers.Length, 3, nameof(stickers), "A max of 3 stickers are allowed."); } - - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) + if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); - var args = new CreateMessageParams(text) + var args = new CreateMessageParams { + Content = text, IsTTS = isTTS, Embeds = embeds.Any() ? embeds.Select(x => x.ToModel()).ToArray() : Optional.Unspecified, AllowedMentions = allowedMentions?.ToModel(), @@ -343,7 +343,7 @@ namespace Discord.Rest /// is in an invalid format. /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . - /// The only valid are and . + /// The only valid are , and . public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, @@ -376,7 +376,7 @@ namespace Discord.Rest => SendFilesAsync(channel, client, new[] { attachment }, text, isTTS, embed, allowedMentions, messageReference, components, stickers, options, embeds, flags); - /// The only valid are and . + /// The only valid are , and . public static async Task SendFilesAsync(IMessageChannel channel, BaseDiscordClient client, IEnumerable attachments, string text, bool isTTS, Embed embed, AllowedMentions allowedMentions, MessageReference messageReference, MessageComponent components, ISticker[] stickers, RequestOptions options, diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 35c2411bd..59c502f1c 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -127,7 +127,7 @@ namespace Discord.Rest /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -135,7 +135,7 @@ namespace Discord.Rest components, stickers, options, isSpoiler, embeds, flags); /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -144,7 +144,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -153,7 +153,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index ba4cbdb2b..38e20e8f4 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -137,7 +137,7 @@ namespace Discord.Rest /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -146,7 +146,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -155,7 +155,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -164,7 +164,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 1d1e6df96..497bbfdc8 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -110,7 +110,7 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . + /// The only valid are , and . public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -142,8 +142,8 @@ namespace Discord.Rest /// is in an invalid format. /// An I/O error occurred while opening the file. /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + /// The only valid are , and . + public virtual Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -152,8 +152,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -162,8 +162,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -172,8 +172,8 @@ namespace Discord.Rest /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 47e92626b..e2031feff 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -148,7 +148,7 @@ namespace Discord.WebSocket /// /// The only valid are and . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -157,7 +157,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -166,7 +166,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -175,7 +175,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 90dfafb00..7502a4e2f 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -187,7 +187,7 @@ namespace Discord.WebSocket /// /// The only valid are and . - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -196,7 +196,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -205,7 +205,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -214,7 +214,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . /// The only valid are and . - public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 13f4e6694..5d45202e4 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -218,7 +218,7 @@ namespace Discord.WebSocket /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . + /// The only valid are , and . public virtual Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -226,8 +226,8 @@ namespace Discord.WebSocket components, stickers, options, embeds, flags); /// - /// The only valid are and . - public virtual Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + /// The only valid are , and . + public virtual Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -235,8 +235,8 @@ namespace Discord.WebSocket components, stickers, options, isSpoiler, embeds, flags); /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -244,8 +244,8 @@ namespace Discord.WebSocket messageReference, components, stickers, options, isSpoiler, embeds, flags); /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) @@ -253,8 +253,8 @@ namespace Discord.WebSocket messageReference, components, stickers, options, embeds, flags); /// /// Message content is too long, length must be less or equal to . - /// The only valid are and . - public virtual Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + /// The only valid are , and . + public virtual Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 53f5b488e..af1005aea 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -1,5 +1,6 @@ using Discord.Logging; using Discord.Rest; + using System; using System.Collections.Generic; using System.Globalization; @@ -7,195 +8,237 @@ using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Discord.Webhook +namespace Discord.Webhook; + +/// +/// A client responsible for connecting as a Webhook. +/// +public class DiscordWebhookClient : IDisposable { - /// A client responsible for connecting as a Webhook. - public class DiscordWebhookClient : IDisposable + public event Func Log { - public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } - internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); - - private readonly ulong _webhookId; - internal IWebhook Webhook; - internal readonly Logger _restLogger; - - internal API.DiscordRestApiClient ApiClient { get; } - internal LogManager LogManager { get; } - - /// Creates a new Webhook Discord client. - public DiscordWebhookClient(IWebhook webhook) - : this(webhook.Id, webhook.Token, new DiscordRestConfig()) { } - /// Creates a new Webhook Discord client. - public DiscordWebhookClient(ulong webhookId, string webhookToken) - : this(webhookId, webhookToken, new DiscordRestConfig()) { } - /// Creates a new Webhook Discord client. - public DiscordWebhookClient(string webhookUrl) - : this(webhookUrl, new DiscordRestConfig()) { } - - // regex pattern to match webhook urls - private static Regex WebhookUrlRegex = new Regex(@"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - - /// Creates a new Webhook Discord client. - public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config) - : this(config) - { - _webhookId = webhookId; - ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult(); - Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult(); - } - /// Creates a new Webhook Discord client. - public DiscordWebhookClient(IWebhook webhook, DiscordRestConfig config) - : this(config) - { - Webhook = webhook; - _webhookId = Webhook.Id; - } + add => _logEvent.Add(value); + remove => _logEvent.Remove(value); + } - /// - /// Creates a new Webhook Discord client. - /// - /// The url of the webhook. - /// The configuration options to use for this client. - /// Thrown if the is an invalid format. - /// Thrown if the is null or whitespace. - public DiscordWebhookClient(string webhookUrl, DiscordRestConfig config) : this(config) - { - ParseWebhookUrl(webhookUrl, out _webhookId, out string token); - ApiClient.LoginAsync(TokenType.Webhook, token).GetAwaiter().GetResult(); - Webhook = WebhookClientHelper.GetWebhookAsync(this, _webhookId).GetAwaiter().GetResult(); - } + internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + + private readonly ulong _webhookId; + internal IWebhook Webhook; + internal readonly Logger _restLogger; + + internal API.DiscordRestApiClient ApiClient { get; } + internal LogManager LogManager { get; } + + /// + /// Creates a new Webhook Discord client. + /// + public DiscordWebhookClient(IWebhook webhook) + : this(webhook.Id, webhook.Token, new DiscordRestConfig()) { } + + /// + /// Creates a new Webhook Discord client. + /// + public DiscordWebhookClient(ulong webhookId, string webhookToken) + : this(webhookId, webhookToken, new DiscordRestConfig()) { } + + /// + /// Creates a new Webhook Discord client. + /// + public DiscordWebhookClient(string webhookUrl) + : this(webhookUrl, new DiscordRestConfig()) { } + + // regex pattern to match webhook urls + private static Regex WebhookUrlRegex = new Regex(@"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + /// + /// Creates a new Webhook Discord client. + /// + public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config) + : this(config) + { + _webhookId = webhookId; + ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult(); + Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult(); + } + /// Creates a new Webhook Discord client. + public DiscordWebhookClient(IWebhook webhook, DiscordRestConfig config) + : this(config) + { + Webhook = webhook; + _webhookId = Webhook.Id; + } - private DiscordWebhookClient(DiscordRestConfig config) - { - ApiClient = CreateApiClient(config); - LogManager = new LogManager(config.LogLevel); - LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); - - _restLogger = LogManager.CreateLogger("Rest"); - - ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) => - { - if (info == null) - await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); - else - await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); - }; - ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); - } - private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback); - /// Sends a message to the channel for this webhook. - /// Returns the ID of the created message. - public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, - string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, - MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) - => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, components, flags, threadId); - - /// - /// Modifies a message posted using this webhook. - /// - /// - /// This method can only modify messages that were sent using the same webhook. - /// - /// ID of the modified message. - /// A delegate containing the properties to modify the message with. - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous modification operation. - /// - public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null, ulong? threadId = null) - => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options, threadId); - - /// - /// Deletes a message posted using this webhook. - /// - /// - /// This method can only delete messages that were sent using the same webhook. - /// - /// ID of the deleted message. - /// The options to be used when sending the request. - /// - /// A task that represents the asynchronous deletion operation. - /// - public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null, ulong? threadId = null) - => WebhookClientHelper.DeleteMessageAsync(this, messageId, options, threadId); - - /// Sends a message to the channel for this webhook with an attachment. - /// Returns the ID of the created message. - public Task SendFileAsync(string filePath, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, - RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, - MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) - => WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl, - allowedMentions, options, isSpoiler, components, flags, threadId); - /// Sends a message to the channel for this webhook with an attachment. - /// Returns the ID of the created message. - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, - RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, - MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) - => WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username, - avatarUrl, allowedMentions, options, isSpoiler, components, flags, threadId); - - /// Sends a message to the channel for this webhook with an attachment. - /// Returns the ID of the created message. - public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, - RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, - MessageFlags flags = MessageFlags.None, ulong? threadId = null) - => WebhookClientHelper.SendFileAsync(this, attachment, text, isTTS, embeds, username, - avatarUrl, allowedMentions, components, options, flags, threadId); - - /// Sends a message to the channel for this webhook with an attachment. - /// Returns the ID of the created message. - public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, - RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, - MessageFlags flags = MessageFlags.None, ulong? threadId = null) - => WebhookClientHelper.SendFilesAsync(this, attachments, text, isTTS, embeds, username, avatarUrl, - allowedMentions, components, options, flags, threadId); - - - /// Modifies the properties of this webhook. - public Task ModifyWebhookAsync(Action func, RequestOptions options = null) - => Webhook.ModifyAsync(func, options); - - /// Deletes this webhook from Discord and disposes the client. - public async Task DeleteWebhookAsync(RequestOptions options = null) - { - await Webhook.DeleteAsync(options).ConfigureAwait(false); - Dispose(); - } + /// + /// Creates a new Webhook Discord client. + /// + /// The url of the webhook. + /// The configuration options to use for this client. + /// Thrown if the is an invalid format. + /// Thrown if the is null or whitespace. + public DiscordWebhookClient(string webhookUrl, DiscordRestConfig config) : this(config) + { + ParseWebhookUrl(webhookUrl, out _webhookId, out string token); + ApiClient.LoginAsync(TokenType.Webhook, token).GetAwaiter().GetResult(); + Webhook = WebhookClientHelper.GetWebhookAsync(this, _webhookId).GetAwaiter().GetResult(); + } - public void Dispose() - { - ApiClient?.Dispose(); - } + private DiscordWebhookClient(DiscordRestConfig config) + { + ApiClient = CreateApiClient(config); + LogManager = new LogManager(config.LogLevel); + LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); + + _restLogger = LogManager.CreateLogger("Rest"); - internal static void ParseWebhookUrl(string webhookUrl, out ulong webhookId, out string webhookToken) + ApiClient.RequestQueue.RateLimitTriggered += async (id, info, endpoint) => { - if (string.IsNullOrWhiteSpace(webhookUrl)) - throw new ArgumentNullException(paramName: nameof(webhookUrl), message: - "The given webhook Url cannot be null or whitespace."); - - // thrown when groups are not populated/valid, or when there is no match - ArgumentException ex(string reason = null) - => new ArgumentException(paramName: nameof(webhookUrl), message: - $"The given webhook Url was not in a valid format. {reason}"); - var match = WebhookUrlRegex.Match(webhookUrl); - if (match != null) - { - // ensure that the first group is a ulong, set the _webhookId - // 0th group is always the entire match, and 1 is the domain; so start at index 2 - if (!(match.Groups[2].Success && ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId))) - throw ex("The webhook Id could not be parsed."); - - if (!match.Groups[3].Success) - throw ex("The webhook token could not be parsed."); - webhookToken = match.Groups[3].Value; - } + if (info == null) + await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); else - throw ex(); + await _restLogger.WarningAsync($"Rate limit triggered: {endpoint} {(id.IsHashBucket ? $"(Bucket: {id.BucketHash})" : "")}").ConfigureAwait(false); + }; + ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + } + private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) + => new (config.RestClientProvider, DiscordConfig.UserAgent, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback); + + /// + /// Sends a message to the channel for this webhook. + /// + /// + /// Returns the ID of the created message. + /// + public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, + string username = null, string avatarUrl = null, RequestOptions options = null, AllowedMentions allowedMentions = null, + MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) + => WebhookClientHelper.SendMessageAsync(this, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, components, flags, threadId); + + /// + /// Modifies a message posted using this webhook. + /// + /// + /// This method can only modify messages that were sent using the same webhook. + /// + /// ID of the modified message. + /// A delegate containing the properties to modify the message with. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous modification operation. + /// + public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null, ulong? threadId = null) + => WebhookClientHelper.ModifyMessageAsync(this, messageId, func, options, threadId); + + /// + /// Deletes a message posted using this webhook. + /// + /// + /// This method can only delete messages that were sent using the same webhook. + /// + /// ID of the deleted message. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous deletion operation. + /// + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null, ulong? threadId = null) + => WebhookClientHelper.DeleteMessageAsync(this, messageId, options, threadId); + + /// + /// Sends a message to the channel for this webhook with an attachment. + /// + /// + /// Returns the ID of the created message. + /// + public Task SendFileAsync(string filePath, string text, bool isTTS = false, + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, + MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) + => WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl, + allowedMentions, options, isSpoiler, components, flags, threadId); + + /// + /// Sends a message to the channel for this webhook with an attachment. + /// + /// + /// Returns the ID of the created message. + /// + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, + MessageComponent components = null, MessageFlags flags = MessageFlags.None, ulong? threadId = null) + => WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username, + avatarUrl, allowedMentions, options, isSpoiler, components, flags, threadId); + + /// + /// Sends a message to the channel for this webhook with an attachment. + /// + /// + /// Returns the ID of the created message. + /// + public Task SendFileAsync(FileAttachment attachment, string text, bool isTTS = false, + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, + MessageFlags flags = MessageFlags.None, ulong? threadId = null) + => WebhookClientHelper.SendFileAsync(this, attachment, text, isTTS, embeds, username, + avatarUrl, allowedMentions, components, options, flags, threadId); + + /// + /// Sends a message to the channel for this webhook with an attachment. + /// + /// + /// Returns the ID of the created message. + /// + public Task SendFilesAsync(IEnumerable attachments, string text, bool isTTS = false, + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null, AllowedMentions allowedMentions = null, MessageComponent components = null, + MessageFlags flags = MessageFlags.None, ulong? threadId = null) + => WebhookClientHelper.SendFilesAsync(this, attachments, text, isTTS, embeds, username, avatarUrl, + allowedMentions, components, options, flags, threadId); + + + /// + /// Modifies the properties of this webhook. + /// + public Task ModifyWebhookAsync(Action func, RequestOptions options = null) + => Webhook.ModifyAsync(func, options); + + /// + /// Deletes this webhook from Discord and disposes the client. + /// + public async Task DeleteWebhookAsync(RequestOptions options = null) + { + await Webhook.DeleteAsync(options).ConfigureAwait(false); + Dispose(); + } + + public void Dispose() + { + ApiClient?.Dispose(); + } + + internal static void ParseWebhookUrl(string webhookUrl, out ulong webhookId, out string webhookToken) + { + if (string.IsNullOrWhiteSpace(webhookUrl)) + throw new ArgumentNullException(nameof(webhookUrl), "The given webhook Url cannot be null or whitespace."); + + // thrown when groups are not populated/valid, or when there is no match + ArgumentException ex(string reason = null) + => new ($"The given webhook Url was not in a valid format. {reason}", nameof(webhookUrl)); + + var match = WebhookUrlRegex.Match(webhookUrl); + + if (match != null) + { + // ensure that the first group is a ulong, set the _webhookId + // 0th group is always the entire match, and 1 is the domain; so start at index 2 + if (!(match.Groups[2].Success && ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out webhookId))) + throw ex("The webhook Id could not be parsed."); + + if (!match.Groups[3].Success) + throw ex("The webhook token could not be parsed."); + webhookToken = match.Groups[3].Value; } + else + throw ex(); } } diff --git a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs index ca2ff10a0..6cb15bc92 100644 --- a/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs +++ b/src/Discord.Net.Webhook/Entities/Messages/WebhookMessageProperties.cs @@ -26,5 +26,9 @@ namespace Discord.Webhook /// Gets or sets the components that the message should display. /// public Optional Components { get; set; } + /// + /// Gets or sets the attachments for the message. + /// + public Optional> Attachments { get; set; } } } diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index e8bc1e1ac..751286ed5 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -20,6 +20,7 @@ namespace Discord.Webhook throw new InvalidOperationException("Could not find a webhook with the supplied credentials."); return RestInternalWebhook.Create(client, model); } + public static async Task SendMessageAsync(DiscordWebhookClient client, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, MessageComponent components, MessageFlags flags, ulong? threadId = null) @@ -82,26 +83,49 @@ namespace Discord.Webhook } } - var apiArgs = new ModifyWebhookMessageParams + if (!args.Attachments.IsSpecified) { - Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create(), - Embeds = - args.Embeds.IsSpecified - ? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray() - : Optional.Create(), - AllowedMentions = args.AllowedMentions.IsSpecified - ? args.AllowedMentions.Value.ToModel() - : Optional.Create(), - Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, - }; - - await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId) - .ConfigureAwait(false); + var apiArgs = new ModifyWebhookMessageParams + { + Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create(), + Embeds = + args.Embeds.IsSpecified + ? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray() + : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified + ? args.AllowedMentions.Value.ToModel() + : Optional.Create(), + Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + }; + + await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId) + .ConfigureAwait(false); + } + else + { + var apiArgs = new UploadWebhookFileParams(args.Attachments.Value.ToArray()) + { + Content = args.Content.IsSpecified ? args.Content.Value : Optional.Create(), + Embeds = + args.Embeds.IsSpecified + ? args.Embeds.Value.Select(embed => embed.ToModel()).ToArray() + : Optional.Create(), + AllowedMentions = args.AllowedMentions.IsSpecified + ? args.AllowedMentions.Value.ToModel() + : Optional.Create(), + MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional.Unspecified, + }; + + await client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId) + .ConfigureAwait(false); + } } + public static async Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options, ulong? threadId) { await client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options, threadId).ConfigureAwait(false); } + public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler, MessageComponent components, MessageFlags flags = MessageFlags.None, ulong? threadId = null) @@ -110,6 +134,7 @@ namespace Discord.Webhook using (var file = File.OpenRead(filePath)) return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, allowedMentions, options, isSpoiler, components, flags, threadId).ConfigureAwait(false); } + public static Task SendFileAsync(DiscordWebhookClient client, Stream stream, string filename, string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options, bool isSpoiler, MessageComponent components, MessageFlags flags, ulong? threadId) @@ -152,8 +177,8 @@ namespace Discord.Webhook } } - if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds) - throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds and none.", nameof(flags)); + if (flags is not MessageFlags.None and not MessageFlags.SuppressEmbeds and not MessageFlags.SuppressNotification) + throw new ArgumentException("The only valid MessageFlags are SuppressEmbeds, SuppressNotification and none.", nameof(flags)); var args = new UploadWebhookFileParams(attachments.ToArray()) { @@ -170,8 +195,7 @@ namespace Discord.Webhook return msg.Id; } - public static async Task ModifyAsync(DiscordWebhookClient client, - Action func, RequestOptions options) + public static async Task ModifyAsync(DiscordWebhookClient client, Action func, RequestOptions options) { var args = new WebhookProperties(); func(args);