Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] Weebhook Client #3050

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Discord.Net.Rest/DiscordRestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ namespace Discord.API
internal class DiscordRestApiClient : IDisposable, IAsyncDisposable
{
#region DiscordRestApiClient
private static readonly ConcurrentDictionary<string, Func<BucketIds, BucketId>> _bucketIdGenerators = new ConcurrentDictionary<string, Func<BucketIds, BucketId>>();
private static readonly ConcurrentDictionary<string, Func<BucketIds, BucketId>> _bucketIdGenerators = new ();

public event Func<string, string, double, Task> SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } }
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new AsyncEvent<Func<string, string, double, Task>>();
private readonly AsyncEvent<Func<string, string, double, Task>> _sentRequestEvent = new ();

protected readonly JsonSerializer _serializer;
protected readonly SemaphoreSlim _stateLock;
Expand Down Expand Up @@ -136,6 +136,7 @@ public async Task LoginAsync(TokenType tokenType, string token, RequestOptions o
}
finally { _stateLock.Release(); }
}

private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null)
{
if (LoginState != LoginState.LoggedOut)
Expand Down
113 changes: 42 additions & 71 deletions src/Discord.Net.Webhook/DiscordWebhookClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,82 +13,32 @@ namespace Discord.Webhook;
/// <summary>
/// A client responsible for connecting as a Webhook.
/// </summary>
public class DiscordWebhookClient : IDisposable
public class DiscordWebhookClient : IAsyncDisposable, IDisposable
{
public event Func<LogMessage, Task> Log
{
add => _logEvent.Add(value);
remove => _logEvent.Remove(value);
}

internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new AsyncEvent<Func<LogMessage, Task>>();
internal readonly AsyncEvent<Func<LogMessage, Task>> _logEvent = new();

private readonly ulong _webhookId;
internal IWebhook Webhook;
internal ulong WebhookId;
internal readonly Logger _restLogger;

internal API.DiscordRestApiClient ApiClient { get; }
internal LogManager LogManager { get; }

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
public DiscordWebhookClient(IWebhook webhook)
: this(webhook.Id, webhook.Token, new DiscordRestConfig()) { }

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
public DiscordWebhookClient(ulong webhookId, string webhookToken)
: this(webhookId, webhookToken, new DiscordRestConfig()) { }

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
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);
private static Regex WebhookUrlRegex = new(@"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-z0-9_-]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config)
: this(config)
public DiscordWebhookClient(DiscordRestConfig config = null)
{
_webhookId = webhookId;
ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult();
Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult();
}

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
public DiscordWebhookClient(IWebhook webhook, DiscordRestConfig config)
: this(config)
{
Webhook = webhook;
_webhookId = Webhook.Id;
ApiClient.LoginAsync(TokenType.Webhook, webhook.Token).GetAwaiter().GetResult();
}

/// <summary>
/// Creates a new Webhook Discord client.
/// </summary>
/// <param name="webhookUrl">The url of the webhook.</param>
/// <param name="config">The configuration options to use for this client.</param>
/// <exception cref="ArgumentException">Thrown if the <paramref name="webhookUrl"/> is an invalid format.</exception>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception>
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();
}
config ??= new DiscordRestConfig();

private DiscordWebhookClient(DiscordRestConfig config)
{
ApiClient = CreateApiClient(config);
LogManager = new LogManager(config.LogLevel);
LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false);
Expand All @@ -104,9 +54,30 @@ private DiscordWebhookClient(DiscordRestConfig config)
};
ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false);
}

public async Task LoginAsync(ulong webhookId, string webhookToken)
{
WebhookId = webhookId;

await ApiClient.LoginAsync(TokenType.Webhook, webhookToken).ConfigureAwait(false);
}

public async Task LoginAsync(IWebhook webhook)
{
WebhookId = webhook.Id;

await ApiClient.LoginAsync(TokenType.Webhook, webhook.Token).ConfigureAwait(false);
}

public async Task LoginAsync(string webhookUrl)
{
ParseWebhookUrl(webhookUrl, out WebhookId, out var webhookToken);
await ApiClient.LoginAsync(TokenType.Webhook, webhookToken).ConfigureAwait(false);
}

private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);
=> new(config.RestClientProvider, DiscordConfig.UserAgent, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);

/// <summary>
/// Sends a message to the channel for this webhook.
/// </summary>
Expand Down Expand Up @@ -161,7 +132,7 @@ public Task<ulong> SendFileAsync(string filePath, string text, bool isTTS = fals
string threadName = null, ulong[] appliedTags = null, PollProperties poll = null)
=> WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl,
allowedMentions, options, isSpoiler, components, flags, threadId, threadName, appliedTags, poll);

