forked from Galactic/galactic-bot
wordfilter improvements, word watcher
This commit is contained in:
parent
52e4eab9d6
commit
42327f6485
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user