diff --git a/bot/src/bot/commands/configuration/botctl.ts b/bot/src/bot/commands/configuration/botctl.ts index 61bff65..219f523 100644 --- a/bot/src/bot/commands/configuration/botctl.ts +++ b/bot/src/bot/commands/configuration/botctl.ts @@ -233,6 +233,7 @@ export default { return await message.reply({ embeds: [ embed( 'This command lets you change the message the bot will send if a message is filtered.\n' + + 'Note that this message will not be sent if the configured action is to log events only.\n' + 'The current message is:\n' + `>${sanitizeMessageContent(config?.wordlistAction?.message ?? WORDLIST_DEFAULT_MESSAGE).trim().replace(/\n/g, '\n>')}\n` + '`{{user_id}}` will be substituted for the target user\'s ID.', @@ -353,3 +354,5 @@ export default { } } } as SimpleCommand; + +export { WORDLIST_DEFAULT_MESSAGE } diff --git a/bot/src/bot/modules/antispam.ts b/bot/src/bot/modules/antispam.ts index 05c8b74..ffd940e 100644 --- a/bot/src/bot/modules/antispam.ts +++ b/bot/src/bot/modules/antispam.ts @@ -6,12 +6,16 @@ import Infraction from "automod/dist/types/antispam/Infraction"; import InfractionType from "automod/dist/types/antispam/InfractionType"; import ModerationAction from "automod/dist/types/antispam/ModerationAction"; import logger from "../logger"; -import { awaitClient, isModerator, storeInfraction } from "../util"; +import { awaitClient, generateInfractionDMEmbed, isModerator, sendLogMessage, storeInfraction } from "../util"; import { getDmChannel, sanitizeMessageContent } from "../util"; import ServerConfig from "automod/dist/types/ServerConfig"; +import { WORDLIST_DEFAULT_MESSAGE } from "../commands/configuration/botctl"; let msgCountStore: Map = new Map(); +// Should use redis for this +const SENT_FILTER_MESSAGE: string[] = []; + /** * * @param message @@ -107,6 +111,78 @@ function getWarnMsg(rule: AntispamRule, message: Message) { } else return `<@${message.author_id}>, please stop spamming.`; } +/** + * Run word filter check and act on message if required + */ +async function wordFilterCheck(message: Message, config: ServerConfig) { + try { + if (!message.content) return; + const match = checkMessageForFilteredWords(message.content, config); + if (!match) return; + + console.log('Message matched word filter!'); + + // Lack of `break` is intended here + switch(config.wordlistAction?.action) { + case 'WARN': { + try { + const infraction: Infraction = { + _id: ulid(), + createdBy: null, + date: Date.now(), + reason: 'Word filter triggered', + server: message.channel!.server_id!, + type: InfractionType.Automatic, + user: message.author_id, + } + + await storeInfraction(infraction); + + if (config.dmOnWarn) { + const embed = generateInfractionDMEmbed(message.channel!.server!, config, infraction, message); + const dmChannel = await getDmChannel(message.author!); + + if (dmChannel.havePermission('SendMessage') && dmChannel.havePermission('SendEmbeds')) { + await dmChannel.sendMessage({ embeds: [ embed ] }); + } + else logger.warn('Missing permission to DM user.'); + } + } catch(e) { + console.error(e); + } + } + case 'DELETE': { + if (message.channel?.havePermission('ManageMessages')) { + const key = `${message.author_id}:${message.channel_id}`; + await message.delete(); + + if (!SENT_FILTER_MESSAGE.includes(key)) { + SENT_FILTER_MESSAGE.push(key); + setTimeout(() => SENT_FILTER_MESSAGE.splice(SENT_FILTER_MESSAGE.indexOf(key), 1), 30000); + await message.channel.sendMessage((config.wordlistAction.message || WORDLIST_DEFAULT_MESSAGE) + .replaceAll('{{user_id}}', message.author_id)); + } + } + } + case 'LOG': + default: { + if (!config.logs?.modAction) break; + await sendLogMessage(config.logs.modAction, { + title: 'Message triggered word filter', + description: `**Author:** @${message.author?.username} (${message.author_id})\n` + + `**Action:** ${config.wordlistAction?.action || 'LOG'}\n` + + `#### Content\n` + + `>${sanitizeMessageContent(message.content.substring(0, 1000)).trim().replace(/\n/g, '\n>')}`, + color: '#ff557f', + }); + } + + } + } catch(e) { + console.error(e); + } +} + function checkMessageForFilteredWords(message: string, config: ServerConfig): boolean { if (!config.wordlistEnabled || !config.wordlist?.length || !message) return false; @@ -215,4 +291,4 @@ Thanks for being part of Revolt!`); awaitClient().then(() => notifyPublicServers()); -export { antispam, checkMessageForFilteredWords } +export { antispam, wordFilterCheck, checkMessageForFilteredWords } diff --git a/bot/src/bot/modules/command_handler.ts b/bot/src/bot/modules/command_handler.ts index 56c48ca..f2272f9 100644 --- a/bot/src/bot/modules/command_handler.ts +++ b/bot/src/bot/modules/command_handler.ts @@ -3,7 +3,7 @@ import logger from "../logger"; import { client, dbs } from "../../index"; import fs from 'fs'; import path from 'path'; -import { antispam } from "./antispam"; +import { antispam, wordFilterCheck } from "./antispam"; import checkCustomRules from "./custom_rules/custom_rules"; import MessageCommandContext from "../../struct/MessageCommandContext"; import { fileURLToPath } from 'url'; @@ -65,6 +65,10 @@ let commands: SimpleCommand[]; dbs.USERS.findOne({ id: msg.author_id }), ]); + if (config) { + await wordFilterCheck(msg, config); + } + if (userConfig?.ignore) return; let args = msg.content.split(' '); diff --git a/bot/src/bot/util.ts b/bot/src/bot/util.ts index 03a1469..6cfbd76 100644 --- a/bot/src/bot/util.ts +++ b/bot/src/bot/util.ts @@ -465,11 +465,11 @@ const generateInfractionDMEmbed = ( server: Server, serverConfig: ServerConfig, infraction: Infraction, - message: MessageCommandContext + message: Message ) => { const embed: SendableEmbed = { - title: message.serverContext.name, - icon_url: message.serverContext.generateIconURL({ max_side: 128 }), + title: server.name, + icon_url: server.generateIconURL({ max_side: 128 }), colour: "#ff9e2f", url: message.url, description: @@ -480,7 +480,7 @@ const generateInfractionDMEmbed = ( }** from ` : `**warned** in `) + `'${sanitizeMessageContent( - message.serverContext.name + server.name ).trim()}' .\n` + `**Reason:** ${infraction.reason}\n` + `**Moderator:** [@${sanitizeMessageContent(