AutoMod/src/bot/modules/custom_rules/message_content_trigger.ts
2022-01-16 15:50:11 +01:00

98 lines
3.7 KiB
TypeScript

import { Message } from "revolt.js/dist/maps/Messages";
import { client } from "../../..";
import CustomRuleTrigger from "../../../struct/antispam/CustomRuleTrigger";
import VM from 'vm';
let ruleTimeouts: Map<string, number> = new Map();
async function messageContentTrigger(message: Message, trigger: CustomRuleTrigger): Promise<boolean> {
/* Match message content */
let matched = false;
if (trigger.matcher) {
if (trigger.channelFilter) {
if (trigger.channelFilter.mode == 'WHITELIST' && !trigger.channelFilter.channels.includes(message.channel_id)) return false;
if (trigger.channelFilter.mode == 'BLACKLIST' && trigger.channelFilter.channels.includes(message.channel_id)) return false;
}
if (trigger.matcher instanceof RegExp) {
/**
* Since users will eventually be able to provide regexes, we need to protect
* againt ReDoS (Regular Expression Denial of Service).
* Therefore all RegExes will have an execution limit of 2ms.
* An additional mitigation step would be to disable rules that are frequently
* found to hit that limit, but that is out of scope for now.
*
* (If someone finds a flaw in my logic, please report it)
*/
// We use node's built-in `vm` module to limit execution time.
// No idea if this works the way I want it to
try {
let ctx = VM.createContext({
regex: trigger.matcher!,
matchedStrings: [],
content: message.content as string,
});
let script = new VM.Script('matchedStrings = content.match(regex);', { timeout: 2 });
script.runInContext(ctx);
if (ctx.matchedStrings?.length) matched = true;
} catch(e) {
console.error('Exception thrown while parsing RegEx: ' + e);
}
} else {
if ((message.content as string).includes(trigger.matcher)) matched = true;
}
}
if (!matched) return false;
/* Timeouts */
let timeoutKeys = {
global: trigger._id,
channel: trigger._id + '/channel/' + message.channel_id,
user: trigger._id + '/user/' + message.author_id,
}
let timeoutPass = true;
// Global
if (trigger.timeout?.global && ruleTimeouts.has(timeoutKeys.global)) {
if (ruleTimeouts.get(timeoutKeys.global)! + (trigger.timeout.global * 1000) < Date.now()) {
ruleTimeouts.set(timeoutKeys.global, Date.now());
} else timeoutPass = false;
}
// Per Channel
if (trigger.timeout?.perChannel && ruleTimeouts.has(timeoutKeys.channel)) {
if (ruleTimeouts.get(timeoutKeys.channel)! + (trigger.timeout.perChannel * 1000) < Date.now()) {
ruleTimeouts.set(timeoutKeys.channel, Date.now());
} else timeoutPass = false;
}
// Per User
if (trigger.timeout?.perUser && ruleTimeouts.has(timeoutKeys.user)) {
if (ruleTimeouts.get(timeoutKeys.user)! + (trigger.timeout.perUser * 1000) < Date.now()) {
ruleTimeouts.set(timeoutKeys.user, Date.now());
} else timeoutPass = false;
}
if (!timeoutPass) return false;
/* User/bot filter comes last because we want to avoid fetching users if possible */
if (trigger.userFilter && trigger.userFilter != 'any') {
let user = message.author || await client.users.fetch(message.author_id);
if (trigger.userFilter == 'bot' && !user.bot) return false;
if (trigger.userFilter == 'user' && user.bot) return false;
}
return true;
}
export default messageContentTrigger;