From 539fc82342ae283517019e8040467aa67be0ffea Mon Sep 17 00:00:00 2001 From: alyssa Date: Sun, 8 Jun 2025 19:52:37 +0000 Subject: [PATCH] feat(bot): lookup command messages --- PluralKit.Bot/ApplicationCommands/Message.cs | 19 +++++++- .../CommandSystem/Context/Context.cs | 17 ++++--- PluralKit.Bot/Commands/Message.cs | 44 ++++++++++++------- .../Services/CommandMessageService.cs | 20 +++------ PluralKit.Bot/Services/EmbedService.cs | 18 ++++++++ .../Repository/ModelRepository.Message.cs | 39 ++++++++++++++++ 6 files changed, 120 insertions(+), 37 deletions(-) diff --git a/PluralKit.Bot/ApplicationCommands/Message.cs b/PluralKit.Bot/ApplicationCommands/Message.cs index a425b31c..2f7be692 100644 --- a/PluralKit.Bot/ApplicationCommands/Message.cs +++ b/PluralKit.Bot/ApplicationCommands/Message.cs @@ -33,7 +33,10 @@ public class ApplicationCommandProxiedMessage var messageId = ctx.Event.Data!.TargetId!.Value; var msg = await ctx.Repository.GetFullMessage(messageId); if (msg == null) - throw Errors.MessageNotFound(messageId); + { + await QueryCommandMessage(ctx); + return; + } var showContent = true; var channel = await _rest.GetChannelOrNull(msg.Message.Channel); @@ -58,6 +61,20 @@ public class ApplicationCommandProxiedMessage await ctx.Reply(embeds: embeds.ToArray()); } + private async Task QueryCommandMessage(InteractionContext ctx) + { + var messageId = ctx.Event.Data!.TargetId!.Value; + var msg = await ctx.Repository.GetCommandMessage(messageId); + if (msg == null) + throw Errors.MessageNotFound(messageId); + + var embeds = new List(); + + embeds.Add(await _embeds.CreateCommandMessageInfoEmbed(msg, true)); + + await ctx.Reply(embeds: embeds.ToArray()); + } + public async Task DeleteMessage(InteractionContext ctx) { var messageId = ctx.Event.Data!.TargetId!.Value; diff --git a/PluralKit.Bot/CommandSystem/Context/Context.cs b/PluralKit.Bot/CommandSystem/Context/Context.cs index b18dd9a8..2dd2dce9 100644 --- a/PluralKit.Bot/CommandSystem/Context/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context/Context.cs @@ -101,12 +101,17 @@ public class Context AllowedMentions = mentions ?? new AllowedMentions() }); - // if (embed != null) - // { - // Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example) - // but since we can, we just store all sent messages for possible deletion - await _commandMessageService.RegisterMessage(msg.Id, Guild?.Id ?? 0, msg.ChannelId, Author.Id); - // } + // store log of sent message, so it can be queried or deleted later + // skip DMs as DM messages can always be deleted + if (Guild != null) + await Repository.AddCommandMessage(new Core.CommandMessage + { + Mid = msg.Id, + Guild = Guild!.Id, + Channel = Channel.Id, + Sender = Author.Id, + OriginalMid = Message.Id, + }); return msg; } diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index 94602a06..ec34cea9 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -347,13 +347,8 @@ public class ProxiedMessage var message = await ctx.Repository.GetFullMessage(messageId.Value); if (message == null) { - if (isDelete) - { - await DeleteCommandMessage(ctx, messageId.Value); - return; - } - - throw Errors.MessageNotFound(messageId.Value); + await GetCommandMessage(ctx, messageId.Value, isDelete); + return; } var showContent = true; @@ -448,20 +443,35 @@ public class ProxiedMessage await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message, showContent, ctx.Config)); } - private async Task DeleteCommandMessage(Context ctx, ulong messageId) + private async Task GetCommandMessage(Context ctx, ulong messageId, bool isDelete) { - var cmessage = await ctx.Services.Resolve().GetCommandMessage(messageId); - if (cmessage == null) + var msg = await _repo.GetCommandMessage(messageId); + if (msg == null) throw Errors.MessageNotFound(messageId); - if (cmessage!.AuthorId != ctx.Author.Id) - throw new PKError("You can only delete command messages queried by this account."); + if (isDelete) + { + if (msg.Sender != ctx.Author.Id) + throw new PKError("You can only delete command messages queried by this account."); - await ctx.Rest.DeleteMessage(cmessage.ChannelId, messageId); + await ctx.Rest.DeleteMessage(msg.Channel, messageId); - if (ctx.Guild != null) - await ctx.Rest.DeleteMessage(ctx.Message); - else - await ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new Emoji { Name = Emojis.Success }); + if (ctx.Guild != null) + await ctx.Rest.DeleteMessage(ctx.Message); + else + await ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new Emoji { Name = Emojis.Success }); + + return; + } + + var showContent = true; + + var channel = await _rest.GetChannelOrNull(msg.Channel); + if (channel == null) + showContent = false; + else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel)) + showContent = false; + + await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent)); } } \ No newline at end of file diff --git a/PluralKit.Bot/Services/CommandMessageService.cs b/PluralKit.Bot/Services/CommandMessageService.cs index 440670ab..684daf86 100644 --- a/PluralKit.Bot/Services/CommandMessageService.cs +++ b/PluralKit.Bot/Services/CommandMessageService.cs @@ -9,29 +9,23 @@ namespace PluralKit.Bot; public class CommandMessageService { private readonly RedisService _redis; + private readonly ModelRepository _repo; private readonly ILogger _logger; private static readonly TimeSpan CommandMessageRetention = TimeSpan.FromHours(24); - public CommandMessageService(RedisService redis, IClock clock, ILogger logger) + public CommandMessageService(RedisService redis, ModelRepository repo, IClock clock, ILogger logger) { _redis = redis; + _repo = repo; _logger = logger.ForContext(); } - public async Task RegisterMessage(ulong messageId, ulong guildId, ulong channelId, ulong authorId) - { - if (_redis.Connection == null) return; - - _logger.Debug( - "Registering command response {MessageId} from author {AuthorId} in {ChannelId}", - messageId, authorId, channelId - ); - - await _redis.Connection.GetDatabase().StringSetAsync("command_message:" + messageId.ToString(), $"{authorId}-{channelId}-{guildId}", expiry: CommandMessageRetention); - } - public async Task GetCommandMessage(ulong messageId) { + var repoMsg = await _repo.GetCommandMessage(messageId); + if (repoMsg != null) + return new CommandMessage(repoMsg.Sender, repoMsg.Channel, repoMsg.Guild); + var str = await _redis.Connection.GetDatabase().StringGetAsync(messageId.ToString()); if (str.HasValue) { diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 178d1339..186e3a45 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -420,6 +420,24 @@ public class EmbedService return eb.Build(); } + public async Task CreateCommandMessageInfoEmbed(Core.CommandMessage msg, bool showContent) + { + var content = "*(command message deleted or inaccessible)*"; + if (showContent) + { + var discordMessage = await _rest.GetMessageOrNull(msg.Channel, msg.OriginalMid); + if (discordMessage != null) + content = discordMessage.Content; + } + + return new EmbedBuilder() + .Title("Command response message") + .Description(content) + .Field(new("Original message", $"https://discord.com/channels/{msg.Guild}/{msg.Channel}/{msg.OriginalMid}", true)) + .Field(new("Sent by", $"<@{msg.Sender}>", true)) + .Build(); + } + public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, PKSystem system, PKGroup group, DateTimeZone tz, LookupContext ctx, string embedTitle, bool ignoreNoFronters, bool showFlat) diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs index 706e3a13..05313c89 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs @@ -42,12 +42,37 @@ public partial class ModelRepository }; } + public async Task AddCommandMessage(CommandMessage msg) + { + var query = new Query("command_messages").AsInsert(new + { + mid = msg.Mid, + guild = msg.Guild, + channel = msg.Channel, + sender = msg.Sender, + original_mid = msg.OriginalMid + }); + await _db.ExecuteQuery(query, messages: true); + + _logger.Debug("Stored command message {@StoredMessage} in channel {Channel}", msg, msg.Channel); + } + + public Task GetCommandMessage(ulong id) + => _db.QueryFirst(new Query("command_messages").Where("mid", id), messages: true); + public async Task DeleteMessage(ulong id) { var query = new Query("messages").AsDelete().Where("mid", id); var rowCount = await _db.ExecuteQuery(query, messages: true); if (rowCount > 0) _logger.Information("Deleted message {MessageId} from database", id); + else + { + var cquery = new Query("command_messages").AsDelete().Where("mid", id); + var crowCount = await _db.ExecuteQuery(query, messages: true); + if (crowCount > 0) + _logger.Information("Deleted command message {MessageId} from database", id); + } } public async Task DeleteMessagesBulk(IReadOnlyCollection ids) @@ -59,5 +84,19 @@ public partial class ModelRepository if (rowCount > 0) _logger.Information("Bulk deleted messages ({FoundCount} found) from database: {MessageIds}", rowCount, ids); + var cquery = new Query("command_messages").AsDelete().WhereIn("mid", ids.Select(id => (long)id).ToArray()); + var crowCount = await _db.ExecuteQuery(query, messages: true); + if (crowCount > 0) + _logger.Information("Bulk deleted command messages ({FoundCount} found) from database: {MessageIds}", rowCount, + ids); } +} + +public class CommandMessage +{ + public ulong Mid { get; set; } + public ulong Guild { get; set; } + public ulong Channel { get; set; } + public ulong Sender { get; set; } + public ulong OriginalMid { get; set; } } \ No newline at end of file