automod stuff, mentionfilter & wordwatcher actions

This commit is contained in:
Erik 2022-03-28 01:44:05 +03:00
parent d29107cced
commit c252fd4a1f
Signed by: Navy.gif
GPG Key ID: 811EC0CD80E7E5FB

View File

@ -1,3 +1,4 @@
/* eslint-disable require-unicode-regexp */
const { inspect } = require('util');
const similarity = require('similarity');
const { stripIndents } = require('common-tags');
@ -14,6 +15,17 @@ const CONSTANTS = {
KICK: Kick,
SOFTBAN: Softban,
BAN: Ban
},
ButtonStyles: {
BAN: 'DANGER',
},
Permissions: {
WARN: 'KICK_MEMBERS',
MUTE: 'MODERATE_MEMBERS',
KICK: 'KICK_MEMBERS',
SOFTBAN: 'KICK_MEMBERS',
BAN: 'BAN_MEMBERS',
DELETE: 'MANAGE_MESSAGES'
}
};
@ -41,12 +53,19 @@ module.exports = class AutoModeration extends Observer {
['messageUpdate', this.filterLinks.bind(this)],
['messageCreate', this.filterInvites.bind(this)],
['messageUpdate', this.filterInvites.bind(this)],
['messageCreate', this.filterMentions.bind(this)]
['messageCreate', this.filterMentions.bind(this)],
['interactionCreate', this.flagAction.bind(this)]
];
this.whitelist = new BinaryTree(this.client, FilterPresets.whitelist);
this.executing = {};
this.regex = {
invite: /((discord)?\s*\.?\s*gg\s*|discord(app)?\.com\/invite)\/\s?(?<code>[a-z0-9]+)/i,
mention: /<@!?(?<id>[0-9]{18,22})>/,
mentionG: /<@!?(?<id>[0-9]{18,22})>/g,
};
}
async _moderate(action, wrapper, channel, member, reason, filterResult) {
@ -118,7 +137,7 @@ module.exports = class AutoModeration extends Observer {
// matcher: what gets shown in the message logs |
// _matcher: locally used variable for which word in the list triggered it |
// type: which detection type matched it
let filterResult = { filter: 'word', match: null, matched: false, matcher: null, _matcher: null, preset: false };
let filterResult = { filter: 'word', match: null, matched: false, matcher: null, _matcher: null };
const words = content.toLowerCase().replace(/[,?.!]/gu, '').split(' ').filter((elem) => elem.length);
// Remove any potential bypass characters
//const _words = words.map((word) => word.replace(/[.'*_?+"#%&=-]/gu, ''));
@ -235,7 +254,9 @@ module.exports = class AutoModeration extends Observer {
// 5. Remove message, inline response and add a reason to msg object
if (!filterResult.matched) return;
msg.filtered = filterResult;
filterResult.filter = 'word';
log += `\nFilter result: ${inspect(filterResult)}`;
if (!silent) {
const res = await this.client.rateLimiter.limitSend(msg.channel, wrapper.format('W_FILTER_DELETE', { user: author.id }), undefined, 'wordFilter').catch(catcher(255));
//const res = await msg.formattedRespond('W_FILTER_DELETE', { params: { user: author.id } });
@ -244,6 +265,7 @@ module.exports = class AutoModeration extends Observer {
res.delete().catch(() => { /**/ });
}, 10000);
}
this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(269));
// 6. Automated actions
if (actions.length) {
@ -258,19 +280,16 @@ module.exports = class AutoModeration extends Observer {
return act.trigger === 'generic';
});
if (!action) {
this.logger.debug(log);
return this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(275));
return;
}
this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(279));
this.logger.debug(log + '\nSanctioned');
filterResult.filter = 'word';
await this._moderate(action, wrapper, channel, member, wrapper.format('W_FILTER_ACTION'), filterResult, message);
await this._moderate(action, wrapper, channel, member, wrapper.format('W_FILTER_ACTION'), filterResult);
} else {
this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(269));
this.logger.debug(log);
}
@ -284,7 +303,7 @@ module.exports = class AutoModeration extends Observer {
const member = message.member || await guild.members.fetch(author.id).catch();
const settings = await wrapper.settings();
const { wordwatcher: setting } = settings;
const { words, bypass, ignore, channel: _logChannel } = setting;
const { words, bypass, ignore, channel: _logChannel, actions } = 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;
@ -302,11 +321,8 @@ module.exports = class AutoModeration extends Observer {
let match = null;
for (const reg of words) {
match = content.match(new RegExp(`(?:^|\\s)(${reg})`, 'iu'));
if (match) break;
}
if (!match) return;
@ -315,7 +331,7 @@ module.exports = class AutoModeration extends Observer {
const embed = {
title: `⚠️ Word trigger in **#${channel.name}**`,
description: stripIndents`
**[Jump to message](${msg.link})**
**[Jump to message](${msg.url})**
`, // ** User:** <@${ author.id }>
color: 15120384,
fields: context.reverse().reduce((acc, val) => {
@ -333,12 +349,109 @@ module.exports = class AutoModeration extends Observer {
};
// TODO: Add action buttons
const sent = await logChannel.send({ embeds: [embed] }).catch((err) => {
const components = [];
for (const action of actions) {
components.push({
type: 'BUTTON',
label: action.type,
customId: `WORDWATCHER_${action.trigger}`,
style: CONSTANTS.ButtonStyles[action.type] || 'PRIMARY'
});
}
const actionRow = components.length ? [{
type: 'ACTION_ROW',
components
}] : undefined;
const sent = await logChannel.send({
embeds: [embed], components: actionRow }).catch((err) => {
this.logger.error('Error in message flag:\n' + err.stack);
});
await this.client.storageManager.mongodb.wordwatcher.insertOne({
message: sent.id,
target: msg.id,
channel: msg.channel.id,
timestamp: Date.now()
});
}
async flagAction(interaction) {
if (!interaction.isButton() || !interaction.inGuild()) return;
const { guild, message, customId: _actionType, member: moderator } = interaction;
const { components } = message;
const [, actionType] = _actionType.split('_');
const { permissions } = this.client;
const settings = await guild.settings();
const { wordwatcher } = settings;
const { actions } = wordwatcher;
await interaction.deferUpdate();
const fail = (index, opts) => {
this.client.emit('wordWatcherError', {
warning: true, guild,
message: guild.format(index, opts)
});
components[0].components.find((comp) => comp.customId === _actionType).style = 'DANGER';
return message.edit({ components });
};
// TODO: finish perm checks, need to figure out a workaround for `command:delete`
//actionType === 'DELETE' ?
// { error: false } :
const inhibitor =
await permissions.execute(
interaction,
{ resolveable: `command:${actionType.toLowerCase()}` },
[CONSTANTS.Permissions[actionType]]
);
console.log(inhibitor);
// return;
const log = await this.client.storageManager.mongodb.wordwatcher.findOne({
message: message.id
});
if (!log) return fail('WORDWATCHER_MISSING_LOG');
const action = actions.find((act) => act.trigger === actionType);
if (!action) return fail('WORDWATCHER_MISSING_ACTION', { actionType });
const targetChannel = await guild.resolveChannel(log.channel).catch(() => null);
if (!targetChannel) return fail('WORDWATCHER_MISSING_CHANNEL');
const filterObj = {
filter: 'wordwatcher',
action: action.type
};
const msg = await targetChannel.messages.fetch(log.target).catch(() => null);
if (msg) {
await msg.delete();
msg.filtered = filterObj;
}
await this.client.storageManager.mongodb.wordwatcher.deleteOne({
message: message.id
});
let success = false;
if (action.type !== 'DELETE') {
const member = msg.member || await guild.members.fetch(msg.author.id).catch(() => null);
if (member)
success = await this._moderate(action, guild, targetChannel, member, guild.format('WORDWATCHER_ACTION'), filterObj);
else
this.client.emit('wordWatcherError', { warning: true, guild, message: guild.format('WORDWATCHER_MISSING_MEMBER', { actionType }) });
}
components[0].components.find((comp) => comp.customId === _actionType).style = success ? 'SUCCESS' : 'SECONDARY';
await message.edit({ components }).catch(() => null);
}
async filterLinks(message, edited) {
const { guild, author, channel, guildWrapper: wrapper } = message;
@ -391,6 +504,8 @@ module.exports = class AutoModeration extends Observer {
if (!remove) return;
msg.filtered = filterResult;
filterResult.filter = 'link';
if (!silent) {
const res = await this.client.rateLimiter.limitSend(msg.channel, wrapper.format('L_FILTER_DELETE', { user: author.id }), undefined, 'linkFilter');
//const res = await msg.formattedRespond(`L_FILTER_DELETE`, { params: { user: author.id } });
@ -414,12 +529,12 @@ module.exports = class AutoModeration extends Observer {
if (!action) this.client.rateLimiter.queueDelete(msg.channel, msg);
msg.filtered.sanctioned = true;
// msg.filtered.sanctioned = true;
this.client.rateLimiter.queueDelete(msg.channel, msg);
//msg.delete();
filterResult.filter = 'link';
this._moderate(action, guild, channel, member, wrapper.format('L_FILTER_ACTION', { domain: filterResult.match }), filterResult, message);
await this._moderate(action, guild, channel, member, wrapper.format('L_FILTER_ACTION', { domain: filterResult.match }), filterResult, message);
} else this.client.rateLimiter.queueDelete(msg.channel, msg); //msg.delete();
@ -443,11 +558,10 @@ module.exports = class AutoModeration extends Observer {
const { content } = msg;
if (!content) return;
const reg = /((discord)?\s?\.?\s?gg\s?|discord(app)?\.com\/invite)\/\s?(?<code>[a-z0-9]+)/iu;
const match = content.match(reg);
const match = content.match(this.regex.invite);
if (!match) return;
const result = await guild.checkInvite(match.groups.code);
const result = await wrapper.checkInvite(match.groups.code);
if (!result) { // Doesn't resolve to the origin server
let action = null;
@ -455,7 +569,8 @@ module.exports = class AutoModeration extends Observer {
msg.filtered = {
match: match[0],
matcher: 'invites'
matcher: 'invites',
filter: 'invite'
};
if (!action) return this.client.rateLimiter.queueDelete(msg.channel, msg); //msg.delete();
if (!silent) {
@ -465,10 +580,10 @@ module.exports = class AutoModeration extends Observer {
res.delete().catch(() => { /**/ });
}, 10000);
}
msg.filtered.sactioned = true;
// msg.filtered.sactioned = true;
this.client.rateLimiter.queueDelete(msg.channel, msg);
this._moderate(action, guild, channel, member, wrapper.format('I_FILTER_ACTION'), { filtered: msg.filtered }, message);
await this._moderate(action, guild, channel, member, wrapper.format('I_FILTER_ACTION'), { filtered: msg.filtered }, message);
}
@ -481,16 +596,51 @@ module.exports = class AutoModeration extends Observer {
const member = message.member || await guild.members.fetch(author.id).catch();
const settings = await wrapper.settings();
const { mentionfilter: setting, modpoints } = settings;
const { bypass, ignore, enabled, silent, unique, mentions, actions } = setting;
const { mentionfilter: setting } = settings;
const { bypass, ignore, enabled, silent, unique, limit, actions } = setting;
const roles = member?.roles.cache.map((r) => r.id) || [];
if (!enabled || roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
const reg = /<@!?[0-9]{18,22}>/gu;
const { content } = message;
if (!content) return;
//const mentions = content.match(reg);
const matches = content.match(this.regex.mentionG);
if (!matches) return;
let ids = matches.map((match) => match.match(this.regex.mention).groups.id);
if (unique) {
const set = new Set(ids);
ids = [];
for (const id of set.values()) ids.push(id);
}
if (ids.length < limit) return;
if (!silent) {
const res = await this.client.rateLimiter.limitSend(channel, wrapper.format('M_FILTER_DELETE', { user: author.id }), undefined, 'mentionFilter');
setTimeout(() => {
res.delete().catch(() => { /**/ });
}, 10000);
}
this.client.rateLimiter.queueDelete(channel, message);
const filterResult = {
filter: 'mention',
amount: ids.length
};
message.filtered = filterResult;
if (actions.length) {
const action = actions.find((act) => {
return act.trigger === 'generic';
});
if (!action) return;
await this._moderate(action, wrapper, channel, member, wrapper.format('M_FILTER_ACTION'), filterResult);
}
}