using Catalogger.Backend.Cache; using Catalogger.Backend.Database; using Catalogger.Backend.Database.Queries; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Humanizer; using NodaTime; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Results; namespace Catalogger.Backend.Bot.Responders; public class MessageDeleteResponder( ILogger logger, DatabaseContext db, MessageRepository messageRepository, WebhookExecutorService webhookExecutor, ChannelCacheService channelCache, UserCacheService userCache, IClock clock) : IResponder { private readonly ILogger _logger = logger.ForContext(); public async Task RespondAsync(IMessageDelete ev, CancellationToken ct = default) { if (!ev.GuildID.IsDefined()) return Result.Success; if (ev.ID.Timestamp < DateTimeOffset.Now - 1.Minutes()) { _logger.Debug( "Deleted message {MessageId} is less than 1 minute old, delaying 5 seconds to give PK time to catch up", ev.ID); await Task.Delay(5.Seconds(), ct); } if (await messageRepository.IsMessageIgnoredAsync(ev.ID.Value, ct)) return Result.Success; var guild = await db.GetGuildAsync(ev.GuildID, ct); if (guild.IsMessageIgnored(ev.ChannelID, ev.ID)) return Result.Success; var logChannel = webhookExecutor.GetLogChannel(guild, LogChannelType.MessageDelete, ev.ChannelID); var msg = await messageRepository.GetMessageAsync(ev.ID.Value, ct); // Sometimes a message that *should* be logged isn't stored in the database, notify the user of that if (msg == null) { if (logChannel == null) return Result.Success; await webhookExecutor.QueueLogAsync(logChannel.Value, new Embed( Title: "Message deleted", Description: $"A message not found in the database was deleted in <#{ev.ChannelID}> ({ev.ChannelID}).", Footer: new EmbedFooter(Text: $"ID: {ev.ID}"), Timestamp: clock.GetCurrentInstant().ToDateTimeOffset() )); return Result.Success; } logChannel = webhookExecutor.GetLogChannel(guild, LogChannelType.MessageDelete, ev.ChannelID, msg.UserId); if (logChannel == null) return Result.Success; var user = await userCache.GetUserAsync(DiscordSnowflake.New(msg.UserId)); var builder = new EmbedBuilder() .WithTitle("Message deleted") .WithDescription(msg.Content) .WithColour(DiscordUtils.Red) .WithFooter($"ID: {msg.Id}") .WithTimestamp(ev.ID); if (user != null) builder.WithAuthor(user.Tag(), url: null, iconUrl: user.AvatarUrl()); if (msg.Member != null) builder.WithTitle($"Message by {msg.Username} deleted"); string channelMention; if (!channelCache.GetChannel(ev.ChannelID, out var channel)) channelMention = $"<#{msg.ChannelId}>"; else if (channel.Type is ChannelType.AnnouncementThread or ChannelType.PrivateThread or ChannelType.PublicThread) channelMention = $"<#{channel.ParentID.Value}>\nID: {channel.ParentID.Value}\n\nThread: {channel.Name} (<#{channel.ID}>)"; else channelMention = $"<#{channel.ID}>\nID: {channel.ID}"; var userMention = user != null ? $"<@{user.ID}>\n{user.Tag()}\nID: {user.ID}" : $"<@{msg.UserId}>\nID: {msg.UserId}"; builder.AddField("Channel", channelMention, true); builder.AddField(msg.System != null ? "Linked Discord account" : "Sender", userMention, true); if (msg is { System: not null, Member: not null }) { builder.AddField("\u200b", "**PluralKit information**", false); builder.AddField("System ID", msg.System, true); builder.AddField("Member ID", msg.Member, true); } await webhookExecutor.QueueLogAsync(logChannel.Value, builder.Build().GetOrThrow()); return Result.Success; } }