2024-10-14 14:56:40 +02:00
|
|
|
// Copyright (C) 2021-present sam (starshines.gay)
|
|
|
|
|
//
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
|
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
2024-08-13 13:08:50 +02:00
|
|
|
using System.Collections.Concurrent;
|
2024-10-12 23:47:18 +02:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2024-08-13 13:08:50 +02:00
|
|
|
using Catalogger.Backend.Cache;
|
2024-08-19 16:12:28 +02:00
|
|
|
using Catalogger.Backend.Cache.InMemoryCache;
|
2024-08-13 13:08:50 +02:00
|
|
|
using Catalogger.Backend.Extensions;
|
2024-10-28 14:04:55 +01:00
|
|
|
using OneOf;
|
2024-08-13 13:08:50 +02:00
|
|
|
using Remora.Discord.API;
|
|
|
|
|
using Remora.Discord.API.Abstractions.Objects;
|
|
|
|
|
using Remora.Discord.API.Abstractions.Rest;
|
|
|
|
|
using Remora.Rest.Core;
|
2024-08-13 16:48:54 +02:00
|
|
|
using Guild = Catalogger.Backend.Database.Models.Guild;
|
2024-08-13 13:08:50 +02:00
|
|
|
|
|
|
|
|
namespace Catalogger.Backend.Services;
|
|
|
|
|
|
2024-10-12 23:47:18 +02:00
|
|
|
[SuppressMessage(
|
|
|
|
|
"ReSharper",
|
|
|
|
|
"InconsistentlySynchronizedField",
|
|
|
|
|
Justification = "ILogger doesn't need to be synchronized"
|
|
|
|
|
)]
|
2024-08-13 13:08:50 +02:00
|
|
|
public class WebhookExecutorService(
|
|
|
|
|
Config config,
|
|
|
|
|
ILogger logger,
|
|
|
|
|
IWebhookCache webhookCache,
|
2024-08-19 16:12:28 +02:00
|
|
|
ChannelCache channelCache,
|
2024-10-09 17:35:11 +02:00
|
|
|
IDiscordRestWebhookAPI webhookApi
|
|
|
|
|
)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
|
|
|
|
private readonly ILogger _logger = logger.ForContext<WebhookExecutorService>();
|
|
|
|
|
private readonly Snowflake _applicationId = DiscordSnowflake.New(config.Discord.ApplicationId);
|
|
|
|
|
private readonly ConcurrentDictionary<ulong, ConcurrentQueue<IEmbed>> _cache = new();
|
2024-08-13 16:48:54 +02:00
|
|
|
private readonly ConcurrentDictionary<ulong, object> _locks = new();
|
2024-08-13 13:08:50 +02:00
|
|
|
private readonly ConcurrentDictionary<ulong, Timer> _timers = new();
|
|
|
|
|
private IUser? _selfUser;
|
|
|
|
|
|
2024-10-13 14:58:44 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the current user for this webhook executor service. This must be called as soon as possible,
|
|
|
|
|
/// before any logs are sent, such as in a READY event.
|
|
|
|
|
/// </summary>
|
2024-08-13 13:08:50 +02:00
|
|
|
public void SetSelfUser(IUser user) => _selfUser = user;
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <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)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
2024-11-18 00:47:27 +01:00
|
|
|
var logChannel = GetLogChannel(
|
|
|
|
|
guildConfig,
|
|
|
|
|
logChannelType,
|
|
|
|
|
channelId: null,
|
|
|
|
|
userId: null,
|
|
|
|
|
roleId: null,
|
|
|
|
|
roleIds: null
|
|
|
|
|
);
|
2024-10-09 17:35:11 +02:00
|
|
|
if (logChannel == null)
|
|
|
|
|
return;
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
QueueLog(logChannel.Value, embed);
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Queues a log embed for the given channel ID.
|
|
|
|
|
/// </summary>
|
2024-11-18 00:47:27 +01:00
|
|
|
public void QueueLog(ulong? channelId, IEmbed embed)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
2024-11-18 00:47:27 +01:00
|
|
|
if (channelId is null or 0)
|
2024-10-09 17:35:11 +02:00
|
|
|
return;
|
2024-09-02 15:59:16 +02:00
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
var queue = _cache.GetOrAdd(channelId.Value, []);
|
2024-09-02 15:59:16 +02:00
|
|
|
queue.Enqueue(embed);
|
2024-11-18 00:47:27 +01:00
|
|
|
_cache[channelId.Value] = queue;
|
2024-08-13 16:48:54 +02:00
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
SetTimer(channelId.Value, queue);
|
2024-08-13 16:48:54 +02:00
|
|
|
}
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <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>
|
2024-10-13 14:58:44 +02:00
|
|
|
/// <param name="embeds">The embeds to send. Must be under 6000 characters in length total.</param>
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <param name="files">The files to send.</param>
|
2024-10-09 17:35:11 +02:00
|
|
|
public async Task SendLogAsync(
|
|
|
|
|
ulong channelId,
|
|
|
|
|
List<IEmbed> embeds,
|
|
|
|
|
IEnumerable<FileData> files
|
|
|
|
|
)
|
2024-08-13 16:48:54 +02:00
|
|
|
{
|
2024-10-09 17:35:11 +02:00
|
|
|
if (channelId == 0)
|
|
|
|
|
return;
|
2024-08-21 17:31:39 +02:00
|
|
|
|
2024-10-25 16:18:27 +02:00
|
|
|
if (config.Discord.TestMode)
|
|
|
|
|
{
|
|
|
|
|
_logger.Information(
|
|
|
|
|
"Should have logged to {ChannelId}, but test mode is enabled, ignoring",
|
|
|
|
|
channelId
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
var attachments = files
|
2024-10-28 14:04:55 +01:00
|
|
|
.Select<FileData, OneOf<FileData, IPartialAttachment>>(f => f)
|
2024-09-02 15:59:16 +02:00
|
|
|
.ToList();
|
|
|
|
|
|
2024-10-12 23:47:18 +02:00
|
|
|
if (embeds.Count == 0 && attachments.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_logger.Error(
|
|
|
|
|
"SendLogAsync was called with zero embeds and zero attachments, bailing to prevent a bad request error"
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-13 14:58:44 +02:00
|
|
|
if (embeds.Select(e => e.TextLength()).Sum() > MaxContentLength)
|
|
|
|
|
{
|
|
|
|
|
_logger.Error(
|
|
|
|
|
"SendLogAsync was called with embeds totaling more than 6000 characters, bailing to prevent a bad request error"
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-09 17:35:11 +02:00
|
|
|
_logger.Debug(
|
|
|
|
|
"Sending {EmbedCount} embeds/{FileCount} files to channel {ChannelId}",
|
|
|
|
|
embeds.Count,
|
|
|
|
|
attachments.Count,
|
|
|
|
|
channelId
|
|
|
|
|
);
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-10-09 17:35:11 +02:00
|
|
|
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()
|
|
|
|
|
);
|
2024-08-13 16:48:54 +02:00
|
|
|
}
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <summary>
|
2024-10-09 17:35:11 +02:00
|
|
|
/// Sets a 3 second timer for the given channel.
|
2024-09-02 15:59:16 +02:00
|
|
|
/// </summary>
|
|
|
|
|
private void SetTimer(ulong channelId, ConcurrentQueue<IEmbed> queue)
|
2024-08-13 16:48:54 +02:00
|
|
|
{
|
2024-10-09 17:35:11 +02:00
|
|
|
if (_timers.TryGetValue(channelId, out var existingTimer))
|
|
|
|
|
existingTimer.Dispose();
|
|
|
|
|
_timers[channelId] = new Timer(
|
|
|
|
|
_ =>
|
2024-08-13 16:48:54 +02:00
|
|
|
{
|
2024-10-12 23:47:18 +02:00
|
|
|
var __ = SendLogAsync(channelId, TakeFromQueue(channelId), []);
|
2024-10-09 17:35:11 +02:00
|
|
|
if (!queue.IsEmpty)
|
|
|
|
|
{
|
|
|
|
|
if (_timers.TryGetValue(channelId, out var timer))
|
|
|
|
|
timer.Dispose();
|
|
|
|
|
SetTimer(channelId, queue);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
null,
|
|
|
|
|
3000,
|
|
|
|
|
Timeout.Infinite
|
|
|
|
|
);
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-12 23:47:18 +02:00
|
|
|
private const int MaxContentLength = 6000;
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
/// <summary>
|
2024-10-12 23:47:18 +02:00
|
|
|
/// Takes as many embeds as possible from the queue for the given channel.
|
|
|
|
|
/// Up to ten embeds are returned, or less if their combined length is longer than 6000 characters.
|
2024-09-02 15:59:16 +02:00
|
|
|
/// Note that this locks the queue to prevent duplicate embeds from being sent.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private List<IEmbed> TakeFromQueue(ulong channelId)
|
2024-08-13 16:48:54 +02:00
|
|
|
{
|
2024-09-02 15:59:16 +02:00
|
|
|
var queue = _cache.GetOrAdd(channelId, []);
|
|
|
|
|
var channelLock = _locks.GetOrAdd(channelId, channelId);
|
|
|
|
|
lock (channelLock)
|
|
|
|
|
{
|
2024-10-12 23:47:18 +02:00
|
|
|
var totalContentLength = 0;
|
2024-09-02 15:59:16 +02:00
|
|
|
var embeds = new List<IEmbed>();
|
2024-10-12 23:47:18 +02:00
|
|
|
while (embeds.Count < 10 && totalContentLength < MaxContentLength)
|
2024-09-02 15:59:16 +02:00
|
|
|
{
|
2024-10-12 23:47:18 +02:00
|
|
|
if (!queue.TryPeek(out var embed))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
var length = embed.TextLength();
|
|
|
|
|
if (length > MaxContentLength)
|
|
|
|
|
{
|
|
|
|
|
_logger.Warning(
|
|
|
|
|
"Queued embed for {ChannelId} exceeds maximum length, discarding it",
|
|
|
|
|
channelId
|
|
|
|
|
);
|
|
|
|
|
queue.TryDequeue(out _);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalContentLength + length > MaxContentLength)
|
2024-10-09 17:35:11 +02:00
|
|
|
break;
|
2024-10-12 23:47:18 +02:00
|
|
|
|
|
|
|
|
totalContentLength += length;
|
|
|
|
|
|
|
|
|
|
queue.TryDequeue(out _);
|
2024-09-02 15:59:16 +02:00
|
|
|
embeds.Add(embed);
|
|
|
|
|
}
|
2024-08-13 16:48:54 +02:00
|
|
|
|
2024-10-12 23:47:18 +02:00
|
|
|
if (embeds.Count == 0)
|
|
|
|
|
return embeds;
|
|
|
|
|
|
|
|
|
|
_logger.Debug(
|
|
|
|
|
"Took {EmbedCount} embeds from queue for {ChannelId}, total length is {TotalLength}",
|
|
|
|
|
embeds.Count,
|
|
|
|
|
channelId,
|
|
|
|
|
totalContentLength
|
|
|
|
|
);
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
return embeds;
|
|
|
|
|
}
|
2024-08-13 16:48:54 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-02 15:59:16 +02:00
|
|
|
// TODO: make it so this method can only have one request per channel in flight simultaneously
|
2024-10-09 17:35:11 +02:00
|
|
|
private async Task<IWebhook> FetchWebhookAsync(
|
|
|
|
|
Snowflake channelId,
|
|
|
|
|
CancellationToken ct = default
|
|
|
|
|
)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
2024-10-09 17:35:11 +02:00
|
|
|
var channelWebhooks = await webhookApi.GetChannelWebhooksAsync(channelId, ct).GetOrThrow();
|
|
|
|
|
var webhook = channelWebhooks.FirstOrDefault(w =>
|
|
|
|
|
w.ApplicationID == _applicationId && w.Token.IsDefined()
|
|
|
|
|
);
|
|
|
|
|
if (webhook != null)
|
|
|
|
|
return webhook;
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-10-09 17:35:11 +02:00
|
|
|
return await webhookApi
|
|
|
|
|
.CreateWebhookAsync(
|
|
|
|
|
channelId,
|
|
|
|
|
"Catalogger",
|
|
|
|
|
default,
|
|
|
|
|
reason: "Creating logging webhook",
|
|
|
|
|
ct: ct
|
|
|
|
|
)
|
|
|
|
|
.GetOrThrow();
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-09 17:35:11 +02:00
|
|
|
public ulong? GetLogChannel(
|
2024-11-18 00:47:27 +01:00
|
|
|
Guild guild,
|
|
|
|
|
LogChannelType logChannelType,
|
|
|
|
|
Snowflake? channelId = null,
|
|
|
|
|
ulong? userId = null,
|
|
|
|
|
Snowflake? roleId = null,
|
|
|
|
|
IReadOnlyList<Snowflake>? roleIds = null
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
var isMessageLog =
|
|
|
|
|
logChannelType
|
|
|
|
|
is LogChannelType.MessageUpdate
|
|
|
|
|
or LogChannelType.MessageDelete
|
|
|
|
|
or LogChannelType.MessageDeleteBulk;
|
|
|
|
|
|
|
|
|
|
// Check if we're getting the channel for a channel log
|
|
|
|
|
var isChannelLog =
|
|
|
|
|
channelId != null
|
|
|
|
|
&& logChannelType
|
|
|
|
|
is LogChannelType.ChannelCreate
|
|
|
|
|
or LogChannelType.ChannelDelete
|
|
|
|
|
or LogChannelType.ChannelUpdate;
|
|
|
|
|
|
|
|
|
|
// Check if we're getting the channel for a role log
|
|
|
|
|
var isRoleLog =
|
|
|
|
|
roleId != null
|
|
|
|
|
&& logChannelType
|
|
|
|
|
is LogChannelType.GuildRoleCreate
|
|
|
|
|
or LogChannelType.GuildRoleUpdate
|
|
|
|
|
or LogChannelType.GuildRoleDelete;
|
|
|
|
|
|
|
|
|
|
// Check if we're getting the channel for a member update log
|
|
|
|
|
var isMemberRoleUpdateLog =
|
|
|
|
|
roleIds != null && logChannelType is LogChannelType.GuildMemberUpdate;
|
|
|
|
|
|
|
|
|
|
if (isMessageLog)
|
|
|
|
|
return GetMessageLogChannel(guild, logChannelType, channelId, userId);
|
|
|
|
|
|
2024-11-18 21:26:47 +01:00
|
|
|
if (isChannelLog)
|
|
|
|
|
return GetChannelLogChannel(guild, logChannelType, channelId!.Value);
|
2024-11-18 00:47:27 +01:00
|
|
|
|
|
|
|
|
if (isRoleLog && guild.IgnoredRoles.Contains(roleId!.Value.Value))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// Member update logs are only ignored if *all* updated roles are ignored
|
|
|
|
|
if (isMemberRoleUpdateLog && roleIds!.All(r => guild.IgnoredRoles.Contains(r.Value)))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// If nothing is ignored, return the correct log channel!
|
|
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 21:26:47 +01:00
|
|
|
private ulong? GetChannelLogChannel(
|
|
|
|
|
Guild guild,
|
|
|
|
|
LogChannelType logChannelType,
|
|
|
|
|
Snowflake channelId
|
|
|
|
|
)
|
|
|
|
|
{
|
2024-11-27 16:17:11 +01:00
|
|
|
_logger.Verbose(
|
|
|
|
|
"Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}",
|
|
|
|
|
logChannelType,
|
|
|
|
|
guild.Id,
|
|
|
|
|
channelId
|
|
|
|
|
);
|
|
|
|
|
|
2024-11-18 21:26:47 +01:00
|
|
|
if (!channelCache.TryGet(channelId, out var channel))
|
|
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
|
|
|
|
|
|
|
|
|
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.TryGet(channelId, out var parentChannel))
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Parent channel for thread {ChannelId} is not in cache, returning the default log channel",
|
|
|
|
|
channelId
|
|
|
|
|
);
|
2024-11-18 21:26:47 +01:00
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-11-18 21:26:47 +01:00
|
|
|
categoryId = parentChannel.ParentID.Value;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
channelId = channel.ID;
|
|
|
|
|
categoryId = channel.ParentID.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the channel or its category is ignored
|
|
|
|
|
if (
|
|
|
|
|
guild.IgnoredChannels.Contains(channelId.Value)
|
|
|
|
|
|| (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value))
|
|
|
|
|
)
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Channel {ChannelId} or its parent {CategoryId} is ignored",
|
|
|
|
|
channelId,
|
|
|
|
|
categoryId
|
|
|
|
|
);
|
2024-11-18 21:26:47 +01:00
|
|
|
return null;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-11-18 21:26:47 +01:00
|
|
|
|
2024-11-27 16:17:11 +01:00
|
|
|
_logger.Verbose("Returning default log channel for {EventType}", logChannelType);
|
2024-11-18 21:26:47 +01:00
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
private ulong? GetMessageLogChannel(
|
2024-10-09 17:35:11 +02:00
|
|
|
Guild guild,
|
|
|
|
|
LogChannelType logChannelType,
|
|
|
|
|
Snowflake? channelId = null,
|
|
|
|
|
ulong? userId = null
|
|
|
|
|
)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
2024-11-27 16:17:11 +01:00
|
|
|
_logger.Verbose(
|
|
|
|
|
"Getting log channel for event {Event}. Channel ID: {ChannelId}, user ID: {UserId}",
|
|
|
|
|
logChannelType,
|
|
|
|
|
channelId,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
// Check if the user is ignored globally
|
|
|
|
|
if (userId != null && guild.Messages.IgnoredUsers.Contains(userId.Value))
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose("User {UserId} is ignored globally", userId);
|
2024-11-18 00:47:27 +01:00
|
|
|
return null;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-11-18 00:47:27 +01:00
|
|
|
|
|
|
|
|
// If the user isn't ignored and we didn't get a channel ID, return the default log channel
|
2024-10-09 17:35:11 +02:00
|
|
|
if (channelId == null)
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"No channel ID given so returning default channel for {Event}",
|
|
|
|
|
logChannelType
|
|
|
|
|
);
|
2024-10-09 17:35:11 +02:00
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-11-18 00:47:27 +01:00
|
|
|
|
2024-10-09 17:35:11 +02:00
|
|
|
if (!channelCache.TryGet(channelId.Value, out var channel))
|
|
|
|
|
return null;
|
2024-08-13 13:08:50 +02:00
|
|
|
|
|
|
|
|
Snowflake? categoryId;
|
2024-10-09 17:35:11 +02:00
|
|
|
if (
|
|
|
|
|
channel.Type
|
|
|
|
|
is ChannelType.AnnouncementThread
|
|
|
|
|
or ChannelType.PrivateThread
|
|
|
|
|
or ChannelType.PublicThread
|
|
|
|
|
)
|
2024-08-13 13:08:50 +02:00
|
|
|
{
|
|
|
|
|
// parent_id should always have a value for threads
|
|
|
|
|
channelId = channel.ParentID.Value!.Value;
|
2024-08-13 16:48:54 +02:00
|
|
|
if (!channelCache.TryGet(channelId.Value, out var parentChannel))
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Parent channel for thread {ChannelId} is not in cache, returning the default log channel",
|
|
|
|
|
channelId
|
|
|
|
|
);
|
2024-08-13 13:08:50 +02:00
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-08-13 13:08:50 +02:00
|
|
|
categoryId = parentChannel.ParentID.Value;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
channelId = channel.ID;
|
|
|
|
|
categoryId = channel.ParentID.Value;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
// Check if the channel or its category is ignored
|
2024-10-09 17:35:11 +02:00
|
|
|
if (
|
2024-11-18 00:47:27 +01:00
|
|
|
guild.Messages.IgnoredChannels.Contains(channelId.Value.Value)
|
|
|
|
|
|| categoryId != null && guild.Messages.IgnoredChannels.Contains(categoryId.Value.Value)
|
2024-10-09 17:35:11 +02:00
|
|
|
)
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Channel {ChannelId} or its parent {CategoryId} is ignored",
|
|
|
|
|
channelId,
|
|
|
|
|
categoryId
|
|
|
|
|
);
|
2024-10-09 17:35:11 +02:00
|
|
|
return null;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-11-18 00:47:27 +01:00
|
|
|
|
2024-08-13 13:08:50 +02:00
|
|
|
if (userId != null)
|
|
|
|
|
{
|
|
|
|
|
// Check the channel-local and category-local ignored users
|
|
|
|
|
var channelIgnoredUsers =
|
2024-11-18 00:47:27 +01:00
|
|
|
guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(channelId.Value.Value)
|
2024-10-09 17:35:11 +02:00
|
|
|
?? [];
|
|
|
|
|
var categoryIgnoredUsers =
|
|
|
|
|
(
|
|
|
|
|
categoryId != null
|
2024-11-18 00:47:27 +01:00
|
|
|
? guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(
|
2024-10-09 17:35:11 +02:00
|
|
|
categoryId.Value.Value
|
|
|
|
|
)
|
|
|
|
|
: []
|
|
|
|
|
) ?? [];
|
|
|
|
|
if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value))
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"User {UserId} is ignored in {ChannelId} or its category {CategoryId}",
|
|
|
|
|
userId,
|
|
|
|
|
channelId,
|
|
|
|
|
categoryId
|
|
|
|
|
);
|
2024-10-09 17:35:11 +02:00
|
|
|
return null;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// These three events can be redirected to other channels. Redirects can be on a channel or category level.
|
2024-11-18 00:47:27 +01:00
|
|
|
// The events are only redirected if they're supposed to be logged in the first place.
|
|
|
|
|
if (GetDefaultLogChannel(guild, logChannelType) == 0)
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"No default log channel for event {EventType}, ignoring event",
|
|
|
|
|
logChannelType
|
|
|
|
|
);
|
2024-11-18 00:47:27 +01:00
|
|
|
return null;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
var categoryRedirect =
|
|
|
|
|
categoryId != null
|
|
|
|
|
? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value)
|
|
|
|
|
: 0;
|
2024-08-13 13:08:50 +02:00
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect))
|
2024-11-27 16:17:11 +01:00
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Messages from channel {ChannelId} should be redirected to {RedirectId}",
|
|
|
|
|
channelId,
|
|
|
|
|
channelRedirect
|
|
|
|
|
);
|
2024-11-18 00:47:27 +01:00
|
|
|
return channelRedirect;
|
2024-11-27 16:17:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (categoryRedirect != 0)
|
|
|
|
|
{
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"Messages from categoryId {CategoryId} should be redirected to {RedirectId}",
|
|
|
|
|
categoryId,
|
|
|
|
|
categoryRedirect
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_logger.Verbose(
|
|
|
|
|
"No redirects or ignores for event {EventType}, returning default log channel",
|
|
|
|
|
logChannelType
|
|
|
|
|
);
|
|
|
|
|
return GetDefaultLogChannel(guild, logChannelType);
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-18 00:47:27 +01:00
|
|
|
public static ulong GetDefaultLogChannel(Guild guild, LogChannelType logChannelType) =>
|
|
|
|
|
logChannelType switch
|
2024-10-09 17:35:11 +02:00
|
|
|
{
|
|
|
|
|
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,
|
2024-10-11 20:38:53 +02:00
|
|
|
LogChannelType.GuildMemberTimeout => guild.Channels.GuildMemberTimeout,
|
2024-10-09 17:35:11 +02:00
|
|
|
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,
|
2024-11-18 00:47:27 +01:00
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(logChannelType)),
|
2024-10-09 17:35:11 +02:00
|
|
|
};
|
2024-08-13 13:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public enum LogChannelType
|
|
|
|
|
{
|
|
|
|
|
GuildUpdate,
|
|
|
|
|
GuildEmojisUpdate,
|
|
|
|
|
GuildRoleCreate,
|
|
|
|
|
GuildRoleUpdate,
|
|
|
|
|
GuildRoleDelete,
|
|
|
|
|
ChannelCreate,
|
|
|
|
|
ChannelUpdate,
|
|
|
|
|
ChannelDelete,
|
|
|
|
|
GuildMemberAdd,
|
|
|
|
|
GuildMemberUpdate,
|
|
|
|
|
GuildKeyRoleUpdate,
|
|
|
|
|
GuildMemberNickUpdate,
|
|
|
|
|
GuildMemberAvatarUpdate,
|
2024-10-11 20:38:53 +02:00
|
|
|
GuildMemberTimeout,
|
2024-08-13 13:08:50 +02:00
|
|
|
GuildMemberRemove,
|
|
|
|
|
GuildMemberKick,
|
|
|
|
|
GuildBanAdd,
|
|
|
|
|
GuildBanRemove,
|
|
|
|
|
InviteCreate,
|
|
|
|
|
InviteDelete,
|
|
|
|
|
MessageUpdate,
|
|
|
|
|
MessageDelete,
|
2024-10-09 17:35:11 +02:00
|
|
|
MessageDeleteBulk,
|
|
|
|
|
}
|