diff options
| author | Fuwn <[email protected]> | 2025-10-24 19:22:21 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-10-24 19:22:21 -0700 |
| commit | f46034c83a1d93acb6034d79e35fa94c2e68df84 (patch) | |
| tree | 0b3491e9a99176383e221b3ae0e9385f3fd4bfc1 | |
| parent | fix(listeners:autoDeletion): Fix banned string and inspection logic (diff) | |
| download | umabotdiscord-f46034c83a1d93acb6034d79e35fa94c2e68df84.tar.xz umabotdiscord-f46034c83a1d93acb6034d79e35fa94c2e68df84.zip | |
feat(gateway:listeners): Add bot message logger
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | packages/gateway/src/listeners/botMessageLogger.ts | 155 | ||||
| -rw-r--r-- | packages/gateway/src/listeners/index.ts | 2 |
3 files changed, 158 insertions, 0 deletions
@@ -3,3 +3,4 @@ coverage .dev.vars .wrangler .env* +bot_messages.csv diff --git a/packages/gateway/src/listeners/botMessageLogger.ts b/packages/gateway/src/listeners/botMessageLogger.ts new file mode 100644 index 0000000..8e3815b --- /dev/null +++ b/packages/gateway/src/listeners/botMessageLogger.ts @@ -0,0 +1,155 @@ +import { Client, Events, Message } from "discord.js"; +import { writeFileSync, existsSync } from "fs"; +import { join } from "path"; +import { logUnexpectedDiscordAPIError } from "../utilities"; + +const ENABLE_BOT_MESSAGE_LOGGING = true; +const LOGGED_CHANNEL_IDS = ["1410333697701314791", "1406422619087044675"]; +const CSV_FILE_PATH = join(process.cwd(), "bot_messages.csv"); +const CSV_HEADER = + "timestamp,message_id,channel_id,channel_name,guild_id,guild_name,content,reply_to_message_id,reply_chain_root_id,reply_chain_length,reply_chain_content,has_attachments,attachment_count,embed_count\n"; + +interface BotMessageLogEntry { + timestamp: string; + messageId: string; + channelId: string; + channelName: string; + guildId: string; + guildName: string; + content: string; + replyToMessageId: string; + replyChainRootId: string; + replyChainLength: number; + replyChainContent: string; + hasAttachments: boolean; + attachmentCount: number; + embedCount: number; +} + +const escapeCsvValue = (value: string): string => { + if (value.includes('"') || value.includes(",") || value.includes("\n")) + return `"${value.replace(/"/g, '""')}"`; + + return value; +}; + +const formatCsvRow = (entry: BotMessageLogEntry): string => { + return ( + [ + entry.timestamp, + entry.messageId, + entry.channelId, + escapeCsvValue(entry.channelName), + entry.guildId, + escapeCsvValue(entry.guildName), + escapeCsvValue(entry.content), + entry.replyToMessageId, + entry.replyChainRootId, + entry.replyChainLength.toString(), + escapeCsvValue(entry.replyChainContent), + entry.hasAttachments.toString(), + entry.attachmentCount.toString(), + entry.embedCount.toString(), + ].join(",") + "\n" + ); +}; + +const initializeCsvFile = (): void => { + if (!existsSync(CSV_FILE_PATH)) + writeFileSync(CSV_FILE_PATH, CSV_HEADER, "utf8"); +}; + +const appendToCsv = (entry: BotMessageLogEntry): void => { + try { + const csvRow = formatCsvRow(entry); + + writeFileSync(CSV_FILE_PATH, csvRow, { flag: "a", encoding: "utf8" }); + } catch (error) { + logUnexpectedDiscordAPIError(error); + } +}; + +const findReplyChainRoot = async ( + message: Message, +): Promise<{ rootId: string; length: number; content: string }> => { + if (!message.reference?.messageId) + return { rootId: message.id, length: 0, content: "" }; + + let currentMessage = message; + let chainLength = 0; + const visitedMessages = new Set<string>(); + const chainContent: string[] = []; + + try { + while (currentMessage.reference?.messageId) { + if (visitedMessages.has(currentMessage.id)) break; + + visitedMessages.add(currentMessage.id); + + const referencedMessage = await currentMessage.fetchReference(); + + if (!referencedMessage) break; + + chainContent.push(referencedMessage.content || ""); + + currentMessage = referencedMessage; + chainLength += 1; + + if (chainLength > 50) break; + } + + return { + rootId: currentMessage.id, + length: chainLength, + content: chainContent.join(" | "), + }; + } catch (error) { + logUnexpectedDiscordAPIError(error); + + return { rootId: message.id, length: 0, content: "" }; + } +}; + +const logBotMessage = async (message: Message): Promise<void> => { + if (!ENABLE_BOT_MESSAGE_LOGGING) return; + + if (!LOGGED_CHANNEL_IDS.includes(message.channelId)) return; + + try { + const replyChain = await findReplyChainRoot(message); + const entry: BotMessageLogEntry = { + timestamp: message.createdAt.toISOString(), + messageId: message.id, + channelId: message.channelId, + channelName: message.channel.isDMBased() + ? "DM" + : (message.channel as any).name || "Unknown", + guildId: message.guildId || "DM", + guildName: message.guild?.name || "DM", + content: message.content || "", + replyToMessageId: message.reference?.messageId || "", + replyChainRootId: replyChain.rootId, + replyChainLength: replyChain.length, + replyChainContent: replyChain.content, + hasAttachments: message.attachments.size > 0, + attachmentCount: message.attachments.size, + embedCount: message.embeds.length, + }; + + appendToCsv(entry); + } catch (error) { + logUnexpectedDiscordAPIError(error); + } +}; + +export const handleBotMessageLogger = (client: Client) => { + if (!ENABLE_BOT_MESSAGE_LOGGING) return; + + initializeCsvFile(); + + client.on(Events.MessageCreate, async (message: Message) => { + if (message.author.id !== client.user?.id) return; + + await logBotMessage(message); + }); +}; diff --git a/packages/gateway/src/listeners/index.ts b/packages/gateway/src/listeners/index.ts index 15b617b..4bb6843 100644 --- a/packages/gateway/src/listeners/index.ts +++ b/packages/gateway/src/listeners/index.ts @@ -10,6 +10,7 @@ import { handleMemberLeave } from "./memberLeave"; import { handleTimeoutMirroring } from "./timeoutMirroring"; import { handleAutoDeletion } from "./autoDeletion"; import { handleEmojiUsageTracking } from "./emojiUsageTracking"; +import { handleBotMessageLogger } from "./botMessageLogger"; // import { handleMediaModeration } from "./mediaModeration"; export const handleListeners = (client: Client) => { @@ -24,5 +25,6 @@ export const handleListeners = (client: Client) => { handleTimeoutMirroring(client); handleAutoDeletion(client); handleEmojiUsageTracking(client); + handleBotMessageLogger(client); // handleMediaModeration(client); }; |