diff --git a/package.json b/package.json index f6a3e6a2..99cf6b69 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "humanize-duration-ts": "^2.1.1", "js-yaml": "^4.1.0", "jsdom": "^16.6.0", - "lru-cache": "^11.0.1", "matrix-appservice-bridge": "10.3.1", "nsfwjs": "^4.1.0", "parse-duration": "^1.0.2", @@ -70,6 +69,5 @@ }, "engines": { "node": ">=20.0.0" - }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + } } diff --git a/src/protections/MentionSpam.ts b/src/protections/MentionSpam.ts index 95a6cb3c..81f7714d 100644 --- a/src/protections/MentionSpam.ts +++ b/src/protections/MentionSpam.ts @@ -18,15 +18,10 @@ import { Protection } from "./IProtection"; import { Mjolnir } from "../Mjolnir"; import { LogLevel, LogService, Permalinks, UserID } from "@vector-im/matrix-bot-sdk"; import { NumberProtectionSetting } from "./ProtectionSettings"; -import { LRUCache } from "lru-cache"; export const DEFAULT_MAX_MENTIONS = 10; export class MentionSpam extends Protection { - private roomDisplaynameCache = new LRUCache({ - ttl: 1000 * 60 * 24, // 24 minutes - ttlAutopurge: true, - }); settings = { maxMentions: new NumberProtectionSetting(DEFAULT_MAX_MENTIONS, 1), @@ -43,29 +38,7 @@ export class MentionSpam extends Protection { return "If a user posts many mentions, that message is redacted. No bans are issued."; } - private async getRoomDisplaynames(mjolnir: Mjolnir, roomId: string): Promise { - const existing = this.roomDisplaynameCache.get(roomId); - if (existing) { - return existing; - } - const profiles = await mjolnir.client.getJoinedRoomMembersWithProfiles(roomId); - const displaynames = ( - Object.values(profiles) - .map((v) => v.display_name?.toLowerCase()) - .filter((v) => typeof v === "string") as string[] - ) - // Limit to displaynames with more than a few characters. - .filter((displayname) => displayname.length > 2); - - this.roomDisplaynameCache.set(roomId, displaynames); - return displaynames; - } - - public checkMentions( - body: unknown | undefined, - htmlBody: unknown | undefined, - mentionArray: unknown | undefined, - ): boolean { + public checkMentions(body: unknown|undefined, htmlBody: unknown|undefined, mentionArray: unknown|undefined): boolean { const max = this.settings.maxMentions.value; if (Array.isArray(mentionArray) && mentionArray.length > max) { return true; @@ -79,41 +52,11 @@ export class MentionSpam extends Protection { return false; } - public checkDisplaynameMentions( - body: unknown | undefined, - htmlBody: unknown | undefined, - displaynames: string[], - ): boolean { - const max = this.settings.maxMentions.value; - const bodyWords = ((typeof body === "string" && body) || "").toLowerCase(); - if (displaynames.filter((s) => bodyWords.includes(s.toLowerCase())).length > max) { - return true; - } - const htmlBodyWords = decodeURIComponent((typeof htmlBody === "string" && htmlBody) || "").toLowerCase(); - if (displaynames.filter((s) => htmlBodyWords.includes(s)).length > max) { - return true; - } - return false; - } - public async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise { if (event["type"] === "m.room.message") { let content = event["content"] || {}; const explicitMentions = content["m.mentions"]?.user_ids; - let hitLimit = this.checkMentions(content.body, content.formatted_body, explicitMentions); - - // Slightly more costly to hit displaynames, so only do it if we don't hit on mxid matches. - if (!hitLimit) { - const displaynames = await this.getRoomDisplaynames(mjolnir, roomId); - hitLimit = this.checkDisplaynameMentions(content.body, content.formatted_body, displaynames); - if (hitLimit) { - LogService.info( - "MentionSpam", - `Hitlimit reached via display name mention check for event content ${JSON.stringify(content)}`, - ); - } - } - + const hitLimit = this.checkMentions(content.body, content.formatted_body, explicitMentions); if (hitLimit) { await mjolnir.managementRoomOutput.logMessage( LogLevel.WARN, diff --git a/test/integration/mentionSpamProtectionTest.ts b/test/integration/mentionSpamProtectionTest.ts index bda36ae0..aadb38f8 100644 --- a/test/integration/mentionSpamProtectionTest.ts +++ b/test/integration/mentionSpamProtectionTest.ts @@ -94,18 +94,10 @@ describe("Test: Mention spam protection", function () { }); }); // Also covers HTML mentions - const mentionUsers = Array.from({ length: DEFAULT_MAX_MENTIONS + 1 }, (_, i) => `@user${i}:example.org`); - const mentionDisplaynames = Array.from({ length: DEFAULT_MAX_MENTIONS + 1 }, (_, i) => `Test User ${i}`); - - // Pre-set the displayname cache. - let protection = this.mjolnir.protectionManager.protections.get("MentionSpam"); - protection.roomDisplaynameCache.set(room, mentionDisplaynames); - - const messageWithTextMentions = await client.sendText(room, mentionUsers.join(" ")); - const messageWithHTMLMentions = await client.sendHtmlText( - room, - mentionUsers.map((u) => `${u}`).join(" "), - ); + const mentionUsers = Array.from({length: DEFAULT_MAX_MENTIONS+1}, (_, i) => `@user${i}:example.org`); + const messageWithTextMentions = await client.sendText(room, mentionUsers.join(' ')); + const messageWithHTMLMentions = await client.sendHtmlText(room, + mentionUsers.map(u => `${u}`).join(' ')); const messageWithMMentions = await client.sendMessage(room, { msgtype: "m.text", body: "Hello world", @@ -113,7 +105,6 @@ describe("Test: Mention spam protection", function () { user_ids: mentionUsers, }, }); - const messageWithDisplaynameMentions = await client.sendText(room, mentionDisplaynames.join(" ")); await delay(500); @@ -125,24 +116,5 @@ describe("Test: Mention spam protection", function () { const fetchedMentionsEvent = await client.getEvent(room, messageWithMMentions); assert.equal(Object.keys(fetchedMentionsEvent.content).length, 0, "This event should have been redacted"); - - const fetchedDisplaynameEvent = await client.getEvent(room, messageWithDisplaynameMentions); - assert.equal(Object.keys(fetchedDisplaynameEvent.content).length, 0, "This event should have been redacted"); - - // send messages after activating protection, they should be auto-redacted - const messages = []; - for (let i = 0; i < 10; i++) { - let nextMessage = await client.sendText(room, `hello${i}`); - messages.push(nextMessage); - } - - messages.forEach(async (eventID) => { - await client.getEvent(room, eventID); - assert.equal( - Object.keys(fetchedDisplaynameEvent.content).length, - 0, - "This event should have been redacted", - ); - }); }); }); diff --git a/yarn.lock b/yarn.lock index 29113bca..6e433a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2578,11 +2578,6 @@ lru-cache@^10.0.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.1.tgz#3a732fbfedb82c5ba7bca6564ad3f42afcb6e147" - integrity sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ== - lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"