diff --git a/Catalogger.Backend/Services/WebhookExecutorService.cs b/Catalogger.Backend/Services/WebhookExecutorService.cs index 6b73cb4..8ca8cb3 100644 --- a/Catalogger.Backend/Services/WebhookExecutorService.cs +++ b/Catalogger.Backend/Services/WebhookExecutorService.cs @@ -43,7 +43,7 @@ public class WebhookExecutorService( private readonly ILogger _logger = logger.ForContext(); private readonly Snowflake _applicationId = DiscordSnowflake.New(config.Discord.ApplicationId); private readonly ConcurrentDictionary> _cache = new(); - private readonly ConcurrentDictionary _locks = new(); + private readonly ConcurrentDictionary _locks = new(); private readonly ConcurrentDictionary _timers = new(); private IUser? _selfUser; @@ -189,7 +189,7 @@ public class WebhookExecutorService( private List TakeFromQueue(ulong channelId) { var queue = _cache.GetOrAdd(channelId, []); - var channelLock = _locks.GetOrAdd(channelId, channelId); + var channelLock = _locks.GetOrAdd(channelId, new Lock()); lock (channelLock) { var totalContentLength = 0; @@ -293,10 +293,10 @@ public class WebhookExecutorService( roleIds != null && logChannelType is LogChannelType.GuildMemberUpdate; if (isMessageLog) - return GetMessageLogChannel(guild, logChannelType, channelId, userId); + return GetLogChannelForMessageEvent(guild, logChannelType, channelId, userId); if (isChannelLog) - return GetChannelLogChannel(guild, logChannelType, channelId!.Value); + return GetLogChannelForChannelEvent(guild, logChannelType, channelId!.Value); if (isRoleLog && guild.IgnoredRoles.Contains(roleId!.Value.Value)) return null; @@ -305,77 +305,11 @@ public class WebhookExecutorService( if (isMemberRoleUpdateLog && roleIds!.All(r => guild.IgnoredRoles.Contains(r.Value))) return null; - // If nothing is ignored, return the correct log channel! + // If nothing is ignored, and this isn't a message or channel event, return the default log channel. return GetDefaultLogChannel(guild, logChannelType); } - private ulong? GetChannelLogChannel( - Guild guild, - LogChannelType logChannelType, - Snowflake channelId - ) - { - _logger.Verbose( - "Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}", - logChannelType, - guild.Id, - channelId - ); - - if (!channelCache.TryGet(channelId, out var channel)) - { - _logger.Verbose( - "Channel with ID {ChannelId} is not cached, returning default log channel", - channelId - ); - 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)) - { - _logger.Verbose( - "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", - channelId - ); - return GetDefaultLogChannel(guild, logChannelType); - } - 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)) - ) - { - _logger.Verbose( - "Channel {ChannelId} or its parent {CategoryId} is ignored", - channelId, - categoryId - ); - return null; - } - - _logger.Verbose("Returning default log channel for {EventType}", logChannelType); - return GetDefaultLogChannel(guild, logChannelType); - } - - private ulong? GetMessageLogChannel( + private ulong? GetLogChannelForMessageEvent( Guild guild, LogChannelType logChannelType, Snowflake? channelId = null, @@ -415,41 +349,24 @@ public class WebhookExecutorService( return GetDefaultLogChannel(guild, logChannelType); } - Snowflake? categoryId; - if ( - channel.Type - is ChannelType.AnnouncementThread - or ChannelType.PrivateThread - or ChannelType.PublicThread - ) + if (!GetChannelAndParentId(channel, out var actualChannelId, out var categoryId)) { - // parent_id should always have a value for threads - channelId = channel.ParentID.Value!.Value; - if (!channelCache.TryGet(channelId.Value, out var parentChannel)) - { - _logger.Verbose( - "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", - channelId - ); - return GetDefaultLogChannel(guild, logChannelType); - } - categoryId = parentChannel.ParentID.Value; - } - else - { - channelId = channel.ID; - categoryId = channel.ParentID.Value; + _logger.Verbose( + "Could not get root channel and category ID for channel {ChannelId}, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); } // Check if the channel or its category is ignored if ( - guild.Messages.IgnoredChannels.Contains(channelId.Value.Value) + guild.Messages.IgnoredChannels.Contains(actualChannelId.Value) || categoryId != null && guild.Messages.IgnoredChannels.Contains(categoryId.Value.Value) ) { _logger.Verbose( "Channel {ChannelId} or its parent {CategoryId} is ignored", - channelId, + actualChannelId, categoryId ); return null; @@ -459,8 +376,10 @@ public class WebhookExecutorService( { // Check the channel-local and category-local ignored users var channelIgnoredUsers = - guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(channelId.Value.Value) + guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(actualChannelId.Value) ?? []; + + // Obviously, we can only check for category-level ignored users if we actually got a category ID. var categoryIgnoredUsers = ( categoryId != null @@ -469,6 +388,8 @@ public class WebhookExecutorService( ) : [] ) ?? []; + + // Combine the ignored users in the channel and category, then check if the user is in there. if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value)) { _logger.Verbose( @@ -482,7 +403,7 @@ public class WebhookExecutorService( } // These three events can be redirected to other channels. Redirects can be on a channel or category level. - // The events are only redirected if they're supposed to be logged in the first place. + // The events are only redirected if they're supposed to be logged in the first place (i.e. GetDefaultLogChannel doesn't return 0) if (GetDefaultLogChannel(guild, logChannelType) == 0) { _logger.Verbose( @@ -492,21 +413,21 @@ public class WebhookExecutorService( return null; } - var categoryRedirect = - categoryId != null - ? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value) - : 0; - - if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect)) + if (guild.Channels.Redirects.TryGetValue(actualChannelId.Value, out var channelRedirect)) { _logger.Verbose( "Messages from channel {ChannelId} should be redirected to {RedirectId}", - channelId, + actualChannelId, channelRedirect ); return channelRedirect; } + var categoryRedirect = + categoryId != null + ? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value) + : 0; + if (categoryRedirect != 0) { _logger.Verbose( @@ -514,6 +435,7 @@ public class WebhookExecutorService( categoryId, categoryRedirect ); + return categoryRedirect; } _logger.Verbose( @@ -523,6 +445,92 @@ public class WebhookExecutorService( return GetDefaultLogChannel(guild, logChannelType); } + private ulong? GetLogChannelForChannelEvent( + Guild guild, + LogChannelType logChannelType, + Snowflake channelId + ) + { + _logger.Verbose( + "Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}", + logChannelType, + guild.Id, + channelId + ); + + if (!channelCache.TryGet(channelId, out var channel)) + { + _logger.Verbose( + "Channel with ID {ChannelId} is not cached, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); + } + + if (!GetChannelAndParentId(channel, out channelId, out var categoryId)) + { + _logger.Verbose( + "Could not get root channel and category ID for channel {ChannelId}, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); + } + + // Check if the channel or its category is ignored + if ( + guild.IgnoredChannels.Contains(channelId.Value) + || (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value)) + ) + { + _logger.Verbose( + "Channel {ChannelId} or its parent {CategoryId} is ignored", + channelId, + categoryId + ); + return null; + } + + _logger.Verbose("Returning default log channel for {EventType}", logChannelType); + return GetDefaultLogChannel(guild, logChannelType); + } + + private bool GetChannelAndParentId( + IChannel channel, + out Snowflake channelId, + out 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)) + { + _logger.Verbose( + "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", + channelId + ); + + channelId = Snowflake.CreateTimestampSnowflake(); + categoryId = null; + return false; + } + categoryId = parentChannel.ParentID.Value; + } + else + { + channelId = channel.ID; + categoryId = channel.ParentID.Value; + } + + return true; + } + public static ulong GetDefaultLogChannel(Guild guild, LogChannelType logChannelType) => logChannelType switch {