From ea59b8850c5f9309d337fbb2764fd6bd4081d917 Mon Sep 17 00:00:00 2001 From: seeyebe Date: Fri, 26 Sep 2025 03:12:05 +0300 Subject: [PATCH] feat: add reply info in message delete log --- backend/src/data/DefaultLogMessages.json | 4 +- backend/src/data/GuildSavedMessages.ts | 8 +++ backend/src/data/entities/SavedMessage.ts | 5 ++ .../Logs/logFunctions/logMessageDelete.ts | 7 ++- .../Logs/logFunctions/logMessageDeleteAuto.ts | 17 +++++- backend/src/plugins/Logs/types.ts | 5 ++ .../Logs/util/getMessageReplyLogInfo.ts | 60 +++++++++++++++++++ backend/src/utils/templateSafeObjects.ts | 9 +++ config-checker/public/config-schema.json | 18 +++--- 9 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 backend/src/plugins/Logs/util/getMessageReplyLogInfo.ts diff --git a/backend/src/data/DefaultLogMessages.json b/backend/src/data/DefaultLogMessages.json index ff0d12cae..1e8c574ff 100644 --- a/backend/src/data/DefaultLogMessages.json +++ b/backend/src/data/DefaultLogMessages.json @@ -35,10 +35,10 @@ "ROLE_UPDATE": "{timestamp} 🖊 Role **{newRole.name}** (`{newRole.id}`) was edited. Changes:\n{differenceString}", "MESSAGE_EDIT": "{timestamp} ✏ {userMention(user)} edited their message (`{after.id}`) in {channelMention(channel)}:\n**Before:**{messageSummary(before)}**After:**{messageSummary(after)}", - "MESSAGE_DELETE": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "MESSAGE_DELETE": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "MESSAGE_DELETE_BULK": "{timestamp} 🗑 **{count}** messages by {authorIds} deleted in {channelMention(channel)} ({archiveUrl})", "MESSAGE_DELETE_BARE": "{timestamp} 🗑 Message (`{messageId}`) deleted in {channelMention(channel)} (no more info available)", - "MESSAGE_DELETE_AUTO": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "MESSAGE_DELETE_AUTO": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "VOICE_CHANNEL_JOIN": "{timestamp} 🎙 🔵 {userMention(member)} joined {channelMention(channel)}", "VOICE_CHANNEL_MOVE": "{timestamp} 🎙 ↔ {userMention(member)} moved from {channelMention(oldChannel)} to {channelMention(newChannel)}", diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index 3edc28104..1a2c49498 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -119,6 +119,14 @@ export class GuildSavedMessages extends BaseGuildRepository { })); } + if (msg.reference && (msg.reference.messageId || msg.reference.channelId || msg.reference.guildId)) { + data.reference = { + messageId: msg.reference.messageId ?? null, + channelId: msg.reference.channelId ?? null, + guildId: msg.reference.guildId ?? null, + }; + } + return data; } diff --git a/backend/src/data/entities/SavedMessage.ts b/backend/src/data/entities/SavedMessage.ts index 9a0b310ff..0158dadae 100644 --- a/backend/src/data/entities/SavedMessage.ts +++ b/backend/src/data/entities/SavedMessage.ts @@ -75,6 +75,11 @@ export interface ISavedMessageData { embeds?: ISavedMessageEmbedData[]; stickers?: ISavedMessageStickerData[]; timestamp: number; + reference?: { + messageId?: Snowflake | null; + channelId?: Snowflake | null; + guildId?: Snowflake | null; + }; } @Entity("messages") diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts index 1952d0213..b0a8be31d 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDelete.ts @@ -14,6 +14,7 @@ import { import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin.js"; import { LogsPluginType } from "../types.js"; import { log } from "../util/log.js"; +import { getMessageReplyLogInfo } from "../util/getMessageReplyLogInfo.js"; export interface LogMessageDeleteData { user: User | UnknownUser; @@ -21,7 +22,7 @@ export interface LogMessageDeleteData { message: SavedMessage; } -export function logMessageDelete(pluginData: GuildPluginData, data: LogMessageDeleteData) { +export async function logMessageDelete(pluginData: GuildPluginData, data: LogMessageDeleteData) { // Replace attachment URLs with media URLs if (data.message.data.attachments) { for (const attachment of data.message.data.attachments as ISavedMessageAttachmentData[]) { @@ -33,6 +34,8 @@ export function logMessageDelete(pluginData: GuildPluginData, da const config = pluginData.config.get(); const timestampFormat = config.timestamp_format ?? undefined; + const { replyInfo, reply } = await getMessageReplyLogInfo(pluginData, data.message); + return log( pluginData, LogType.MESSAGE_DELETE, @@ -44,6 +47,8 @@ export function logMessageDelete(pluginData: GuildPluginData, da .getPlugin(TimeAndDatePlugin) .inGuildTz(moment.utc(data.message.data.timestamp, "x")) .format(timestampFormat), + replyInfo, + reply, }), { userId: data.user.id, diff --git a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts index bb4a36e7d..f2eebd6ad 100644 --- a/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts +++ b/backend/src/plugins/Logs/logFunctions/logMessageDeleteAuto.ts @@ -1,9 +1,9 @@ import { GuildBasedChannel, User } from "discord.js"; import { GuildPluginData } from "knub"; import { LogType } from "../../../data/LogType.js"; -import { SavedMessage } from "../../../data/entities/SavedMessage.js"; +import { ISavedMessageAttachmentData, SavedMessage } from "../../../data/entities/SavedMessage.js"; import { createTypedTemplateSafeValueContainer } from "../../../templateFormatter.js"; -import { UnknownUser } from "../../../utils.js"; +import { UnknownUser, useMediaUrls } from "../../../utils.js"; import { resolveChannelIds } from "../../../utils/resolveChannelIds.js"; import { channelToTemplateSafeChannel, @@ -12,6 +12,7 @@ import { } from "../../../utils/templateSafeObjects.js"; import { LogsPluginType } from "../types.js"; import { log } from "../util/log.js"; +import { getMessageReplyLogInfo } from "../util/getMessageReplyLogInfo.js"; export interface LogMessageDeleteAutoData { message: SavedMessage; @@ -20,7 +21,15 @@ export interface LogMessageDeleteAutoData { messageDate: string; } -export function logMessageDeleteAuto(pluginData: GuildPluginData, data: LogMessageDeleteAutoData) { +export async function logMessageDeleteAuto(pluginData: GuildPluginData, data: LogMessageDeleteAutoData) { + if (data.message.data.attachments) { + for (const attachment of data.message.data.attachments as ISavedMessageAttachmentData[]) { + attachment.url = useMediaUrls(attachment.url); + } + } + + const { replyInfo, reply } = await getMessageReplyLogInfo(pluginData, data.message); + return log( pluginData, LogType.MESSAGE_DELETE_AUTO, @@ -29,6 +38,8 @@ export function logMessageDeleteAuto(pluginData: GuildPluginData user: userToTemplateSafeUser(data.user), channel: channelToTemplateSafeChannel(data.channel), messageDate: data.messageDate, + replyInfo, + reply, }), { userId: data.user.id, diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index 1efbbc628..ad38f969c 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -6,6 +6,7 @@ import { GuildCases } from "../../data/GuildCases.js"; import { GuildLogs } from "../../data/GuildLogs.js"; import { GuildSavedMessages } from "../../data/GuildSavedMessages.js"; import { LogType } from "../../data/LogType.js"; +import { TemplateSafeValueContainer } from "../../templateFormatter.js"; import { keys, zBoundedCharacters, zEmbedInput, zMessageContent, zRegex, zSnowflake, zStrictMessageContent } from "../../utils.js"; import { MessageBuffer } from "../../utils/MessageBuffer.js"; import { @@ -236,6 +237,8 @@ export const LogTypeData = z.object({ channel: z.instanceof(TemplateSafeChannel), messageDate: z.string(), message: z.instanceof(TemplateSafeSavedMessage), + replyInfo: z.string(), + reply: z.instanceof(TemplateSafeValueContainer).nullable(), }), [LogType.MESSAGE_DELETE_BULK]: z.object({ @@ -486,6 +489,8 @@ export const LogTypeData = z.object({ user: z.instanceof(TemplateSafeUser), channel: z.instanceof(TemplateSafeChannel), messageDate: z.string(), + replyInfo: z.string(), + reply: z.instanceof(TemplateSafeValueContainer).nullable(), }), [LogType.SET_ANTIRAID_USER]: z.object({ diff --git a/backend/src/plugins/Logs/util/getMessageReplyLogInfo.ts b/backend/src/plugins/Logs/util/getMessageReplyLogInfo.ts new file mode 100644 index 000000000..b63b76b7f --- /dev/null +++ b/backend/src/plugins/Logs/util/getMessageReplyLogInfo.ts @@ -0,0 +1,60 @@ +import { GuildPluginData } from "knub"; +import { ISavedMessageAttachmentData, SavedMessage } from "../../../data/entities/SavedMessage.js"; +import { messageLink, messageSummary, useMediaUrls } from "../../../utils.js"; +import { TemplateSafeValueContainer } from "../../../templateFormatter.js"; +import { savedMessageToTemplateSafeSavedMessage, TemplateSafeSavedMessage } from "../../../utils/templateSafeObjects.js"; +import { LogsPluginType } from "../types.js"; + +export interface MessageReplyLogInfo { + replyInfo: string; + reply: TemplateSafeValueContainer | null; +} + +export async function getMessageReplyLogInfo( + pluginData: GuildPluginData, + message: SavedMessage, +): Promise { + const reference = message.data.reference; + if (!reference?.messageId || !reference.channelId) { + return { replyInfo: "", reply: null }; + } + + const link = messageLink(reference.guildId ?? message.guild_id, reference.channelId, reference.messageId); + let replyInfo = `\n**Replied To:** [Jump to message](${link})`; + + const referencedMessage = await pluginData.state.savedMessages.find(reference.messageId, true); + + let timestamp: string | null = null; + let summary: string | null = null; + let timestampMs: number | null = null; + let templateSafeMessage: TemplateSafeSavedMessage | null = null; + + if (referencedMessage) { + if (referencedMessage.data.attachments) { + for (const attachment of referencedMessage.data.attachments as ISavedMessageAttachmentData[]) { + attachment.url = useMediaUrls(attachment.url); + } + } + + timestampMs = referencedMessage.data.timestamp; + timestamp = ``; + replyInfo += ` (posted at ${timestamp})`; + + summary = messageSummary(referencedMessage); + if (summary) { + replyInfo += `\n${summary}`; + } + + templateSafeMessage = savedMessageToTemplateSafeSavedMessage(referencedMessage); + } + + const reply = new TemplateSafeValueContainer({ + link, + timestamp, + timestampMs, + summary, + message: templateSafeMessage, + }); + + return { replyInfo, reply }; +} diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts index 196593f3a..906f8b3e7 100644 --- a/backend/src/utils/templateSafeObjects.ts +++ b/backend/src/utils/templateSafeObjects.ts @@ -191,6 +191,7 @@ export class TemplateSafeSavedMessageData extends TemplateSafeValueContainer { embeds?: Array>; stickers?: Array>; timestamp: number; + reference?: TypedTemplateSafeValueContainer; constructor(data: InputProps) { super(); @@ -445,6 +446,14 @@ export function savedMessageToTemplateSafeSavedMessage(savedMessage: SavedMessag ), timestamp: savedMessage.data.timestamp, + + reference: savedMessage.data.reference + ? (new TemplateSafeValueContainer({ + messageId: savedMessage.data.reference.messageId ?? null, + channelId: savedMessage.data.reference.channelId ?? null, + guildId: savedMessage.data.reference.guildId ?? null, + }) as TypedTemplateSafeValueContainer) + : undefined, }), }); } diff --git a/config-checker/public/config-schema.json b/config-checker/public/config-schema.json index 65769ff5c..dc00139f9 100644 --- a/config-checker/public/config-schema.json +++ b/config-checker/public/config-schema.json @@ -9210,7 +9210,7 @@ ] }, "MESSAGE_DELETE": { - "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -9650,7 +9650,7 @@ ] }, "MESSAGE_DELETE_AUTO": { - "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -10037,7 +10037,7 @@ ] }, "MESSAGE_DELETE": { - "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -10477,7 +10477,7 @@ ] }, "MESSAGE_DELETE_AUTO": { - "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -11153,7 +11153,7 @@ ] }, "MESSAGE_DELETE": { - "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -11593,7 +11593,7 @@ ] }, "MESSAGE_DELETE_AUTO": { - "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -11980,7 +11980,7 @@ ] }, "MESSAGE_DELETE": { - "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Message (`{message.id}`) from {userMention(user)} deleted in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -12420,7 +12420,7 @@ ] }, "MESSAGE_DELETE_AUTO": { - "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}", + "default": "{timestamp} 🗑 Auto-deleted message (`{message.id}`) from {userMention(user)} in {channelMention(channel)} (originally posted at **{messageDate}**):{messageSummary(message)}{replyInfo}", "anyOf": [ { "type": "string" @@ -20882,4 +20882,4 @@ } }, "$schema": "https://json-schema.org/draft-2020-12/schema" -} \ No newline at end of file +}