wordfilter improvements, word watcher

This commit is contained in:
Erik 2020-11-12 00:59:27 +02:00
parent 52e4eab9d6
commit 42327f6485

View File

@ -3,6 +3,7 @@ const similarity = require('similarity');
const { Observer, BinaryTree } = require('../../../interfaces');
const { FilterUtil, FilterPresets } = require('../../../../util');
const { Warn, Mute, Kick, Softban, Ban } = require('../../../moderation/infractions');
const { stripIndents } = require('common-tags');
const CONSTANTS = {
Infractions: {
@ -26,6 +27,8 @@ module.exports = class AutoModeration extends Observer {
this.hooks = [
['message', this.filterWords.bind(this)],
['messageUpdate', this.filterWords.bind(this)],
['message', this.flagMessages.bind(this)],
['messageUpdate', this.flagMessages.bind(this)],
['message', this.filterLinks.bind(this)],
['messageUpdate', this.filterLinks.bind(this)],
['message', this.filterInvites.bind(this)],
@ -45,8 +48,8 @@ module.exports = class AutoModeration extends Observer {
const member = message.member || await guild.members.fetch(author.id).catch();
const settings = await guild.settings();
const { wordFilter: setting, moderationPoints } = settings;
const { bypass, ignore, enabled, silent, explicit, fuzzy, tokenized, whitelist, actions, presets } = setting;
const roles = member.roles.cache.map((r) => r.id);
const { bypass, ignore, enabled, silent, explicit, fuzzy, regex, whitelist, actions } = setting;
const roles = member?.roles.cache.map((r) => r.id) || [];
if (!enabled || roles.some((r) => bypass.includes(r.id)) || ignore.includes(channel.id)) return;
@ -60,38 +63,41 @@ module.exports = class AutoModeration extends Observer {
// matched: did it match at all ? |
// matcher: what gets shown in the message logs |
// _matcher: locally used variable for which word in the list triggered it |
// filter: which filter list was used
// type: which detection type matched it
let filterResult = { match: null, matched: false, matcher: null, _matcher: null, preset: false, filter: null };
let filterResult = { match: null, matched: false, matcher: null, _matcher: null, preset: false };
const words = content.toLowerCase().split(' ').filter((elem) => elem.length);
// Remove any potential bypass characters
const _words = words.map((word) => word.replace(/[.'*_?+"#%&=-]/gu, ''));
// 1. Filter for preset lists
if (presets.length) {
for (const preset of presets) {
// CHANGED BEHAVIOUR OF PRESETS, THEY NOW USE THE REGEX PART OF THE FILTER
// if (presets.length) {
// for (const preset of presets) {
const text = _words.join('').replace(/\s/u, ''); //Also check for spaced out words, ex "f u c k"
//Combine array of presets to one expression
const regex = new RegExp(`(${FilterPresets[preset].join(')|(')})`, 'ui');
const match = content.match(regex) || text.length === words.length ? text.match(regex) : null;
if (!match) continue;
this.client.logger.debug(`\nMessage matched with "${preset}" preset list.\nMatch: ${match[0]}\nFull content: ${content}`);
filterResult = {
match: match[0],
matched: true,
matcher: preset,
preset,
type: 'preset'
};
break;
// const text = _words.join('').replace(/\s/u, ''); //Also check for spaced out words, ex "f u c k"
// //Combine array of presets to one expression
// const _regex = new RegExp(`(${FilterPresets[preset].join(')|(')})`, 'ui');
// const match = content.match(_regex) || text.length === words.length ? text.match(_regex) : null;
// if (!match) continue;
// this.client.logger.debug(`\nMessage matched with "${preset}" preset list.\nMatch: ${match[0]}\nFull content: ${content}`);
// filterResult = {
// match: match[0],
// matched: true,
// matcher: preset,
// preset,
// type: 'preset'
// };
// break;
}
}
// }
// }
// 2. Filter explicit - no bypass checking (unless you count normalising the text, i.e. emoji letters => normal letters)
if (explicit.length && !filterResult.matched) {
//filterResult = FilterUtil.filterExplicit(words, explicit);
// if(filterResult)
for (const word of explicit) {
//Do it like this instead of regex so it doesn't match stuff like Scunthorpe with cunt
if (words.some((_word) => _word === word)) {
@ -103,6 +109,7 @@ module.exports = class AutoModeration extends Observer {
_matcher: word,
type: 'explicit'
};
break;
}
}
@ -156,20 +163,23 @@ module.exports = class AutoModeration extends Observer {
}
// 4. Filter tokenized
if (tokenized.length && !filterResult.matched) {
// 4. Filter regex
if (regex.length && !filterResult.matched) {
for (const word of tokenized) {
for (const reg of regex) {
const match = content.match(new RegExp(reg, 'iu'));
if (content.toLowerCase().includes(word)) {
this.client.logger.debug(`\nMessage matched with "${word}" in the tokenized list.\nFull content: ${content}`);
if (match) {
this.client.logger.debug(`\nMessage matched with "${reg}" in the regex list.\nMatch: ${match[0]}\nFull content: ${content}`);
filterResult = {
match: word,
match: match[0],
matched: true,
_matcher: word,
matcher: 'tokenized',
type: 'tokenized'
_matcher: reg,
matcher: `Regex: __${reg}__`,
type: 'regex'
};
break;
}
}
@ -201,6 +211,8 @@ module.exports = class AutoModeration extends Observer {
msg.filtered.sanctioned = true;
await msg.delete();
// NOTE: this will have to be changed whenever the moderation manager is finished and properly supports sth like this
this.client.moderationManager.handleInfraction(
CONSTANTS.Infractions[action.type],
{ // Hacky patched together message object with just the required stuff for modmanager to work
@ -221,8 +233,8 @@ module.exports = class AutoModeration extends Observer {
},
format: guild.format.bind(guild),
// eslint-disable-next-line no-empty-function
respond: () => {}
},
respond: () => { }
},
{
targets: [member],
reason: msg.format('W_FILTER_ACTION'),
@ -233,8 +245,61 @@ module.exports = class AutoModeration extends Observer {
}
);
} else msg.delete();
}
async flagMessages(message, edited) {
const { guild, author, channel } = message;
if (!guild || author.bot) return;
const member = message.member || await guild.members.fetch(author.id).catch();
const settings = await guild.settings();
const { wordWatcher: setting } = settings;
const { words, bypass, ignore, channel: _logChannel } = setting;
const roles = member?.roles.cache.map((r) => r.id) || [];
if (!_logChannel || words.length === 0 || roles.some((r) => bypass.includes(r.id)) || ignore.includes(channel.id)) return;
const logChannel = await guild.resolveChannel(_logChannel);
const msg = edited || message;
const content = FilterUtil.normalize(msg.cleanContent);
let match = null;
for (const reg of words) {
match = content.match(new RegExp(reg, 'iu'));
if (match) break;
}
if (!match) return;
const context = channel.messages.cache.sort((m1, m2) => m2.createdTimestamp - m1.createdTimestamp).first(5);
const embed = {
title: `⚠️ Word trigger in **#${channel.name}**`,
description: stripIndents`
**[Jump to message](${msg.link})**
`, // ** User:** <@${ author.id }>
color: 15120384,
fields: context.reverse().reduce((acc, val) => {
const text = val.content.length ? val.content.replace(match, '**__$&__**') : '**NO CONTENT**';
acc.push({
name: `${val.author.tag} (${val.author.id}) - ${val.id}`,
value: text.length < 1024 ? text : text.substring(0, 1013) + '...'
});
if (text.length > 1024) acc.push({
name: `\u200b`,
value: '...' + text.substring(1013, 2034)
});
return acc;
}, [])
};
logChannel.send({ embed });
}
async filterLinks(message, edited) {
@ -242,6 +307,20 @@ module.exports = class AutoModeration extends Observer {
}
async filterInvites(message, edited) {
const { guild, author, channel } = message;
if (!guild || author.bot) return;
const member = message.member || await guild.members.fetch(author.id).catch();
const settings = await guild.settings();
const { wordWatcher: setting } = settings;
const { words, bypass, ignore, channel: _logChannel } = setting;
const roles = member?.roles.cache.map((r) => r.id) || [];
if (!_logChannel || words.length === 0 || roles.some((r) => bypass.includes(r.id)) || ignore.includes(channel.id)) return;
const msg = edited || message;
}