automod stuff, mentionfilter & wordwatcher actions
This commit is contained in:
parent
d29107cced
commit
c252fd4a1f
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable require-unicode-regexp */
|
||||||
const { inspect } = require('util');
|
const { inspect } = require('util');
|
||||||
const similarity = require('similarity');
|
const similarity = require('similarity');
|
||||||
const { stripIndents } = require('common-tags');
|
const { stripIndents } = require('common-tags');
|
||||||
@ -14,6 +15,17 @@ const CONSTANTS = {
|
|||||||
KICK: Kick,
|
KICK: Kick,
|
||||||
SOFTBAN: Softban,
|
SOFTBAN: Softban,
|
||||||
BAN: Ban
|
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)],
|
['messageUpdate', this.filterLinks.bind(this)],
|
||||||
['messageCreate', this.filterInvites.bind(this)],
|
['messageCreate', this.filterInvites.bind(this)],
|
||||||
['messageUpdate', 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.whitelist = new BinaryTree(this.client, FilterPresets.whitelist);
|
||||||
this.executing = {};
|
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) {
|
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: what gets shown in the message logs |
|
||||||
// _matcher: locally used variable for which word in the list triggered it |
|
// _matcher: locally used variable for which word in the list triggered it |
|
||||||
// type: which detection type matched 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);
|
const words = content.toLowerCase().replace(/[,?.!]/gu, '').split(' ').filter((elem) => elem.length);
|
||||||
// Remove any potential bypass characters
|
// Remove any potential bypass characters
|
||||||
//const _words = words.map((word) => word.replace(/[.'*_?+"#%&=-]/gu, ''));
|
//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
|
// 5. Remove message, inline response and add a reason to msg object
|
||||||
if (!filterResult.matched) return;
|
if (!filterResult.matched) return;
|
||||||
msg.filtered = filterResult;
|
msg.filtered = filterResult;
|
||||||
|
filterResult.filter = 'word';
|
||||||
log += `\nFilter result: ${inspect(filterResult)}`;
|
log += `\nFilter result: ${inspect(filterResult)}`;
|
||||||
|
|
||||||
if (!silent) {
|
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 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 } });
|
//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(() => { /**/ });
|
res.delete().catch(() => { /**/ });
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(269));
|
||||||
|
|
||||||
// 6. Automated actions
|
// 6. Automated actions
|
||||||
if (actions.length) {
|
if (actions.length) {
|
||||||
@ -258,19 +280,16 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
return act.trigger === 'generic';
|
return act.trigger === 'generic';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!action) {
|
if (!action) {
|
||||||
this.logger.debug(log);
|
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');
|
this.logger.debug(log + '\nSanctioned');
|
||||||
|
await this._moderate(action, wrapper, channel, member, wrapper.format('W_FILTER_ACTION'), filterResult);
|
||||||
filterResult.filter = 'word';
|
|
||||||
await this._moderate(action, wrapper, channel, member, wrapper.format('W_FILTER_ACTION'), filterResult, message);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.client.rateLimiter.queueDelete(msg.channel, msg).catch(catcher(269));
|
|
||||||
this.logger.debug(log);
|
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 member = message.member || await guild.members.fetch(author.id).catch();
|
||||||
const settings = await wrapper.settings();
|
const settings = await wrapper.settings();
|
||||||
const { wordwatcher: setting } = 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) || [];
|
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;
|
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;
|
let match = null;
|
||||||
|
|
||||||
for (const reg of words) {
|
for (const reg of words) {
|
||||||
|
|
||||||
match = content.match(new RegExp(`(?:^|\\s)(${reg})`, 'iu'));
|
match = content.match(new RegExp(`(?:^|\\s)(${reg})`, 'iu'));
|
||||||
|
|
||||||
if (match) break;
|
if (match) break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!match) return;
|
if (!match) return;
|
||||||
@ -315,7 +331,7 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
const embed = {
|
const embed = {
|
||||||
title: `⚠️ Word trigger in **#${channel.name}**`,
|
title: `⚠️ Word trigger in **#${channel.name}**`,
|
||||||
description: stripIndents`
|
description: stripIndents`
|
||||||
**[Jump to message](${msg.link})**
|
**[Jump to message](${msg.url})**
|
||||||
`, // ** User:** <@${ author.id }>
|
`, // ** User:** <@${ author.id }>
|
||||||
color: 15120384,
|
color: 15120384,
|
||||||
fields: context.reverse().reduce((acc, val) => {
|
fields: context.reverse().reduce((acc, val) => {
|
||||||
@ -333,10 +349,107 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Add action buttons
|
// 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);
|
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) {
|
async filterLinks(message, edited) {
|
||||||
@ -391,6 +504,8 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
|
|
||||||
if (!remove) return;
|
if (!remove) return;
|
||||||
msg.filtered = filterResult;
|
msg.filtered = filterResult;
|
||||||
|
filterResult.filter = 'link';
|
||||||
|
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
const res = await this.client.rateLimiter.limitSend(msg.channel, wrapper.format('L_FILTER_DELETE', { user: author.id }), undefined, 'linkFilter');
|
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 } });
|
//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);
|
if (!action) this.client.rateLimiter.queueDelete(msg.channel, msg);
|
||||||
|
|
||||||
msg.filtered.sanctioned = true;
|
// msg.filtered.sanctioned = true;
|
||||||
this.client.rateLimiter.queueDelete(msg.channel, msg);
|
this.client.rateLimiter.queueDelete(msg.channel, msg);
|
||||||
//msg.delete();
|
//msg.delete();
|
||||||
|
|
||||||
filterResult.filter = 'link';
|
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();
|
} else this.client.rateLimiter.queueDelete(msg.channel, msg); //msg.delete();
|
||||||
|
|
||||||
@ -443,11 +558,10 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
const { content } = msg;
|
const { content } = msg;
|
||||||
if (!content) return;
|
if (!content) return;
|
||||||
|
|
||||||
const reg = /((discord)?\s?\.?\s?gg\s?|discord(app)?\.com\/invite)\/\s?(?<code>[a-z0-9]+)/iu;
|
const match = content.match(this.regex.invite);
|
||||||
const match = content.match(reg);
|
|
||||||
if (!match) return;
|
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
|
if (!result) { // Doesn't resolve to the origin server
|
||||||
|
|
||||||
let action = null;
|
let action = null;
|
||||||
@ -455,7 +569,8 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
|
|
||||||
msg.filtered = {
|
msg.filtered = {
|
||||||
match: match[0],
|
match: match[0],
|
||||||
matcher: 'invites'
|
matcher: 'invites',
|
||||||
|
filter: 'invite'
|
||||||
};
|
};
|
||||||
if (!action) return this.client.rateLimiter.queueDelete(msg.channel, msg); //msg.delete();
|
if (!action) return this.client.rateLimiter.queueDelete(msg.channel, msg); //msg.delete();
|
||||||
if (!silent) {
|
if (!silent) {
|
||||||
@ -465,10 +580,10 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
res.delete().catch(() => { /**/ });
|
res.delete().catch(() => { /**/ });
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
msg.filtered.sactioned = true;
|
// msg.filtered.sactioned = true;
|
||||||
this.client.rateLimiter.queueDelete(msg.channel, msg);
|
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 member = message.member || await guild.members.fetch(author.id).catch();
|
||||||
const settings = await wrapper.settings();
|
const settings = await wrapper.settings();
|
||||||
const { mentionfilter: setting, modpoints } = settings;
|
const { mentionfilter: setting } = settings;
|
||||||
const { bypass, ignore, enabled, silent, unique, mentions, actions } = setting;
|
const { bypass, ignore, enabled, silent, unique, limit, actions } = setting;
|
||||||
const roles = member?.roles.cache.map((r) => r.id) || [];
|
const roles = member?.roles.cache.map((r) => r.id) || [];
|
||||||
|
|
||||||
if (!enabled || roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
if (!enabled || roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
||||||
|
|
||||||
const reg = /<@!?[0-9]{18,22}>/gu;
|
|
||||||
const { content } = message;
|
const { content } = message;
|
||||||
if (!content) return;
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user