cleanup + linkfilter improvements
This commit is contained in:
parent
c140073d7a
commit
1dfc4a7e6b
@ -44,4 +44,9 @@ Missing target member for action **{actionType}**.
|
|||||||
|
|
||||||
[LINKFILTER_WARN]
|
[LINKFILTER_WARN]
|
||||||
Failed to validate domain `{domain}`.
|
Failed to validate domain `{domain}`.
|
||||||
|
The domain has a valid top level domain (e.g. .com, .net) but failed a DNS check.
|
||||||
|
|
||||||
|
{emoji_warning} **Warning**
|
||||||
|
Exercise caution when clicking on unknown links. Adviced to run the link through a link checker such as urlscan.io or similar.
|
||||||
|
Full link: ||{link}||
|
||||||
If this is a valid domain, add it to the greylist to ensure it is filtered.
|
If this is a valid domain, add it to the greylist to ensure it is filtered.
|
@ -1,4 +1,5 @@
|
|||||||
const { EventEmitter } = require('events');
|
const { EventEmitter } = require('events');
|
||||||
|
const { inspect } = require('util');
|
||||||
|
|
||||||
const SlashCommandManager = require('./rest/SlashCommandManager.js');
|
const SlashCommandManager = require('./rest/SlashCommandManager.js');
|
||||||
const { ShardingManager } = require('./shard/');
|
const { ShardingManager } = require('./shard/');
|
||||||
@ -6,6 +7,10 @@ const Logger = require('./Logger.js');
|
|||||||
const Metrics = require('./Metrics.js');
|
const Metrics = require('./Metrics.js');
|
||||||
const ApiClientUtil = require('./ApiClientUtil.js');
|
const ApiClientUtil = require('./ApiClientUtil.js');
|
||||||
|
|
||||||
|
// Available for evals
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const ClientUtils = require('./ClientUtils.js');
|
||||||
|
|
||||||
class BaseClient extends EventEmitter {
|
class BaseClient extends EventEmitter {
|
||||||
|
|
||||||
constructor(options, version) {
|
constructor(options, version) {
|
||||||
@ -85,7 +90,7 @@ class BaseClient extends EventEmitter {
|
|||||||
* @memberof Manager
|
* @memberof Manager
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async eval(shard, { script }) {
|
async eval(shard, { script, debug }) {
|
||||||
|
|
||||||
this.logger.info(`Incoming manager eval from shard ${shard.id}:\n${script}`);
|
this.logger.info(`Incoming manager eval from shard ${shard.id}:\n${script}`);
|
||||||
let result = null,
|
let result = null,
|
||||||
@ -95,6 +100,7 @@ class BaseClient extends EventEmitter {
|
|||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
result = await eval(script);
|
result = await eval(script);
|
||||||
//if(typeof result !== 'string') result = inspect(result);
|
//if(typeof result !== 'string') result = inspect(result);
|
||||||
|
if(debug) this.logger.debug(`Eval result: ${inspect(result)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err.stack || err;
|
error = err.stack || err;
|
||||||
}
|
}
|
||||||
|
23
src/middleware/ClientUtils.js
Normal file
23
src/middleware/ClientUtils.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const { Util } = require("../utilities");
|
||||||
|
|
||||||
|
// Class to house various functions the clients use on the main process
|
||||||
|
class ClientUtils {
|
||||||
|
|
||||||
|
static cache = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
throw new Error('Static class, may not be instantiated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchTlds(force = false) {
|
||||||
|
if (this.cache.tlds && !force) return this.cache.tlds;
|
||||||
|
const buffer = await Util.downloadAsBuffer('https://data.iana.org/TLD/tlds-alpha-by-domain.txt');
|
||||||
|
const list = buffer.toString().split('\n').filter((str) => !str.startsWith('#')).map((str) => str.toLowerCase());
|
||||||
|
|
||||||
|
this.cache.tlds = list;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ClientUtils;
|
@ -85,6 +85,7 @@ class Registry {
|
|||||||
module.components.set(component.resolveable, component);
|
module.components.set(component.resolveable, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (component.initialise && typeof component.initialise === 'function') await component.initialise();
|
||||||
this.components.set(component.resolveable, component);
|
this.components.set(component.resolveable, component);
|
||||||
if(!silent) this.client.emit('componentUpdate', { component, type: 'LOAD' });
|
if(!silent) this.client.emit('componentUpdate', { component, type: 'LOAD' });
|
||||||
return component;
|
return component;
|
||||||
|
@ -29,9 +29,6 @@ const CONSTANTS = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const linkRegG = /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/\S*)?/iug;
|
|
||||||
const linkReg = /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/\S*)?/iu;
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// Clean up commented out code once testing of new code is done
|
// Clean up commented out code once testing of new code is done
|
||||||
// Implement missing automod features -- done
|
// Implement missing automod features -- done
|
||||||
@ -60,17 +57,38 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
['interactionCreate', this.flagAction.bind(this)]
|
['interactionCreate', this.flagAction.bind(this)]
|
||||||
];
|
];
|
||||||
|
|
||||||
this.whitelist = new BinaryTree(this.client, FilterPresets.whitelist);
|
this.topLevelDomains = null;
|
||||||
|
// Moved to FilterUtil
|
||||||
|
// this.whitelist = new BinaryTree(this.client, FilterPresets.whitelist);
|
||||||
this.executing = {};
|
this.executing = {};
|
||||||
|
|
||||||
this.regex = {
|
this.regex = {
|
||||||
invite: /((discord)?\s*\.?\s*gg\s*|discord(app)?\.com\/invite)\/\s?(?<code>[a-z0-9]+)/i,
|
invite: /((discord)?\s*\.?\s*gg\s*|discord(app)?\.com\/invite)\/\s?(?<code>[a-z0-9]+)/i,
|
||||||
|
linkRegG: /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/\S*)?/iug,
|
||||||
|
linkReg: /(https?:\/\/(www\.)?)?(?<domain>([a-z0-9-]{1,63}\.)?([a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})(\.[a-z0-9-]{2,63})?)(\/\S*)?/iu,
|
||||||
mention: /<@!?(?<id>[0-9]{18,22})>/,
|
mention: /<@!?(?<id>[0-9]{18,22})>/,
|
||||||
mentionG: /<@!?(?<id>[0-9]{18,22})>/g,
|
mentionG: /<@!?(?<id>[0-9]{18,22})>/g,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initialise() {
|
||||||
|
|
||||||
|
// Fetch a list of TLDs from iana
|
||||||
|
const tldList = await this.client.managerEval(`
|
||||||
|
(() => {
|
||||||
|
return ClientUtils.fetchTlds()
|
||||||
|
})()
|
||||||
|
`).catch(this.logger.error.bind(this.logger));
|
||||||
|
|
||||||
|
const middlePoint = Math.floor(tldList.length / 2);
|
||||||
|
const [midEntry] = tldList.splice(middlePoint, 1);
|
||||||
|
tldList.splice(0, 0, midEntry);
|
||||||
|
this.topLevelDomains = new BinaryTree(this.client, tldList);
|
||||||
|
this.topLevelDomains.add('onion');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async _moderate(action, wrapper, channel, member, reason, filterResult, moderator) {
|
async _moderate(action, wrapper, channel, member, reason, filterResult, moderator) {
|
||||||
|
|
||||||
// Prevent simultaneous execution of the same filter on the same user when spamming
|
// Prevent simultaneous execution of the same filter on the same user when spamming
|
||||||
@ -150,29 +168,11 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
// 2. Filter explicit - no bypass checking (unless you count normalising the text, i.e. emoji letters => normal letters)
|
// 2. Filter explicit - no bypass checking (unless you count normalising the text, i.e. emoji letters => normal letters)
|
||||||
if (explicit.length && !filterResult.matched) {
|
if (explicit.length && !filterResult.matched) {
|
||||||
|
|
||||||
//filterResult = FilterUtil.filterExplicit(words, explicit);
|
|
||||||
// if(filterResult)
|
|
||||||
|
|
||||||
const result = FilterUtil.filterExplicit(words, explicit);
|
const result = FilterUtil.filterExplicit(words, explicit);
|
||||||
if (result) {
|
if (result) {
|
||||||
log += `\nMessage matched with "${result.match}" in the explicit list.\nFull content: ${content}`;
|
log += `\nMessage matched with "${result.match}" in the explicit list.\nFull content: ${content}`;
|
||||||
filterResult = result;
|
filterResult = result;
|
||||||
}
|
}
|
||||||
// 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)) {
|
|
||||||
// log += `\nMessage matched with "${word}" in the explicit list.\nFull content: ${content}`;
|
|
||||||
// filterResult = {
|
|
||||||
// match: word,
|
|
||||||
// matched: true,
|
|
||||||
// matcher: 'explicit',
|
|
||||||
// _matcher: word,
|
|
||||||
// type: 'explicit'
|
|
||||||
// };
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,40 +184,7 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
log += `\nMessage matched with "${result.matcher}" in the regex list.\nMatch: ${result._raw}, Full word: ${result.match}\nFull content: ${content}`;
|
log += `\nMessage matched with "${result.matcher}" in the regex list.\nMatch: ${result._raw}, Full word: ${result.match}\nFull content: ${content}`;
|
||||||
filterResult = result;
|
filterResult = result;
|
||||||
}
|
}
|
||||||
// for (const reg of regex) {
|
|
||||||
|
|
||||||
// const match = content.toLowerCase().match(new RegExp(`(?:^|\\s)(${reg})`, 'iu')); // (?:^|\\s) |un
|
|
||||||
|
|
||||||
// if (match) {
|
|
||||||
// //log += `\next reg: ${tmp}`;
|
|
||||||
// const fullWord = words.find((word) => word.includes(match[1]));
|
|
||||||
|
|
||||||
// let inWL = false;
|
|
||||||
// try { // This is for debugging only
|
|
||||||
// inWL = this.whitelist.find(fullWord);
|
|
||||||
// } catch (err) {
|
|
||||||
// this.logger.error(`Whitelist.find error: ${fullWord} ${match[1]} ${words}`);
|
|
||||||
// }
|
|
||||||
// if (inWL || whitelist.some((word) => word === fullWord)) continue;
|
|
||||||
|
|
||||||
// const diff = Math.abs(fullWord.length - match[0].length);
|
|
||||||
// if (diff > 3) {
|
|
||||||
// this.logger.debug(`Match length diff: ${diff} MATCH: ${match[0]} FULL: ${fullWord}`);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// log += `\nMessage matched with "${reg}" in the regex list.\nMatch: ${match[0]}, Full word: ${fullWord}\nFull content: ${content}`;
|
|
||||||
// filterResult = {
|
|
||||||
// match: fullWord,
|
|
||||||
// matched: true,
|
|
||||||
// _matcher: match[1].toLowerCase(),
|
|
||||||
// matcher: `Regex: __${reg}__`,
|
|
||||||
// type: 'regex'
|
|
||||||
// };
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Filter fuzzy
|
// 4. Filter fuzzy
|
||||||
@ -228,52 +195,7 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
filterResult = result;
|
filterResult = result;
|
||||||
log += `\nMessage matched with "${result._matcher}" in fuzzy.\nMatched word: ${result.match}\nFull content: ${content}\nSimilarity: ${result._sim}\nThreshold: ${result._threshold}`;
|
log += `\nMessage matched with "${result._matcher}" in fuzzy.\nMatched word: ${result.match}\nFull content: ${content}\nSimilarity: ${result._sim}\nThreshold: ${result._threshold}`;
|
||||||
}
|
}
|
||||||
// const text = words.join('').replace(/\s/u, '');
|
|
||||||
// let threshold = (0.93 - 0.133 * Math.log(text.length)).toFixed(3);
|
|
||||||
// if (threshold < 0.6) threshold = 0.6;
|
|
||||||
// else if (threshold > 0.9) threshold = 0.9;
|
|
||||||
|
|
||||||
// outer:
|
|
||||||
// for (const _word of fuzzy) {
|
|
||||||
|
|
||||||
// for (const word of words) {
|
|
||||||
// const sim = similarity(word, _word);
|
|
||||||
// let threshold = (0.93 - 0.133 * Math.log(word.length)).toFixed(3);
|
|
||||||
// if (threshold < 0.6) threshold = 0.6;
|
|
||||||
// else if (threshold > 0.9) threshold = 0.9;
|
|
||||||
// if (sim >= threshold && Math.abs(_word.length - word.length) <= 2) {
|
|
||||||
// if (this.whitelist.find(word) || whitelist.some((w) => w === word) && sim < 1) continue;
|
|
||||||
// log += `\nMessage matched with "${_word}" in fuzzy.\nMatched word: ${word}\nFull content: ${content}\nSimilarity: ${sim}\nThreshold: ${threshold}`;
|
|
||||||
// filterResult = {
|
|
||||||
// match: word,
|
|
||||||
// matched: true,
|
|
||||||
// _matcher: _word,
|
|
||||||
// matcher: `fuzzy [\`${_word}\`, \`${sim}\`, \`${threshold}\`]`,
|
|
||||||
// type: 'fuzzy'
|
|
||||||
// };
|
|
||||||
// break outer;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const sim = similarity(text, _word);
|
|
||||||
// if (sim >= threshold && Math.abs(_word.length - text.length) <= 2) {
|
|
||||||
// if (this.whitelist.find(text) || whitelist.some((w) => w === text) && sim < 1) continue;
|
|
||||||
// log += `\nMessage matched with "${_word}" in fuzzy.\nMatched word: ${text}\nFull content: ${content}\nSimilarity: ${sim}\nThreshold: ${threshold}`;
|
|
||||||
// filterResult = {
|
|
||||||
// match: text,
|
|
||||||
// matched: true,
|
|
||||||
// _matcher: _word,
|
|
||||||
// matcher: `fuzzy [\`${_word}\`, \`${sim}\`, \`${threshold}\`]`,
|
|
||||||
// type: 'fuzzy'
|
|
||||||
// };
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //this.client.logger.debug(`Message did not match with "${_word}" in fuzzy.\nFull content: ${content}\nSimilarity: ${sim}\nThreshold: ${threshold}`);
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Remove message, inline response and add a reason to msg object
|
// 5. Remove message, inline response and add a reason to msg object
|
||||||
@ -510,15 +432,15 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
const msg = edited || message;
|
const msg = edited || message;
|
||||||
if (!msg.content) return;
|
if (!msg.content) return;
|
||||||
const content = msg.content.split('').join(''); //Copy the string...
|
const content = msg.content.split('').join(''); //Copy the string...
|
||||||
let matches = content.match(linkRegG);
|
let matches = content.match(this.regex.linkRegG);
|
||||||
if (!matches) matches = content.replace(/\s/u, '').match(linkRegG);
|
if (!matches) matches = content.replace(/\s/u, '').match(this.regex.linkRegG);
|
||||||
if (!matches) return;
|
if (!matches) return;
|
||||||
let remove = false;
|
let remove = false;
|
||||||
const filterResult = {};
|
const filterResult = {};
|
||||||
let log = `${guild.name} Link filter debug:`;
|
let log = `${guild.name} Link filter debug:`;
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
const { domain } = match.match(linkReg).groups;
|
const { domain } = match.match(this.regex.linkReg).groups;
|
||||||
log += `\nMatched link ${match}: `;
|
log += `\nMatched link ${match}: `;
|
||||||
|
|
||||||
const predicate = (dom) => {
|
const predicate = (dom) => {
|
||||||
@ -543,9 +465,13 @@ module.exports = class AutoModeration extends Observer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parts = domain.split('.');
|
||||||
|
const validTld = this.topLevelDomains.find(parts[parts.length - 1]);
|
||||||
|
// console.log(parts, validTld);
|
||||||
|
if (!validTld) continue;
|
||||||
const valid = await resolver.validateDomain(domain);
|
const valid = await resolver.validateDomain(domain);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
this.client.emit('linkFilterWarn', { guild: wrapper, message: wrapper.format('LINKFILTER_WARN', { domain }) });
|
this.client.emit('linkFilterWarn', { guild: wrapper, message: wrapper.format('LINKFILTER_WARN', { domain, link: match }) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user