fix: remove unnecessary async methods, fix PluralkitApiService
This commit is contained in:
parent
086eb95452
commit
db01f879bd
10 changed files with 96 additions and 73 deletions
|
|
@ -69,7 +69,7 @@ public class ChannelCreateResponder(
|
||||||
}
|
}
|
||||||
|
|
||||||
var guildConfig = await db.GetGuildAsync(ch.GuildID.Value, ct);
|
var guildConfig = await db.GetGuildAsync(ch.GuildID.Value, ct);
|
||||||
await webhookExecutor.QueueLogAsync(guildConfig, LogChannelType.ChannelCreate, builder.Build().GetOrThrow());
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.ChannelCreate, builder.Build().GetOrThrow());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ public class ChannelDeleteResponder(
|
||||||
if (channel.Topic.IsDefined(out var topic))
|
if (channel.Topic.IsDefined(out var topic))
|
||||||
embed.AddField("Description", topic);
|
embed.AddField("Description", topic);
|
||||||
|
|
||||||
await webhookExecutor.QueueLogAsync(guildConfig, LogChannelType.ChannelDelete, embed.Build().GetOrThrow());
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.ChannelDelete, embed.Build().GetOrThrow());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,8 +141,7 @@ public class ChannelUpdateResponder(
|
||||||
// Sometimes we get channel update events for channels that didn't actually have anything loggable change.
|
// Sometimes we get channel update events for channels that didn't actually have anything loggable change.
|
||||||
// If that happens, there will be no embed fields, so just check for that
|
// If that happens, there will be no embed fields, so just check for that
|
||||||
if (builder.Fields.Count == 0) return Result.Success;
|
if (builder.Fields.Count == 0) return Result.Success;
|
||||||
await webhookExecutor.QueueLogAsync(guildConfig, LogChannelType.ChannelUpdate,
|
webhookExecutor.QueueLog(guildConfig, LogChannelType.ChannelUpdate, builder.Build().GetOrThrow());
|
||||||
builder.Build().GetOrThrow());
|
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public class GuildCreateResponder(
|
||||||
_logger.Information("Joined new guild {GuildName} / {GuildId}", guildName, guildId);
|
_logger.Information("Joined new guild {GuildName} / {GuildId}", guildName, guildId);
|
||||||
|
|
||||||
if (config.Discord.GuildLogId != null && evt.Guild.IsT0)
|
if (config.Discord.GuildLogId != null && evt.Guild.IsT0)
|
||||||
await webhookExecutor.QueueLogAsync(config.Discord.GuildLogId.Value, new EmbedBuilder()
|
webhookExecutor.QueueLog(config.Discord.GuildLogId.Value, new EmbedBuilder()
|
||||||
.WithTitle("Joined new guild")
|
.WithTitle("Joined new guild")
|
||||||
.WithDescription($"Joined new guild **{guild.Name}**")
|
.WithDescription($"Joined new guild **{guild.Name}**")
|
||||||
.WithFooter($"ID: {guild.ID}")
|
.WithFooter($"ID: {guild.ID}")
|
||||||
|
|
@ -75,24 +75,24 @@ public class GuildCreateResponder(
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> RespondAsync(IGuildDelete evt, CancellationToken ct = default)
|
public Task<Result> RespondAsync(IGuildDelete evt, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (evt.IsUnavailable.OrDefault(false))
|
if (evt.IsUnavailable.OrDefault(false))
|
||||||
{
|
{
|
||||||
_logger.Debug("Guild {GuildId} became unavailable", evt.ID);
|
_logger.Debug("Guild {GuildId} became unavailable", evt.ID);
|
||||||
return Result.Success;
|
return Task.FromResult(Result.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!guildCache.TryGet(evt.ID, out var guild))
|
if (!guildCache.TryGet(evt.ID, out var guild))
|
||||||
{
|
{
|
||||||
_logger.Information("Left uncached guild {GuildId}", evt.ID);
|
_logger.Information("Left uncached guild {GuildId}", evt.ID);
|
||||||
return Result.Success;
|
return Task.FromResult(Result.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Information("Left guild {GuildName} / {GuildId}", guild.Name, guild.ID);
|
_logger.Information("Left guild {GuildName} / {GuildId}", guild.Name, guild.ID);
|
||||||
|
|
||||||
if (config.Discord.GuildLogId != null)
|
if (config.Discord.GuildLogId != null)
|
||||||
await webhookExecutor.QueueLogAsync(config.Discord.GuildLogId.Value, new EmbedBuilder()
|
webhookExecutor.QueueLog(config.Discord.GuildLogId.Value, new EmbedBuilder()
|
||||||
.WithTitle("Left guild")
|
.WithTitle("Left guild")
|
||||||
.WithDescription($"Left guild **{guild.Name}**")
|
.WithDescription($"Left guild **{guild.Name}**")
|
||||||
.WithFooter($"ID: {guild.ID}")
|
.WithFooter($"ID: {guild.ID}")
|
||||||
|
|
@ -103,6 +103,6 @@ public class GuildCreateResponder(
|
||||||
.Build()
|
.Build()
|
||||||
.GetOrThrow());
|
.GetOrThrow());
|
||||||
|
|
||||||
return Result.Success;
|
return Task.FromResult(Result.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ using Catalogger.Backend.Services;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Remora.Discord.API;
|
using Remora.Discord.API;
|
||||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||||
|
using Remora.Discord.API.Abstractions.Objects;
|
||||||
using Remora.Discord.API.Abstractions.Rest;
|
using Remora.Discord.API.Abstractions.Rest;
|
||||||
using Remora.Discord.API.Objects;
|
using Remora.Discord.API.Objects;
|
||||||
using Remora.Discord.Extensions.Embeds;
|
using Remora.Discord.Extensions.Embeds;
|
||||||
|
|
@ -110,8 +111,9 @@ public class GuildMemberAddResponder(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (embeds.Count > 1)
|
if (embeds.Count > 1)
|
||||||
await webhookExecutor.SendLogWithAttachmentsAsync(guildConfig.Channels.GuildMemberAdd, embeds, []);
|
await webhookExecutor.SendLogAsync(guildConfig.Channels.GuildMemberAdd,
|
||||||
else await webhookExecutor.QueueLogAsync(guildConfig.Channels.GuildMemberAdd, embeds[0]);
|
embeds.Cast<IEmbed>().ToList(), []);
|
||||||
|
else webhookExecutor.QueueLog(guildConfig.Channels.GuildMemberAdd, embeds[0]);
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public class MessageDeleteResponder(
|
||||||
if (msg == null)
|
if (msg == null)
|
||||||
{
|
{
|
||||||
if (logChannel == null) return Result.Success;
|
if (logChannel == null) return Result.Success;
|
||||||
await webhookExecutor.QueueLogAsync(logChannel.Value, new Embed(
|
webhookExecutor.QueueLog(logChannel.Value, new Embed(
|
||||||
Title: "Message deleted",
|
Title: "Message deleted",
|
||||||
Description: $"A message not found in the database was deleted in <#{ev.ChannelID}> ({ev.ChannelID}).",
|
Description: $"A message not found in the database was deleted in <#{ev.ChannelID}> ({ev.ChannelID}).",
|
||||||
Footer: new EmbedFooter(Text: $"ID: {ev.ID}"),
|
Footer: new EmbedFooter(Text: $"ID: {ev.ID}"),
|
||||||
|
|
@ -121,7 +121,7 @@ public class MessageDeleteResponder(
|
||||||
if (!string.IsNullOrWhiteSpace(attachmentInfo)) builder.AddField("Attachments", attachmentInfo, false);
|
if (!string.IsNullOrWhiteSpace(attachmentInfo)) builder.AddField("Attachments", attachmentInfo, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
await webhookExecutor.QueueLogAsync(logChannel.Value, builder.Build().GetOrThrow());
|
webhookExecutor.QueueLog(logChannel.Value, builder.Build().GetOrThrow());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ public class MessageUpdateResponder(
|
||||||
|
|
||||||
embedBuilder.AddField("Link", $"https://discord.com/channels/{msg.GuildID}/{msg.ChannelID}/{msg.ID}");
|
embedBuilder.AddField("Link", $"https://discord.com/channels/{msg.GuildID}/{msg.ChannelID}/{msg.ID}");
|
||||||
|
|
||||||
await webhookExecutor.QueueLogAsync(logChannel.Value, embedBuilder.Build().GetOrThrow());
|
webhookExecutor.QueueLog(logChannel.Value, embedBuilder.Build().GetOrThrow());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||||
<PackageReference Include="NodaTime" Version="3.1.11"/>
|
<PackageReference Include="NodaTime" Version="3.1.11"/>
|
||||||
|
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.4"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="8.0.4"/>
|
||||||
<PackageReference Include="Polly.Core" Version="8.4.1"/>
|
<PackageReference Include="Polly.Core" Version="8.4.1"/>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ using System.Text.Json;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
|
using NodaTime.Text;
|
||||||
using Polly;
|
using Polly;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Services;
|
namespace Catalogger.Backend.Services;
|
||||||
|
|
@ -46,8 +48,14 @@ public class PluralkitApiService(ILogger logger)
|
||||||
throw new CataloggerError("Non-200 status code from PluralKit API");
|
throw new CataloggerError("Non-200 status code from PluralKit API");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await resp.Content.ReadFromJsonAsync<T>(new JsonSerializerOptions
|
var jsonOptions = new JsonSerializerOptions
|
||||||
{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }, ct) ??
|
{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }
|
||||||
|
.ConfigureForNodaTime(new NodaJsonSettings
|
||||||
|
{
|
||||||
|
InstantConverter = new NodaPatternConverter<Instant>(InstantPattern.ExtendedIso)
|
||||||
|
});
|
||||||
|
|
||||||
|
return await resp.Content.ReadFromJsonAsync<T>(jsonOptions, ct) ??
|
||||||
throw new CataloggerError("JSON response from PluralKit API was null");
|
throw new CataloggerError("JSON response from PluralKit API was null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ using Guild = Catalogger.Backend.Database.Models.Guild;
|
||||||
|
|
||||||
namespace Catalogger.Backend.Services;
|
namespace Catalogger.Backend.Services;
|
||||||
|
|
||||||
// TODO: this entire class is a mess, clean it up
|
|
||||||
public class WebhookExecutorService(
|
public class WebhookExecutorService(
|
||||||
Config config,
|
Config config,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
|
|
@ -28,14 +27,79 @@ public class WebhookExecutorService(
|
||||||
|
|
||||||
public void SetSelfUser(IUser user) => _selfUser = user;
|
public void SetSelfUser(IUser user) => _selfUser = user;
|
||||||
|
|
||||||
public async Task QueueLogAsync(Guild guild, LogChannelType logChannelType, IEmbed embed)
|
/// <summary>
|
||||||
|
/// Queues a log embed for the given log channel type.
|
||||||
|
/// If the log channel is already known, use the ulong overload of this method instead.
|
||||||
|
/// If the log channel depends on the source channel and source user, also use the ulong overload.
|
||||||
|
/// </summary>
|
||||||
|
public void QueueLog(Guild guildConfig, LogChannelType logChannelType, IEmbed embed)
|
||||||
{
|
{
|
||||||
var logChannel = GetLogChannel(guild, logChannelType, channelId: null, userId: null);
|
var logChannel = GetLogChannel(guildConfig, logChannelType, channelId: null, userId: null);
|
||||||
if (logChannel == null) return;
|
if (logChannel == null) return;
|
||||||
|
|
||||||
await QueueLogAsync(logChannel.Value, embed);
|
QueueLog(logChannel.Value, embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a log embed for the given channel ID.
|
||||||
|
/// </summary>
|
||||||
|
public void QueueLog(ulong channelId, IEmbed embed)
|
||||||
|
{
|
||||||
|
if (channelId == 0) return;
|
||||||
|
|
||||||
|
var queue = _cache.GetOrAdd(channelId, []);
|
||||||
|
queue.Enqueue(embed);
|
||||||
|
_cache[channelId] = queue;
|
||||||
|
|
||||||
|
SetTimer(channelId, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends multiple embeds and/or files to a channel, bypassing the embed queue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelId">The channel ID to send the content to.</param>
|
||||||
|
/// <param name="embeds">The embeds to send. Must be under 6000 characters in length total, this is not checked by this method.</param>
|
||||||
|
/// <param name="files">The files to send.</param>
|
||||||
|
public async Task SendLogAsync(ulong channelId, List<IEmbed> embeds, IEnumerable<FileData> files)
|
||||||
|
{
|
||||||
|
if (channelId == 0) return;
|
||||||
|
|
||||||
|
var attachments = files
|
||||||
|
.Select<FileData, OneOf.OneOf<FileData, IPartialAttachment>>(f => f)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_logger.Debug("Sending {EmbedCount} embeds/{FileCount} files to channel {ChannelId}", embeds.Count,
|
||||||
|
attachments.Count, channelId);
|
||||||
|
|
||||||
|
var webhook = await webhookCache.GetOrFetchWebhookAsync(channelId, id => FetchWebhookAsync(id));
|
||||||
|
await webhookApi.ExecuteWebhookAsync(DiscordSnowflake.New(webhook.Id), webhook.Token, shouldWait: false,
|
||||||
|
embeds: embeds, attachments: attachments, username: _selfUser!.Username,
|
||||||
|
avatarUrl: _selfUser.AvatarUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a 3 second timer for the given channel.
|
||||||
|
/// </summary>
|
||||||
|
private void SetTimer(ulong channelId, ConcurrentQueue<IEmbed> queue)
|
||||||
|
{
|
||||||
|
if (_timers.TryGetValue(channelId, out var existingTimer)) existingTimer.Dispose();
|
||||||
|
_timers[channelId] = new Timer(_ =>
|
||||||
|
{
|
||||||
|
_logger.Debug("Sending 5 queued embeds");
|
||||||
|
|
||||||
|
var __ = SendLogAsync(channelId, TakeFromQueue(channelId).ToList(), []);
|
||||||
|
if (!queue.IsEmpty)
|
||||||
|
{
|
||||||
|
if (_timers.TryGetValue(channelId, out var timer)) timer.Dispose();
|
||||||
|
SetTimer(channelId, queue);
|
||||||
|
}
|
||||||
|
}, null, 3000, Timeout.Infinite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes 5 embeds from the queue for the given channel.
|
||||||
|
/// Note that this locks the queue to prevent duplicate embeds from being sent.
|
||||||
|
/// </summary>
|
||||||
private List<IEmbed> TakeFromQueue(ulong channelId)
|
private List<IEmbed> TakeFromQueue(ulong channelId)
|
||||||
{
|
{
|
||||||
var queue = _cache.GetOrAdd(channelId, []);
|
var queue = _cache.GetOrAdd(channelId, []);
|
||||||
|
|
@ -53,58 +117,7 @@ public class WebhookExecutorService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task QueueLogAsync(ulong channelId, IEmbed embed)
|
// TODO: make it so this method can only have one request per channel in flight simultaneously
|
||||||
{
|
|
||||||
if (channelId == 0) return;
|
|
||||||
|
|
||||||
var queue = _cache.GetOrAdd(channelId, []);
|
|
||||||
queue.Enqueue(embed);
|
|
||||||
_cache[channelId] = queue;
|
|
||||||
|
|
||||||
await SetTimer(channelId, queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetTimer(ulong channelId, ConcurrentQueue<IEmbed> queue)
|
|
||||||
{
|
|
||||||
if (_timers.TryGetValue(channelId, out var existingTimer)) await existingTimer.DisposeAsync();
|
|
||||||
_timers[channelId] = new Timer(_ =>
|
|
||||||
{
|
|
||||||
_logger.Debug("Sending 5 queued embeds");
|
|
||||||
|
|
||||||
var __ = SendLogsAsync(channelId, TakeFromQueue(channelId));
|
|
||||||
if (!queue.IsEmpty)
|
|
||||||
{
|
|
||||||
if (_timers.TryGetValue(channelId, out var timer)) timer.Dispose();
|
|
||||||
var ___ = SetTimer(channelId, queue);
|
|
||||||
}
|
|
||||||
}, null, 3000, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SendLogsAsync(ulong channelId, List<IEmbed> embeds)
|
|
||||||
{
|
|
||||||
_logger.Debug("Sending {Count} embeds to channel {ChannelId}", embeds.Count, channelId);
|
|
||||||
if (embeds.Count == 0) return;
|
|
||||||
|
|
||||||
var webhook = await webhookCache.GetOrFetchWebhookAsync(channelId, id => FetchWebhookAsync(id));
|
|
||||||
|
|
||||||
await webhookApi.ExecuteWebhookAsync(DiscordSnowflake.New(webhook.Id), webhook.Token, shouldWait: false,
|
|
||||||
embeds: embeds, username: _selfUser!.Username, avatarUrl: _selfUser.AvatarUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendLogWithAttachmentsAsync(ulong channelId, List<Embed> embeds, IEnumerable<FileData> files)
|
|
||||||
{
|
|
||||||
if (channelId == 0) return;
|
|
||||||
|
|
||||||
var attachments = files
|
|
||||||
.Select<FileData, OneOf.OneOf<FileData, IPartialAttachment>>(f => f)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var webhook = await webhookCache.GetOrFetchWebhookAsync(channelId, id => FetchWebhookAsync(id));
|
|
||||||
await webhookApi.ExecuteWebhookAsync(DiscordSnowflake.New(webhook.Id), webhook.Token, shouldWait: false,
|
|
||||||
embeds: embeds, attachments: attachments, username: _selfUser!.Username,
|
|
||||||
avatarUrl: _selfUser.AvatarUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IWebhook> FetchWebhookAsync(Snowflake channelId, CancellationToken ct = default)
|
private async Task<IWebhook> FetchWebhookAsync(Snowflake channelId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var channelWebhooks =
|
var channelWebhooks =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue