From 956c9a353992f35bd580b9e330a7232377774482 Mon Sep 17 00:00:00 2001 From: Nudesuppe42 Date: Mon, 15 Jun 2026 18:21:42 +0200 Subject: [PATCH 1/4] feat(messageCreate): Act on suspicious amount of attachments (mrbeast) --- src/events/messageCreate.event.ts | 69 +++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/src/events/messageCreate.event.ts b/src/events/messageCreate.event.ts index cf7198f7..0c98c8ca 100644 --- a/src/events/messageCreate.event.ts +++ b/src/events/messageCreate.event.ts @@ -1,15 +1,19 @@ -import BotClient from "../struct/BotClient.js" -import BotGuildMember from "../struct/discord/BotGuildMember.js" -import BotGuild from "../struct/discord/BotGuild.js" +import { hexToNum, noop } from "@buildtheearth/bot-utils" +import crypto from "crypto" +import { Message, MessageType, TextChannel } from "discord.js" +import typeorm from "typeorm" +import { runBtCommand } from "../commands/team.command.js" +import ModerationMenu from "../entities/ModerationMenu.entity.js" import Snippet from "../entities/Snippet.entity.js" +import BotClient from "../struct/BotClient.js" import languages from "../struct/client/iso6391.js" +import BotGuild from "../struct/discord/BotGuild.js" +import BotGuildMember from "../struct/discord/BotGuildMember.js" import chalk = require("chalk") -import { noop } from "@buildtheearth/bot-utils" -import { Message, MessageType } from "discord.js" -import typeorm from "typeorm" -import ModerationMenu from "../entities/ModerationMenu.entity.js" -import { runBtCommand } from "../commands/team.command.js" + +const ATTACHMENT_DUPLICATE_WINDOW = 30_000 +const recentAttachmentHashes: Map = new Map() function consumeCommand(client: BotClient, message: Message): string { const content = message.content @@ -50,6 +54,22 @@ function consumeTeam(client: BotClient, message: Message): string { return commandSplit.join(" ").trim() || "" } +function clearOldAttachmentHashes(now: number): void { + for (const [hash, timestamp] of recentAttachmentHashes) { + if (now - timestamp > ATTACHMENT_DUPLICATE_WINDOW) { + recentAttachmentHashes.delete(hash) + } + } +} + +async function getAttachmentHash(url: string): Promise { + const attachmentFetch = await fetch(url).catch(noop) + if (!attachmentFetch?.ok) return null + const arrayBuffer = await attachmentFetch.arrayBuffer().catch(() => null) + if (!arrayBuffer) return null + return crypto.createHash("sha256").update(new Uint8Array(arrayBuffer)).digest("hex") +} + export default async function (this: BotClient, message: Message): Promise { if (message.partial) await message.fetch().catch(noop) if (message.author.bot) return @@ -158,7 +178,38 @@ export default async function (this: BotClient, message: Message): Promise= 1 && message.guild?.id === this.config.guilds.main) + if (bannedWords.length >= 1 && message.guild?.id === this.config.guilds.main) { return ModerationMenu.createMenu(message, bannedWords, this) + } + } + if (main && message.attachments.size > 0) { + const attachmentCount = message.attachments.size + + if (attachmentCount == 2 || attachmentCount == 4) { + message.attachments.forEach(attachment => { + getAttachmentHash(attachment.url).then(async hash => { + if (!hash) return + const now = Date.now() + clearOldAttachmentHashes(now) + + const previous = recentAttachmentHashes.get(hash) + recentAttachmentHashes.set(hash, now) + if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { + message.delete().catch(noop) + const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") + + if (bannedWords.length >= 1) { + return ModerationMenu.createMenu(message, bannedWords, this) + } else { + await client.log({ + color: hexToNum(client.config.colors.info), + author: { name: "Attachment Delete" }, + description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` + }) + } + } + }) + }) + } } } From 34c6b775a912d8c63d9ff44fe6bdbda5b3acd6e1 Mon Sep 17 00:00:00 2001 From: Nudesuppe42 Date: Mon, 15 Jun 2026 20:32:29 +0200 Subject: [PATCH 2/4] refactor(messageCreate): optimize attachment handling --- src/events/messageCreate.event.ts | 82 +++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/events/messageCreate.event.ts b/src/events/messageCreate.event.ts index 0c98c8ca..06f41c4e 100644 --- a/src/events/messageCreate.event.ts +++ b/src/events/messageCreate.event.ts @@ -1,6 +1,7 @@ import { hexToNum, noop } from "@buildtheearth/bot-utils" import crypto from "crypto" -import { Message, MessageType, TextChannel } from "discord.js" +import { Message, MessageType } from "discord.js" +import fetch from "node-fetch" import typeorm from "typeorm" import { runBtCommand } from "../commands/team.command.js" import ModerationMenu from "../entities/ModerationMenu.entity.js" @@ -9,7 +10,6 @@ import BotClient from "../struct/BotClient.js" import languages from "../struct/client/iso6391.js" import BotGuild from "../struct/discord/BotGuild.js" import BotGuildMember from "../struct/discord/BotGuildMember.js" - import chalk = require("chalk") const ATTACHMENT_DUPLICATE_WINDOW = 30_000 @@ -185,31 +185,61 @@ export default async function (this: BotClient, message: Message): Promise 0) { const attachmentCount = message.attachments.size - if (attachmentCount == 2 || attachmentCount == 4) { - message.attachments.forEach(attachment => { - getAttachmentHash(attachment.url).then(async hash => { - if (!hash) return - const now = Date.now() - clearOldAttachmentHashes(now) - - const previous = recentAttachmentHashes.get(hash) - recentAttachmentHashes.set(hash, now) - if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { - message.delete().catch(noop) - const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") - - if (bannedWords.length >= 1) { - return ModerationMenu.createMenu(message, bannedWords, this) - } else { - await client.log({ - color: hexToNum(client.config.colors.info), - author: { name: "Attachment Delete" }, - description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` - }) - } + // if (attachmentCount == 2 || attachmentCount == 4) { + // message.attachments.forEach(attachment => { + // getAttachmentHash(attachment.url).then(async hash => { + // if (!hash) return + // const now = Date.now() + // clearOldAttachmentHashes(now) + + // const previous = recentAttachmentHashes.get(hash) + // recentAttachmentHashes.set(hash, now) + // if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { + // message.delete().catch(noop) + // const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") + + // if (bannedWords.length >= 1) { + // return ModerationMenu.createMenu(message, bannedWords, this) + // } else { + // await client.log({ + // color: hexToNum(client.config.colors.info), + // author: { name: "Attachment Delete" }, + // description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` + // }) + // } + // } + // }) + // }) + // } + if (attachmentCount === 2 || attachmentCount === 4) { + const messageHashes = new Set() + + for (const attachment of message.attachments.values()) { + const hash = await getAttachmentHash(attachment.url) + if (!hash) continue + if (messageHashes.has(hash)) continue + + messageHashes.add(hash) + const now = Date.now() + clearOldAttachmentHashes(now) + + const previous = recentAttachmentHashes.get(hash) + recentAttachmentHashes.set(hash, now) + if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { + const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") + if (bannedWords.length >= 1) { + await ModerationMenu.createMenu(message, bannedWords, this) + } else { + await message.delete().catch(noop) + await this.log({ + color: hexToNum(this.config.colors.info), + author: { name: "Attachment Delete" }, + description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` + }) + break } - }) - }) + } + } } } } From a6020a2374d123734d6ff48708b411331a5ae605 Mon Sep 17 00:00:00 2001 From: Nudesuppe42 Date: Mon, 15 Jun 2026 20:34:34 +0200 Subject: [PATCH 3/4] fix: remove deleted dependency --- package-lock.json | 97 ----------------------------------------------- package.json | 1 - 2 files changed, 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index e290c1d9..ec0f2794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,6 @@ "patch-package": "^6.4.7", "prom-client": "^15.1.0", "reflect-metadata": "0.1.13", - "run-my-sql-file": "^1.0.0", "sentiment": "^5.0.2", "sharp": "^0.30.7", "sprintf-js": "^1.1.2", @@ -12778,56 +12777,6 @@ "node": ">=0.12.0" } }, - "node_modules/run-my-sql-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/run-my-sql-file/-/run-my-sql-file-1.0.0.tgz", - "integrity": "sha512-5omLYPow55xnzlmjl4KIW1o4bf64+LLxG3p0J/TEJUDK5JtyYAkkVCj/ex57IvJ0oabntocuJY3hsNpYMGFS+g==", - "dependencies": { - "mysql2": "^1.7.0" - } - }, - "node_modules/run-my-sql-file/node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/run-my-sql-file/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/run-my-sql-file/node_modules/mysql2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", - "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", - "dependencies": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "lru-cache": "^5.1.1", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.1" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/run-my-sql-file/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -25289,52 +25238,6 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, - "run-my-sql-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/run-my-sql-file/-/run-my-sql-file-1.0.0.tgz", - "integrity": "sha512-5omLYPow55xnzlmjl4KIW1o4bf64+LLxG3p0J/TEJUDK5JtyYAkkVCj/ex57IvJ0oabntocuJY3hsNpYMGFS+g==", - "requires": { - "mysql2": "^1.7.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "mysql2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", - "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", - "requires": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "lru-cache": "^5.1.1", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.1" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 2c0d6b38..2d393057 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "patch-package": "^6.4.7", "prom-client": "^15.1.0", "reflect-metadata": "0.1.13", - "run-my-sql-file": "^1.0.0", "sentiment": "^5.0.2", "sharp": "^0.30.7", "sprintf-js": "^1.1.2", From bd9e495ec4975b7563762b9be826c7e466f9f549 Mon Sep 17 00:00:00 2001 From: Nudesuppe42 Date: Sat, 20 Jun 2026 18:25:28 +0200 Subject: [PATCH 4/4] refactor(messageCreate): remove comment, store message of connected hashes --- src/events/messageCreate.event.ts | 93 ++++++++++++------------------- 1 file changed, 37 insertions(+), 56 deletions(-) diff --git a/src/events/messageCreate.event.ts b/src/events/messageCreate.event.ts index 06f41c4e..c781c14a 100644 --- a/src/events/messageCreate.event.ts +++ b/src/events/messageCreate.event.ts @@ -13,7 +13,8 @@ import BotGuildMember from "../struct/discord/BotGuildMember.js" import chalk = require("chalk") const ATTACHMENT_DUPLICATE_WINDOW = 30_000 -const recentAttachmentHashes: Map = new Map() +const recentAttachmentHashes: Map = + new Map() function consumeCommand(client: BotClient, message: Message): string { const content = message.content @@ -55,8 +56,8 @@ function consumeTeam(client: BotClient, message: Message): string { } function clearOldAttachmentHashes(now: number): void { - for (const [hash, timestamp] of recentAttachmentHashes) { - if (now - timestamp > ATTACHMENT_DUPLICATE_WINDOW) { + for (const [hash, { time }] of recentAttachmentHashes) { + if (now - time > ATTACHMENT_DUPLICATE_WINDOW) { recentAttachmentHashes.delete(hash) } } @@ -185,59 +186,39 @@ export default async function (this: BotClient, message: Message): Promise 0) { const attachmentCount = message.attachments.size - // if (attachmentCount == 2 || attachmentCount == 4) { - // message.attachments.forEach(attachment => { - // getAttachmentHash(attachment.url).then(async hash => { - // if (!hash) return - // const now = Date.now() - // clearOldAttachmentHashes(now) - - // const previous = recentAttachmentHashes.get(hash) - // recentAttachmentHashes.set(hash, now) - // if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { - // message.delete().catch(noop) - // const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") - - // if (bannedWords.length >= 1) { - // return ModerationMenu.createMenu(message, bannedWords, this) - // } else { - // await client.log({ - // color: hexToNum(client.config.colors.info), - // author: { name: "Attachment Delete" }, - // description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` - // }) - // } - // } - // }) - // }) - // } - if (attachmentCount === 2 || attachmentCount === 4) { - const messageHashes = new Set() - - for (const attachment of message.attachments.values()) { - const hash = await getAttachmentHash(attachment.url) - if (!hash) continue - if (messageHashes.has(hash)) continue - - messageHashes.add(hash) - const now = Date.now() - clearOldAttachmentHashes(now) - - const previous = recentAttachmentHashes.get(hash) - recentAttachmentHashes.set(hash, now) - if (previous && now - previous <= ATTACHMENT_DUPLICATE_WINDOW) { - const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") - if (bannedWords.length >= 1) { - await ModerationMenu.createMenu(message, bannedWords, this) - } else { - await message.delete().catch(noop) - await this.log({ - color: hexToNum(this.config.colors.info), - author: { name: "Attachment Delete" }, - description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` - }) - break - } + const messageHashes = new Set() + + for (const attachment of message.attachments.values()) { + const hash = await getAttachmentHash(attachment.url) + if (!hash) continue + if (messageHashes.has(hash)) continue + + messageHashes.add(hash) + const now = Date.now() + clearOldAttachmentHashes(now) + + const previous = recentAttachmentHashes.get(hash) + recentAttachmentHashes.set(hash, { + time: now, + messages: [...(previous?.messages || []), message] + }) + if (previous && now - previous.time <= ATTACHMENT_DUPLICATE_WINDOW) { + for (const prevMessage of previous.messages) { + if (prevMessage.id === message.id) continue + await prevMessage.delete().catch(noop) + } + + const bannedWords = this.filter.findBannedWord("SUS_ATTACHMENT") + if (bannedWords.length >= 1) { + await ModerationMenu.createMenu(message, bannedWords, this) + } else { + await message.delete().catch(noop) + await this.log({ + color: hexToNum(this.config.colors.info), + author: { name: "Attachment Delete" }, + description: `Message with ${attachmentCount} attachments by ${message.member} in ${message.channel} deleted. No moderation action found.` + }) + break } } }