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", diff --git a/src/events/messageCreate.event.ts b/src/events/messageCreate.event.ts index cf7198f7..c781c14a 100644 --- a/src/events/messageCreate.event.ts +++ b/src/events/messageCreate.event.ts @@ -1,15 +1,20 @@ -import BotClient from "../struct/BotClient.js" -import BotGuildMember from "../struct/discord/BotGuildMember.js" -import BotGuild from "../struct/discord/BotGuild.js" -import Snippet from "../entities/Snippet.entity.js" -import languages from "../struct/client/iso6391.js" - -import chalk = require("chalk") -import { noop } from "@buildtheearth/bot-utils" +import { hexToNum, noop } from "@buildtheearth/bot-utils" +import crypto from "crypto" import { Message, MessageType } from "discord.js" +import fetch from "node-fetch" import typeorm from "typeorm" -import ModerationMenu from "../entities/ModerationMenu.entity.js" 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") + +const ATTACHMENT_DUPLICATE_WINDOW = 30_000 +const recentAttachmentHashes: Map = + new Map() function consumeCommand(client: BotClient, message: Message): string { const content = message.content @@ -50,6 +55,22 @@ function consumeTeam(client: BotClient, message: Message): string { return commandSplit.join(" ").trim() || "" } +function clearOldAttachmentHashes(now: number): void { + for (const [hash, { time }] of recentAttachmentHashes) { + if (now - time > 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 +179,48 @@ 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 + + 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 + } + } + } } }