summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-10-24 19:22:21 -0700
committerFuwn <[email protected]>2025-10-24 19:22:21 -0700
commitf46034c83a1d93acb6034d79e35fa94c2e68df84 (patch)
tree0b3491e9a99176383e221b3ae0e9385f3fd4bfc1
parentfix(listeners:autoDeletion): Fix banned string and inspection logic (diff)
downloadumabotdiscord-f46034c83a1d93acb6034d79e35fa94c2e68df84.tar.xz
umabotdiscord-f46034c83a1d93acb6034d79e35fa94c2e68df84.zip
feat(gateway:listeners): Add bot message logger
-rw-r--r--.gitignore1
-rw-r--r--packages/gateway/src/listeners/botMessageLogger.ts155
-rw-r--r--packages/gateway/src/listeners/index.ts2
3 files changed, 158 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index a00343a..ac21de2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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);
};