98 lines
3.7 KiB
TypeScript
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;
|