init
This commit is contained in:
commit
ded4f4db26
43 changed files with 2052 additions and 0 deletions
186
Catalogger.Backend/Services/WebhookExecutorService.cs
Normal file
186
Catalogger.Backend/Services/WebhookExecutorService.cs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Catalogger.Backend.Cache;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Humanizer;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
using Remora.Discord.API.Abstractions.Rest;
|
||||
using Remora.Rest.Core;
|
||||
|
||||
namespace Catalogger.Backend.Services;
|
||||
|
||||
public class WebhookExecutorService(
|
||||
Config config,
|
||||
ILogger logger,
|
||||
IWebhookCache webhookCache,
|
||||
ChannelCacheService channelCache,
|
||||
IDiscordRestWebhookAPI webhookApi)
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<WebhookExecutorService>();
|
||||
private readonly Snowflake _applicationId = DiscordSnowflake.New(config.Discord.ApplicationId);
|
||||
private readonly ConcurrentDictionary<ulong, ConcurrentQueue<IEmbed>> _cache = new();
|
||||
private readonly ConcurrentDictionary<ulong, Timer> _timers = new();
|
||||
private IUser? _selfUser;
|
||||
|
||||
public void SetSelfUser(IUser user) => _selfUser = user;
|
||||
|
||||
public async Task QueueLogAsync(Guild guild, LogChannelType logChannelType, IEmbed embed)
|
||||
{
|
||||
var logChannel = GetLogChannel(guild, logChannelType, channelId: null, userId: null);
|
||||
if (logChannel == null) return;
|
||||
|
||||
await QueueLogAsync(logChannel.Value, embed);
|
||||
}
|
||||
|
||||
public async Task QueueLogAsync(ulong channelId, IEmbed embed)
|
||||
{
|
||||
_logger.Debug("Queueing embed for channel {ChannelId}", channelId);
|
||||
var webhook = await webhookCache.GetOrFetchWebhookAsync(channelId, id => FetchWebhookAsync(id));
|
||||
|
||||
var queue = _cache.GetOrAdd(channelId, []);
|
||||
if (queue.Count >= 5)
|
||||
await SendLogsAsync(channelId);
|
||||
queue.Enqueue(embed);
|
||||
|
||||
if (_timers.TryGetValue(channelId, out var existingTimer)) await existingTimer.DisposeAsync();
|
||||
|
||||
_timers[channelId] = new Timer(_ =>
|
||||
{
|
||||
var __ = SendLogsAsync(channelId);
|
||||
}, null, 3000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private async Task SendLogsAsync(ulong channelId)
|
||||
{
|
||||
var queue = _cache.GetValueOrDefault(channelId);
|
||||
if (queue == null) return;
|
||||
var embeds = queue.Take(5).ToList();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private async Task<IWebhook> FetchWebhookAsync(Snowflake channelId, CancellationToken ct = default)
|
||||
{
|
||||
var channelWebhooks =
|
||||
await webhookApi.GetChannelWebhooksAsync(channelId, ct).GetOrThrow();
|
||||
var webhook = channelWebhooks.FirstOrDefault(w => w.ApplicationID == _applicationId && w.Token.IsDefined());
|
||||
if (webhook != null) return webhook;
|
||||
|
||||
return await webhookApi.CreateWebhookAsync(channelId, "Catalogger", default, reason: "Creating logging webhook",
|
||||
ct: ct).GetOrThrow();
|
||||
}
|
||||
|
||||
public ulong? GetLogChannel(Guild guild, LogChannelType logChannelType, Snowflake? channelId = null,
|
||||
ulong? userId = null)
|
||||
{
|
||||
if (channelId == null) return GetDefaultLogChannel(guild, logChannelType);
|
||||
if (!channelCache.GetChannel(channelId.Value, out var channel)) return null;
|
||||
|
||||
Snowflake? categoryId;
|
||||
if (channel.Type is ChannelType.AnnouncementThread or ChannelType.PrivateThread or ChannelType.PublicThread)
|
||||
{
|
||||
// parent_id should always have a value for threads
|
||||
channelId = channel.ParentID.Value!.Value;
|
||||
if (!channelCache.GetChannel(channelId.Value, out var parentChannel))
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
categoryId = parentChannel.ParentID.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelId = channel.ID;
|
||||
categoryId = channel.ParentID.Value;
|
||||
}
|
||||
|
||||
// Check if the channel, or its category, or the user is ignored
|
||||
if (guild.Channels.IgnoredChannels.Contains(channelId.Value.Value) ||
|
||||
categoryId != null && guild.Channels.IgnoredChannels.Contains(categoryId.Value.Value)) return null;
|
||||
if (userId != null)
|
||||
{
|
||||
if (guild.Channels.IgnoredUsers.Contains(userId.Value)) return null;
|
||||
|
||||
// Check the channel-local and category-local ignored users
|
||||
var channelIgnoredUsers =
|
||||
guild.Channels.IgnoredUsersPerChannel.GetValueOrDefault(channelId.Value.Value) ?? [];
|
||||
var categoryIgnoredUsers = (categoryId != null
|
||||
? guild.Channels.IgnoredUsersPerChannel.GetValueOrDefault(categoryId.Value.Value)
|
||||
: []) ?? [];
|
||||
if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value)) return null;
|
||||
}
|
||||
|
||||
// These three events can be redirected to other channels. Redirects can be on a channel or category level.
|
||||
// Obviously, the events are only redirected if they're supposed to be logged in the first place.
|
||||
if (logChannelType is LogChannelType.MessageUpdate or LogChannelType.MessageDelete
|
||||
or LogChannelType.MessageDeleteBulk)
|
||||
{
|
||||
if (GetDefaultLogChannel(guild, logChannelType) == null) return null;
|
||||
|
||||
ulong categoryRedirect = categoryId != null
|
||||
? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value)
|
||||
: 0;
|
||||
|
||||
if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect))
|
||||
return channelRedirect;
|
||||
if (categoryRedirect != 0) return categoryRedirect;
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
return GetDefaultLogChannel(guild, logChannelType);
|
||||
}
|
||||
|
||||
private ulong? GetDefaultLogChannel(Guild guild, LogChannelType channelType) => channelType switch
|
||||
{
|
||||
LogChannelType.GuildUpdate => guild.Channels.GuildUpdate,
|
||||
LogChannelType.GuildEmojisUpdate => guild.Channels.GuildEmojisUpdate,
|
||||
LogChannelType.GuildRoleCreate => guild.Channels.GuildRoleCreate,
|
||||
LogChannelType.GuildRoleUpdate => guild.Channels.GuildRoleUpdate,
|
||||
LogChannelType.GuildRoleDelete => guild.Channels.GuildRoleDelete,
|
||||
LogChannelType.ChannelCreate => guild.Channels.ChannelCreate,
|
||||
LogChannelType.ChannelUpdate => guild.Channels.ChannelUpdate,
|
||||
LogChannelType.ChannelDelete => guild.Channels.ChannelDelete,
|
||||
LogChannelType.GuildMemberAdd => guild.Channels.GuildMemberAdd,
|
||||
LogChannelType.GuildMemberUpdate => guild.Channels.GuildMemberUpdate,
|
||||
LogChannelType.GuildKeyRoleUpdate => guild.Channels.GuildKeyRoleUpdate,
|
||||
LogChannelType.GuildMemberNickUpdate => guild.Channels.GuildMemberNickUpdate,
|
||||
LogChannelType.GuildMemberAvatarUpdate => guild.Channels.GuildMemberAvatarUpdate,
|
||||
LogChannelType.GuildMemberRemove => guild.Channels.GuildMemberRemove,
|
||||
LogChannelType.GuildMemberKick => guild.Channels.GuildMemberKick,
|
||||
LogChannelType.GuildBanAdd => guild.Channels.GuildBanAdd,
|
||||
LogChannelType.GuildBanRemove => guild.Channels.GuildBanRemove,
|
||||
LogChannelType.InviteCreate => guild.Channels.InviteCreate,
|
||||
LogChannelType.InviteDelete => guild.Channels.InviteDelete,
|
||||
LogChannelType.MessageUpdate => guild.Channels.MessageUpdate,
|
||||
LogChannelType.MessageDelete => guild.Channels.MessageDelete,
|
||||
LogChannelType.MessageDeleteBulk => guild.Channels.MessageDeleteBulk,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(channelType))
|
||||
};
|
||||
}
|
||||
|
||||
public enum LogChannelType
|
||||
{
|
||||
GuildUpdate,
|
||||
GuildEmojisUpdate,
|
||||
GuildRoleCreate,
|
||||
GuildRoleUpdate,
|
||||
GuildRoleDelete,
|
||||
ChannelCreate,
|
||||
ChannelUpdate,
|
||||
ChannelDelete,
|
||||
GuildMemberAdd,
|
||||
GuildMemberUpdate,
|
||||
GuildKeyRoleUpdate,
|
||||
GuildMemberNickUpdate,
|
||||
GuildMemberAvatarUpdate,
|
||||
GuildMemberRemove,
|
||||
GuildMemberKick,
|
||||
GuildBanAdd,
|
||||
GuildBanRemove,
|
||||
InviteCreate,
|
||||
InviteDelete,
|
||||
MessageUpdate,
|
||||
MessageDelete,
|
||||
MessageDeleteBulk
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue