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();
}
}