From 9acc3c641457e73e196b68783a4705a68fd374c4 Mon Sep 17 00:00:00 2001 From: Lea Date: Fri, 7 Apr 2023 22:14:23 +0200 Subject: [PATCH] Add word filter function --- bot/src/bot/commands/configuration/botctl.ts | 14 +++- bot/src/bot/modules/antispam.ts | 69 +++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/bot/src/bot/commands/configuration/botctl.ts b/bot/src/bot/commands/configuration/botctl.ts index 40380f9..61bff65 100644 --- a/bot/src/bot/commands/configuration/botctl.ts +++ b/bot/src/bot/commands/configuration/botctl.ts @@ -1,9 +1,11 @@ +import ServerConfig from "automod/dist/types/ServerConfig"; import axios from "axios"; import FormData from "form-data"; import { client, dbs } from "../../.."; import CommandCategory from "../../../struct/commands/CommandCategory"; import SimpleCommand from "../../../struct/commands/SimpleCommand"; import MessageCommandContext from "../../../struct/MessageCommandContext"; +import { checkMessageForFilteredWords } from "../../modules/antispam"; import { DEFAULT_PREFIX } from "../../modules/command_handler"; import { embed, EmbedColor, getAutumnURL, getDmChannel, isBotManager, NO_MANAGER_MSG, sanitizeMessageContent } from "../../util"; @@ -295,6 +297,15 @@ export default { ] }); break; } + case 'test': { + const match = checkMessageForFilteredWords(args.join(' '), config as ServerConfig); + await message.reply({ embeds: [ + match + ? embed('Your word list matches this test phrase!', 'Filter Test', EmbedColor.SoftError) + : embed('Your word list does not match this test phrase!', 'Filter Test', EmbedColor.Success) + ] }); + break; + } default: { await message.reply({ embeds: [ embed( @@ -305,7 +316,8 @@ export default { `- **${DEFAULT_PREFIX}botctl filter remove** - Remove a word from the list.\n` + `- **${DEFAULT_PREFIX}botctl filter show** - Send the current filter list.\n` + `- **${DEFAULT_PREFIX}botctl filter message [message]** - Set the message sent when a message is matched.\n` + - `- **${DEFAULT_PREFIX}botctl filter action [log|delete|warn]** - Configure the action taken on filtered messages.\n`, + `- **${DEFAULT_PREFIX}botctl filter action [log|delete|warn]** - Configure the action taken on filtered messages.\n` + + `- **${DEFAULT_PREFIX}botctl filter test [phrase]** - Test whether a phrase matches your word list.\n`, 'Word filter', ), embed( diff --git a/bot/src/bot/modules/antispam.ts b/bot/src/bot/modules/antispam.ts index d4ca347..05c8b74 100644 --- a/bot/src/bot/modules/antispam.ts +++ b/bot/src/bot/modules/antispam.ts @@ -8,6 +8,7 @@ import ModerationAction from "automod/dist/types/antispam/ModerationAction"; import logger from "../logger"; import { awaitClient, isModerator, storeInfraction } from "../util"; import { getDmChannel, sanitizeMessageContent } from "../util"; +import ServerConfig from "automod/dist/types/ServerConfig"; let msgCountStore: Map = new Map(); @@ -106,6 +107,72 @@ function getWarnMsg(rule: AntispamRule, message: Message) { } else return `<@${message.author_id}>, please stop spamming.`; } +function checkMessageForFilteredWords(message: string, config: ServerConfig): boolean { + if (!config.wordlistEnabled || !config.wordlist?.length || !message) return false; + + const words = { + soft: config.wordlist.filter(w => w.strictness == 'SOFT').map(w => w.word), + hard: config.wordlist.filter(w => w.strictness == 'HARD').map(w => w.word), + strict: config.wordlist.filter(w => w.strictness == 'STRICT').map(w => w.word), + } + + const softSegments = message.split(/\s/g).map(s => s.toLowerCase()); + for (const word of words.soft) { + if (softSegments.includes(word.toLowerCase())) return true; + } + + for (const word of words.hard) { + if (message.toLowerCase().includes(word.toLowerCase())) return true; + } + + const replace = { + '0': 'o', + '1': 'i', + '4': 'a', + '3': 'e', + '5': 's', + '6': 'g', + '7': 't', + '8': 'b', + '9': 'g', + '@': 'a', + '^': 'a', + 'Д': 'a', + 'ß': 'b', + '¢': 'c', + '©': 'c', + '<': 'c', + '€': 'e', + 'ƒ': 'f', + 'ท': 'n', + 'И': 'n', + 'Ø': 'o', + 'Я': 'r', + '®': 'r', + '$': 's', + '§': 's', + '†': 't', + 'บ': 'u', + 'พ': 'w', + '₩': 'w', + '×': 'x', + '¥': 'y', + } + const replaceChars = (input: string) => { + input = `${input}`; + for (const pair of Object.entries(replace)) { + input = input.replaceAll(pair[0], pair[1]); + } + return input; + } + const replacedMsg = replaceChars(message.toLowerCase().replace(/\s/g, '')); + for (const word of words.strict) { + if (replacedMsg.includes(replaceChars(word.toLowerCase()))) return true; + } + + return false; +} + // Scan all servers for the `discoverable` flag and notify their owners that antispam is forcefully enabled const notifyPublicServers = async () => { logger.info('Sending antispam notification to public servers'); @@ -148,4 +215,4 @@ Thanks for being part of Revolt!`); awaitClient().then(() => notifyPublicServers()); -export { antispam } +export { antispam, checkMessageForFilteredWords }