using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Database; using Catalogger.Backend.Database.Queries; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Humanizer; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Results; namespace Catalogger.Backend.Bot.Responders.Roles; public class RoleUpdateResponder( ILogger logger, DatabaseContext db, RoleCache roleCache, WebhookExecutorService webhookExecutor ) : IResponder { private readonly ILogger _logger = logger.ForContext(); public async Task RespondAsync(IGuildRoleUpdate evt, CancellationToken ct = default) { try { var newRole = evt.Role; if (!roleCache.TryGet(evt.Role.ID, out var oldRole)) { _logger.Information( "Received role update event for {RoleId} but it wasn't cached, ignoring", evt.Role.ID ); return Result.Success; } var embed = new EmbedBuilder() .WithTitle($"Role \"{evt.Role.Name}\" updated") .WithColour(DiscordUtils.Blue) .WithFooter($"ID: {evt.Role.ID}") .WithCurrentTimestamp(); if (newRole.Name != oldRole.Name) { embed.AddField("Name", $"**Before:** {oldRole.Name}\n**After:** {newRole.Name}"); } if ( newRole.IsHoisted != oldRole.IsHoisted || newRole.IsMentionable != oldRole.IsMentionable ) { embed.AddField( "\u200b", $"**Mentionable:** {newRole.IsMentionable}\n**Shown separately:** {newRole.IsHoisted}" ); } if (newRole.Colour != oldRole.Colour) { embed.AddField( "Colour", $"**Before:** {oldRole.Colour.ToPrettyString()}\n**After:** {newRole.Colour.ToPrettyString()}" ); } if (newRole.Permissions.Value != oldRole.Permissions.Value) { var diff = string.Join( "\n", PermissionUpdate(oldRole.Permissions, newRole.Permissions) ); embed.AddField("Permissions", $"```diff\n{diff}\n```"); } // All updates are shown in fields. If there are no fields, there were no updates we care about // (we don't care about position, for example, because it's not actually useful) if (embed.Fields.Count == 0) return Result.Success; var guildConfig = await db.GetGuildAsync(evt.GuildID, ct); webhookExecutor.QueueLog( guildConfig, LogChannelType.GuildRoleUpdate, embed.Build().GetOrThrow() ); } finally { roleCache.Set(evt.Role, evt.GuildID); } return Result.Success; } private static IEnumerable PermissionUpdate( IDiscordPermissionSet oldValue, IDiscordPermissionSet newValue ) { foreach (var perm in Enum.GetValues()) { if (!oldValue.HasPermission(perm) && newValue.HasPermission(perm)) { yield return $"+ {perm.Humanize(LetterCasing.Title)}"; } else if (oldValue.HasPermission(perm) && !newValue.HasPermission(perm)) { yield return $"- {perm.Humanize(LetterCasing.Title)}"; } } } }