diff --git a/DisCatSharp/Clients/DiscordWebhookClient.cs b/DisCatSharp/Clients/DiscordWebhookClient.cs index ed0ade4d5..1c95f4ba8 100644 --- a/DisCatSharp/Clients/DiscordWebhookClient.cs +++ b/DisCatSharp/Clients/DiscordWebhookClient.cs @@ -1,282 +1,255 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Exceptions; using DisCatSharp.Net; using Microsoft.Extensions.Logging; namespace DisCatSharp; /// /// Represents a webhook-only client. This client can be used to execute Discord Webhooks. /// public class DiscordWebhookClient { /// /// Gets the logger for this client. /// public ILogger Logger { get; } /// /// Gets the webhook regex. /// This regex has 2 named capture groups: "id" and "token". /// private static Regex s_webhookRegex { get; } = new(@"(?:https?:\/\/)?discord(?:app)?.com\/api\/(?:v\d\/)?webhooks\/(?\d+)\/(?[A-Za-z0-9_\-]+)", RegexOptions.ECMAScript); /// /// Gets the collection of registered webhooks. /// public IReadOnlyList Webhooks { get; } /// /// Gets or sets the username for registered webhooks. Note that this only takes effect when broadcasting. /// public string Username { get; set; } /// /// Gets or set the avatar for registered webhooks. Note that this only takes effect when broadcasting. /// public string AvatarUrl { get; set; } internal List Hooks; internal DiscordApiClient Apiclient; internal LogLevel MinimumLogLevel; internal string LogTimestampFormat; /// /// Creates a new webhook client. /// public DiscordWebhookClient() : this(null, null) { } /// /// Creates a new webhook client, with specified HTTP proxy, timeout, and logging settings. /// /// The proxy to use for HTTP connections. Defaults to null. /// The optional timeout to use for HTTP requests. Set to to disable timeouts. Defaults to null. /// Whether to use the system clock for computing rate limit resets. See for more details. Defaults to true. /// The optional logging factory to use for this client. Defaults to null. /// The minimum logging level for messages. Defaults to information. /// The timestamp format to use for the logger. public DiscordWebhookClient(IWebProxy proxy = null, TimeSpan? timeout = null, bool useRelativeRateLimit = true, ILoggerFactory loggerFactory = null, LogLevel minimumLogLevel = LogLevel.Information, string logTimestampFormat = "yyyy-MM-dd HH:mm:ss zzz") { this.MinimumLogLevel = minimumLogLevel; this.LogTimestampFormat = logTimestampFormat; if (loggerFactory == null) { loggerFactory = new DefaultLoggerFactory(); loggerFactory.AddProvider(new DefaultLoggerProvider(this)); } this.Logger = loggerFactory.CreateLogger(); var parsedTimeout = timeout ?? TimeSpan.FromSeconds(10); this.Apiclient = new DiscordApiClient(proxy, parsedTimeout, useRelativeRateLimit, this.Logger); this.Hooks = new List(); this.Webhooks = new ReadOnlyCollection(this.Hooks); } /// /// Registers a webhook with this client. This retrieves a webhook based on the ID and token supplied. /// /// The ID of the webhook to add. /// The token of the webhook to add. /// The registered webhook. public async Task AddWebhookAsync(ulong id, string token) { if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); token = token.Trim(); if (this.Hooks.Any(x => x.Id == id)) throw new InvalidOperationException("This webhook is registered with this client."); var wh = await this.Apiclient.GetWebhookWithTokenAsync(id, token).ConfigureAwait(false); this.Hooks.Add(wh); return wh; } /// /// Registers a webhook with this client. This retrieves a webhook from webhook URL. /// /// URL of the webhook to retrieve. This URL must contain both ID and token. /// The registered webhook. public Task AddWebhookAsync(Uri url) { if (url == null) throw new ArgumentNullException(nameof(url)); var m = s_webhookRegex.Match(url.ToString()); if (!m.Success) throw new ArgumentException("Invalid webhook URL supplied.", nameof(url)); var idraw = m.Groups["id"]; var tokenraw = m.Groups["token"]; if (!ulong.TryParse(idraw.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id)) throw new ArgumentException("Invalid webhook URL supplied.", nameof(url)); var token = tokenraw.Value; return this.AddWebhookAsync(id, token); } /// /// Registers a webhook with this client. This retrieves a webhook using the supplied full discord client. /// /// ID of the webhook to register. /// Discord client to which the webhook will belong. /// The registered webhook. public async Task AddWebhookAsync(ulong id, BaseDiscordClient client) { if (client == null) throw new ArgumentNullException(nameof(client)); if (this.Hooks.Any(x => x.Id == id)) throw new ArgumentException("This webhook is already registered with this client."); var wh = await client.ApiClient.GetWebhookAsync(id).ConfigureAwait(false); - // personally I don't think we need to override anything. - // it would even make sense to keep the hook as-is, in case - // it's returned without a token for some bizarre reason - // remember -- discord is not really consistent - //var nwh = new DiscordWebhook() - //{ - // ApiClient = _apiclient, - // AvatarHash = wh.AvatarHash, - // ChannelId = wh.ChannelId, - // GuildId = wh.GuildId, - // Id = wh.Id, - // Name = wh.Name, - // Token = wh.Token, - // User = wh.User, - // Discord = null - //}; this.Hooks.Add(wh); return wh; } /// /// Registers a webhook with this client. This reuses the supplied webhook object. /// /// Webhook to register. /// The registered webhook. public DiscordWebhook AddWebhook(DiscordWebhook webhook) { if (webhook == null) throw new ArgumentNullException(nameof(webhook)); if (this.Hooks.Any(x => x.Id == webhook.Id)) throw new ArgumentException("This webhook is already registered with this client."); - //var nwh = new DiscordWebhook() - //{ - // ApiClient = _apiclient, - // AvatarHash = webhook.AvatarHash, - // ChannelId = webhook.ChannelId, - // GuildId = webhook.GuildId, - // Id = webhook.Id, - // Name = webhook.Name, - // Token = webhook.Token, - // User = webhook.User, - // Discord = null - //}; + webhook.ApiClient = this.Apiclient; this.Hooks.Add(webhook); return webhook; } /// /// Unregisters a webhook with this client. /// /// ID of the webhook to unregister. /// The unregistered webhook. public DiscordWebhook RemoveWebhook(ulong id) { if (!this.Hooks.Any(x => x.Id == id)) throw new ArgumentException("This webhook is not registered with this client."); var wh = this.GetRegisteredWebhook(id); this.Hooks.Remove(wh); return wh; } /// /// Gets a registered webhook with specified ID. /// /// ID of the registered webhook to retrieve. /// The requested webhook. public DiscordWebhook GetRegisteredWebhook(ulong id) => this.Hooks.FirstOrDefault(xw => xw.Id == id); /// /// Broadcasts a message to all registered webhooks. /// /// Webhook builder filled with data to send. /// A dictionary of s and s. public async Task> BroadcastMessageAsync(DiscordWebhookBuilder builder) { var deadhooks = new List(); var messages = new Dictionary(); foreach (var hook in this.Hooks) { try { messages.Add(hook, await hook.ExecuteAsync(builder).ConfigureAwait(false)); } catch (NotFoundException) { deadhooks.Add(hook); } } // Removing dead webhooks from collection foreach (var xwh in deadhooks) this.Hooks.Remove(xwh); return messages; } ~DiscordWebhookClient() { this.Hooks.Clear(); this.Hooks = null; this.Apiclient.Rest.Dispose(); } }