From e586bfbda3fb4a9a4c66fdff33ef35828bbc58f1 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Sat, 30 Nov 2019 22:04:28 +0200 Subject: [PATCH] automod: add normalize and loose_matching trigger options --- backend/package-lock.json | 37 ++++++++++++++++++++++++++++++++++ backend/package.json | 1 + backend/src/plugins/Automod.ts | 29 +++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 711f6350..2f4e35e9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -4431,6 +4431,43 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "transliteration": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.1.7.tgz", + "integrity": "sha512-o3678GPmKKGqOBB+trAKzhBUjHddU18He2V8AKB1XuegaGJekO0xmfkkvbc9LCBat62nb7IH8z5/OJY+mNugkg==", + "requires": { + "yargs": "^14.0.0" + }, + "dependencies": { + "yargs": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", + "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "trim-newlines": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 0e7f252e..08e2a049 100644 --- a/backend/package.json +++ b/backend/package.json @@ -51,6 +51,7 @@ "seedrandom": "^3.0.1", "tlds": "^1.203.1", "tmp": "0.0.33", + "transliteration": "^2.1.7", "tsconfig-paths": "^3.9.0", "typeorm": "^0.2.14", "uuid": "^3.3.2" diff --git a/backend/src/plugins/Automod.ts b/backend/src/plugins/Automod.ts index 7964883f..55306c14 100644 --- a/backend/src/plugins/Automod.ts +++ b/backend/src/plugins/Automod.ts @@ -36,6 +36,7 @@ import { GuildLogs } from "../data/GuildLogs"; import { SavedMessage } from "../data/entities/SavedMessage"; import moment from "moment-timezone"; import { renderTemplate } from "../templateFormatter"; +import { transliterate } from "transliteration"; import Timeout = NodeJS.Timeout; type MessageInfo = { channelId: string; messageId: string }; @@ -106,6 +107,9 @@ const MatchWordsTrigger = t.type({ words: t.array(t.string), case_sensitive: t.boolean, only_full_words: t.boolean, + normalize: t.boolean, + loose_matching: t.boolean, + loose_matching_threshold: t.number, match_messages: t.boolean, match_embeds: t.boolean, match_visible_names: t.boolean, @@ -118,6 +122,9 @@ const defaultMatchWordsTrigger: TMatchWordsTrigger = { words: [], case_sensitive: false, only_full_words: true, + normalize: false, + loose_matching: false, + loose_matching_threshold: 4, match_messages: true, match_embeds: true, match_visible_names: false, @@ -129,6 +136,7 @@ const defaultMatchWordsTrigger: TMatchWordsTrigger = { const MatchRegexTrigger = t.type({ patterns: t.array(TSafeRegex), case_sensitive: t.boolean, + normalize: t.boolean, match_messages: t.boolean, match_embeds: t.boolean, match_visible_names: t.boolean, @@ -139,6 +147,7 @@ const MatchRegexTrigger = t.type({ type TMatchRegexTrigger = t.TypeOf; const defaultMatchRegexTrigger: Partial = { case_sensitive: false, + normalize: false, match_messages: true, match_embeds: true, match_visible_names: false, @@ -604,8 +613,22 @@ export class AutomodPlugin extends ZeppelinPlugin { * @return Matched word */ protected evaluateMatchWordsTrigger(trigger: TMatchWordsTrigger, str: string): null | string { + if (trigger.normalize) { + str = transliterate(str); + } + + const looseMatchingThreshold = Math.min(Math.max(trigger.loose_matching_threshold, 1), 64); + for (const word of trigger.words) { - const pattern = trigger.only_full_words ? `\\b${escapeStringRegexp(word)}\\b` : escapeStringRegexp(word); + // When performing loose matching, allow any amount of whitespace or up to looseMatchingThreshold number of other + // characters between the matched characters. E.g. if we're matching banana, a loose match could also match b a n a n a + let pattern = trigger.loose_matching + ? [...word].map(c => escapeStringRegexp(c)).join(`(?:\\s*|.{0,${looseMatchingThreshold})`) + : escapeStringRegexp(word); + + if (trigger.only_full_words) { + pattern = `\\b${pattern}\\b`; + } const regex = new RegExp(pattern, trigger.case_sensitive ? "" : "i"); const test = regex.test(str); @@ -619,6 +642,10 @@ export class AutomodPlugin extends ZeppelinPlugin { * @return Matched regex pattern */ protected evaluateMatchRegexTrigger(trigger: TMatchRegexTrigger, str: string): null | string { + if (trigger.normalize) { + str = transliterate(str); + } + // TODO: Time limit regexes for (const pattern of trigger.patterns) { const regex = new RegExp(pattern, trigger.case_sensitive ? "" : "i");