/// <summary>
/// Sends a message to the channel for this webhook with an attachment.
/// </summary>
Expand Down Expand Up @@ -202,21 +173,13 @@ public Task<ulong> SendFilesAsync(IEnumerable<FileAttachment> attachments, strin
/// Modifies the properties of this webhook.
/// </summary>
public Task ModifyWebhookAsync(Action<WebhookProperties> func, RequestOptions options = null)
=> Webhook.ModifyAsync(func, options);
=> WebhookClientHelper.ModifyAsync(this, func, options);

/// <summary>
/// Deletes this webhook from Discord and disposes the client.
/// </summary>
public async Task DeleteWebhookAsync(RequestOptions options = null)
{
await Webhook.DeleteAsync(options).ConfigureAwait(false);
Dispose();
}

public void Dispose()
{
ApiClient?.Dispose();
}
public Task DeleteWebhookAsync(RequestOptions options = null)
=> WebhookClientHelper.DeleteAsync(this, options);

internal static void ParseWebhookUrl(string webhookUrl, out ulong webhookId, out string webhookToken)
{
Expand All @@ -225,7 +188,7 @@ internal static void ParseWebhookUrl(string webhookUrl, out ulong webhookId, out

// 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));
=> new($"The given webhook Url was not in a valid format. {reason}", nameof(webhookUrl));

var match = WebhookUrlRegex.Match(webhookUrl);

Expand All @@ -243,4 +206,12 @@ ArgumentException ex(string reason = null)
else
throw ex();
}

public async ValueTask DisposeAsync()
{
if (ApiClient != null)
await ApiClient.DisposeAsync();
}

public void Dispose() => ApiClient?.Dispose();
}
17 changes: 7 additions & 10 deletions src/Discord.Net.Webhook/WebhookClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static async Task<ulong> SendMessageAsync(DiscordWebhookClient client,
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 model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options, threadId: threadId).ConfigureAwait(false);
var model = await client.ApiClient.CreateWebhookMessageAsync(client.WebhookId, args, options: options, threadId: threadId).ConfigureAwait(false);
return model.Id;
}

Expand Down Expand Up @@ -111,7 +111,7 @@ public static Task ModifyMessageAsync(DiscordWebhookClient client, ulong message
Components = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
};

return client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId);
return client.ApiClient.ModifyWebhookMessageAsync(client.WebhookId, messageId, apiArgs, options, threadId);
}
else
{
Expand All @@ -130,12 +130,12 @@ public static Task ModifyMessageAsync(DiscordWebhookClient client, ulong message
MessageComponents = args.Components.IsSpecified ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() : Optional<API.ActionRowComponent[]>.Unspecified,
};

return client.ApiClient.ModifyWebhookMessageAsync(client.Webhook.Id, messageId, apiArgs, options, threadId);
return client.ApiClient.ModifyWebhookMessageAsync(client.WebhookId, messageId, apiArgs, options, threadId);
}
}

public static Task DeleteMessageAsync(DiscordWebhookClient client, ulong messageId, RequestOptions options, ulong? threadId)
=> client.ApiClient.DeleteWebhookMessageAsync(client.Webhook.Id, messageId, options, threadId);
=> client.ApiClient.DeleteWebhookMessageAsync(client.WebhookId, messageId, options, threadId);

public static async Task<ulong> SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS,
IEnumerable<Embed> embeds, string username, string avatarUrl, AllowedMentions allowedMentions, RequestOptions options,
Expand Down Expand Up @@ -210,7 +210,7 @@ public static async Task<ulong> SendFilesAsync(DiscordWebhookClient client,
AppliedTags = appliedTags,
Poll = poll?.ToModel() ?? Optional<CreatePollParams>.Unspecified
};
var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options, threadId).ConfigureAwait(false);
var msg = await client.ApiClient.UploadWebhookFileAsync(client.WebhookId, args, options, threadId).ConfigureAwait(false);
return msg.Id;
}

Expand All @@ -224,13 +224,10 @@ public static Task<WebhookModel> ModifyAsync(DiscordWebhookClient client, Action
Name = args.Name
};

if (!apiArgs.Avatar.IsSpecified && client.Webhook.AvatarId != null)
apiArgs.Avatar = new ImageModel(client.Webhook.AvatarId);

return client.ApiClient.ModifyWebhookAsync(client.Webhook.Id, apiArgs, options);
return client.ApiClient.ModifyWebhookAsync(client.WebhookId, apiArgs, options);
}

public static Task DeleteAsync(DiscordWebhookClient client, RequestOptions options)
=> client.ApiClient.DeleteWebhookAsync(client.Webhook.Id, options);
=> client.ApiClient.DeleteWebhookAsync(client.WebhookId, options);
}
}
Loading