UNPKG

@avatarkage/automod

Version:

A new and nearly unbreakable auto-moderation module.

312 lines (276 loc) โ€ข 13.7 kB
function autoMod(value) { // List of filters with pattern-based wildcards and severity level const filters = [ { name: "Discriminatory", severity: "High", words: ["*(๐Ÿฅท|๐Ÿ†–)*", "(?g)ga", "*n(?i|e)g(?a|g|h)(?h|r|e|a)(?a)(?rs|s)*", "*f(?a)ag*", "*retard*", "coon(?s)", "*kike*", "yid(?s)", "*chink*", "ching(?s)", "*gook*", "*slopehead*", "jap(?s)", "*china(? )(wo)m(a|e)n*", "*coolie*", "*sambo*", "*tar(? )baby*", "*jemima(?s)*", "*mamm(?y|ies|s)*", "*pick(? )aninn(?y|ies|s)*", "*gay(? )lord(?s)*", "*dyke*", "*she(? )male*", "*trann(?y|ies|s)*", "*transvestite*", "*cripple*", "*white(? )power*", "n(? )word(?s)", "r(? )word(?s)", "cong(?s)", "*siss(?y|ies|s)*", "hard(?s)r", "*kanker(?s)*", "*lesbo(?s)*", "chong(?s)", "*cotton picker*"], message: "This word has been blocked", bypass: "" }, { name: "Profanity", severity: "Low", words: ["*๐Ÿ–•*", "*middle(? )finger*", "*bi(?a)tch*", "*ar(?r)se(? )hole*", "*as(?s)(? )hole*", "*basta(?r)(?d)*", "*b(o|u)l(?l)(? )lock*", "*stfu*", "*piss*", "*dumbas(?s)*", "*shit*", "*you(?r)(?e)(? )(?a)(? )birch*", "*bish*", "*f(?u)(c|k)*", "*f****", "*s****"], message: "This word has been blocked", bypass: "" }, { name: "Profanity Abbreviations", severity: "Low", words: ["fml", "mf", "(?w)tf", "*my(? )(god|lord)*", "o(m|n)(?f)(g|l)", "ist(g|l)", "lm(?f)(a|b)o", "*๐Ÿคฌ*", "*(โ›ฑ๏ธ|๐Ÿ–๏ธ)*"], message: "This word has been blocked", bypass: "" } ]; // Obfuscation mapping for each characters that allows matching variations of letters using Unicode, homoglyphs, or common substitutions const obfuscation_map = { a: "[aA4@รร€ร‚ร„รƒร…ฤ€ฤ‚ฤ„ฮฑฮ”ฮ›ะ”โˆ†๐’ถ๐”ž๐•’๐–†๐˜ข๐˜ผ๐—ฎ๐™–๐šŠโ’ถโ“๐Ÿ…๐Ÿ„ฐ๐š๐–บแตƒโ‚๐›‚๐›ผ๐Ÿ‡ฆ]", b: "[bB8รŸเธฟฮฒะ‘๐’ท๐”Ÿ๐•“๐–‡๐˜ฃ๐˜ฝ๐—ฏ๐™—๐š‹โ’ทโ“‘๐Ÿ…‘๐Ÿ„ฑ๐›แต‡๐›ƒ๐›ฝ๐Ÿ‡ง]", c: "[cCยขยฉรงฤ†ฤŒฤˆฤŠฯ‚ส—ะก๐•๐’ธ๐” ๐•”๐–ˆ๐˜ค๐˜พ๐—ฐ๐™˜๐šŒโ’ธโ“’๐Ÿ…’๐Ÿ„ฒ๐œแถœ๐›พ๐Ÿ‡จ]", d: "[dDรฤŽฤิ€ิิƒฮดศก๐’น๐”ก๐••๐–‰๐˜ฅ๐˜ฟ๐—ฑ๐™™๐šโ’นโ““๐Ÿ…“๐Ÿ„ณ๐แตˆ๐›…๐Ÿ‡ฉ]", e: "[eE3โ‚ฌรฉรจรชรซฤ“ฤ—ฤ™ฮžฮฃฦ๐‘’๐”ข๐•–๐–Š๐˜ฆ๐™€๐—ฒ๐™š๐šŽโ’บโ“”๐Ÿ…”๐Ÿ„ด๐žแต‰โ‚‘๐Ÿ‡ช]", f: "[fFฦ’า’โ‚ฃ๐’ป๐”ฃ๐•—๐–‹๐˜ง๐™๐—ณ๐™›๐šโ’ปโ“•๐Ÿ…•๐Ÿ„ต๐Ÿแถ ๐Ÿ‡ซ]", g: "[gG69ฤฤŸฤกฤฃษขวฅโ‚ฒ๐“ฐ๐”ค๐•˜๐–Œ๐˜จ๐™‚๐—ด๐™œ๐šโ’ผโ“–๐Ÿ…–๐Ÿ„ถ๐ แต๐Ÿ‡ฌ]", h: "[hHฤฅฤงะฝะฝะฝฤฆ#๐’ฝ๐”ฅ๐•™๐–๐˜ฉ๐™ƒ๐—ต๐™๐š‘โ’ฝโ“—๐Ÿ…—๐Ÿ„ท๐กสฐโ‚•๐Ÿ‡ญ]", i: "[iI1!รญรฌรฎรฏฤซฤฏฮนะ†ยก|ว๐’พ๐”ฆ๐•š๐–Ž๐˜ช๐™„๐—ถ๐™ž๐š’โ’พโ“˜๐Ÿ…˜๐Ÿ„ธ๐ขแถฆแตข๐Ÿ‡ฎ*]", j: "[jJฤตสั˜ะˆยฟ๐’ฟ๐”ง๐•›๐–๐˜ซ๐™…๐—ท๐™Ÿ๐š“โ’ฟโ“™๐Ÿ…™๐Ÿ„น๐ฃสฒ๐Ÿ‡ฏ]", k: "[kKฤธฮบฤทฤถโฑฉา ะบ๐“€๐”จ๐•œ๐–๐˜ฌ๐™†๐—ธ๐™ ๐š”โ“€โ“š๐Ÿ…š๐Ÿ„บ๐คแตโ‚–๐Ÿ‡ฐ]", l: "[lL1|ยฃล‚ฤฟฤนฤปฤฝโ…ƒโˆฃ๐“๐”ฉ๐•๐–‘๐˜ญ๐™‡๐—น๐™ก๐š•โ“โ“›๐Ÿ…›๐Ÿ„ป๐ฅหกโ‚—๐Ÿ‡ฑ]", m: "[mMโ‚ฅยตะผแนƒแนะผะผ๐“‚๐”ช๐•ž๐–’๐˜ฎ๐™ˆ๐—บ๐™ข๐š–โ“‚โ“œ๐Ÿ…œ๐Ÿ„ผ๐ฆแตโ‚˜๐Ÿ‡ฒ]", n: "[nNรฑล„ล†ลˆล‰ฮทะฟะŸะ˜ะ˜โˆ๐“ƒ๐”ซ๐•Ÿ๐–“๐˜ฏ๐™‰๐—ป๐™ฃ๐š—โ“ƒโ“๐Ÿ…๐Ÿ„ฝ๐งโฟโ‚™๐Ÿ‡ณ]", o: "[oO0ยฐรธรตรถรดรณรฒล“ลฮ˜ะคยคโ—Ž๐“ธ๐”ฌ๐• ๐–”๐˜ฐ๐™Š๐—ผ๐™ค๐š˜โ“„โ“ž๐Ÿ…ž๐Ÿ„พ๐จแต’โ‚’๐Ÿ‡ด]", p: "[pPรพฯั€าโ‚ฑยถโ„—โ„˜๐“…๐”ญ๐•ก๐–•๐˜ฑ๐™‹๐—ฝ๐™ฅ๐š™โ“…โ“Ÿ๐Ÿ…Ÿ๐Ÿ„ฟ๐ฉแต–โ‚š๐Ÿ‡ต]", q: "[qQ9วซีฆิ›ษ‹ส ๐“†๐”ฎ๐•ข๐––๐˜ฒ๐™Œ๐—พ๐™ฆ๐ššโ“†โ“ ๐Ÿ… ๐Ÿ…€๐ช๐Ÿ‡ถ]", r: "[rRยฎล™ล—ะณั“า‘ัะฏ๐“‡๐”ฏ๐•ฃ๐–—๐˜ณ๐™๐—ฟ๐™ง๐š›โ“‡โ“ก๐Ÿ…ก๐Ÿ…๐ซสณ๐Ÿ‡ท]", s: "[sS5$ยงลกล›ศ™ศ™ั•๊œฑส‚๐“ˆ๐”ฐ๐•ค๐–˜๐˜ด๐™Ž๐˜€๐š โ“ˆโ“ข๐Ÿ…ข๐Ÿ…‚๐ฌหขโ‚›๐Ÿ‡ธ]", t: "[tT7+โ€ ลฃลงั‚ั‚๐“‰๐”ฑ๐•ฅ๐–™๐˜ต๐™๐—ฒ๐™ฉ๐šโ“‰โ“ฃ๐Ÿ…ฃ๐Ÿ…ƒ๐ญแต—โ‚œ๐Ÿ‡น]", u: "[uU(_)|รผรปรนรบลซลณยตัŸั†ัŸส‰๐“Š๐”ฒ๐•ฆ๐–š๐˜ถ๐™๐—ฒ๐™ช๐šžโ“Šโ“ค๐Ÿ…ค๐Ÿ…„๐ฎแต˜แตค๐Ÿ‡บ]", v: "[vVโˆšสŒฮฝvโฑฑัต๐“‹๐”ณ๐•ง๐–›๐˜ท๐™‘๐—ฒ๐™ซ๐šŸโ“‹โ“ฅ๐Ÿ…ฅ๐Ÿ……๐ฏแต›๐Ÿ‡ป]", w: "[wWฯ‰ลตัˆั‰ักแบแบƒแบ…๐“Œ๐”ด๐•จ๐–œ๐˜ธ๐™’๐—ฒ๐™ฌ๐š โ“Œโ“ฆ๐Ÿ…ฆ๐Ÿ…†๐ฐสท๐Ÿ‡ผ]", x: "[xXร—ฯ‡ั…ะถ๐“๐”ต๐•ฉ๐–๐˜น๐™“๐—ฒ๐™ญ๐šกโ“โ“ง๐Ÿ…ง๐Ÿ…‡๐ฑหฃโ‚“๐Ÿ‡ฝ]", y: "[yYยฅรฝรฟลทัƒาฎาฐาฏฮณ๐“Ž๐”ถ๐•ช๐–ž๐˜บ๐™”๐—ฒ๐™ฎ๐šขโ“Žโ“จ๐Ÿ…จ๐Ÿ…ˆ๐ฒสธ๐Ÿ‡พโ˜ฏ]", z: "[zZ2ลพลบลผสส‘ฦถโ„ค๐“๐”ท๐•ซ๐–Ÿ๐˜ป๐™•๐—ฒ๐™ฏ๐šฃโ“โ“ฉ๐Ÿ…ฉ๐Ÿ…‰๐ณแถป๐Ÿ‡ฟ]", }; // Characters here are removed when checking filters to prevent escaping filters const separators = [ " ", "-", ".", "_", "*", "^", "~", "`", "=", "+", ",", "|", "/", "\\", "'", "\"", ":", ";", "(", ")", "[", "]", "{", "}", "<", ">", "@", "#", "!", "$", "%", "?", "\n", "\r", "\u200b", "\u200c", "\u200d", "\u2060" ]; // Escape regex-special characters in literal text function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // Build a full regex pattern from a potentially wildcarded and obfuscated keyword function buildWordRegex(word) { const prefixWildcard = word.startsWith("*"); const suffixWildcard = word.endsWith("*"); const baseWord = word.replace(/^\*|\*$/g, "").toLowerCase(); let pattern = ""; let i = 0; while (i < baseWord.length) { const ch = baseWord[i]; // Handle optional single char or optional group if (ch === '(' && baseWord[i + 1] === '?') { i += 2; // skip "(?" let group = ""; let depth = 1; while (i < baseWord.length && depth > 0) { const c = baseWord[i]; if (c === '(') depth++; if (c === ')') depth--; if (depth > 0) group += c; i++; } if (group.includes('|')) { // Optional group (a|b|c) const options = group.split('|').map(opt => { return opt .split('') .map(c => obfuscation_map[c] || escapeRegex(c)) .join(`[${escapeRegex(separators.join(""))}]*`); }); pattern += `(?:${options.join('|')})?`; } else { // Optional single character (?x) const charPattern = obfuscation_map[group] || escapeRegex(group); pattern += `(?:${charPattern}[${escapeRegex(separators.join(""))}]*)?`; } } // Handle required group (a|b) else if (ch === '(') { let group = ""; let depth = 1; i++; // skip '(' while (i < baseWord.length && depth > 0) { const c = baseWord[i]; if (c === '(') depth++; if (c === ')') depth--; if (depth > 0) group += c; i++; } const options = group.split('|').map(opt => { return opt .split('') .map(c => obfuscation_map[c] || escapeRegex(c)) .join(`[${escapeRegex(separators.join(""))}]*`); }); // Add separators *before* and *after* the group as well: pattern += `[${escapeRegex(separators.join(""))}]*` + `(?:${options.join('|')})` + `[${escapeRegex(separators.join(""))}]*`; } // Regular character else { const charPattern = obfuscation_map[ch] || escapeRegex(ch); pattern += charPattern; if (i < baseWord.length - 1) { pattern += `[${escapeRegex(separators.join(""))}]*`; } i++; } } // Apply wildcard matching if (prefixWildcard && suffixWildcard) { pattern = `(?<!\\S)\\S*${pattern}\\S*(?!\\S)`; } else if (prefixWildcard) { pattern = `(?<!\\S)\\S*${pattern}(?!\\S)`; } else if (suffixWildcard) { // Here is the important change: const sepClass = `[${escapeRegex(separators.join(''))}]*`; pattern = `(?<!\\S)${sepClass}${pattern}${sepClass}(?!\\S)`; } else { // Also here: const sepClass = `[${escapeRegex(separators.join(''))}]*`; pattern = `(?<!\\S)${sepClass}${pattern}${sepClass}(?!\\S)`; } return new RegExp(pattern, "i"); } function highlight(text, regex) { const sepPattern = separators.map(s => `\\${s}`).join(''); const sepRegex = new RegExp(`^[${sepPattern}]+|[${sepPattern}]+$`, 'g'); return text.replace(regex, match => { const parts = []; let remaining = match; let leading = '', trailing = ''; const leadingMatch = remaining.match(new RegExp(`^[${sepPattern}]+`)); if (leadingMatch) { leading = leadingMatch[0]; remaining = remaining.slice(leading.length); } const trailingMatch = remaining.match(new RegExp(`[${sepPattern}]+$`)); if (trailingMatch) { trailing = trailingMatch[0]; remaining = remaining.slice(0, -trailing.length); } return `${leading}<mark>${remaining}</mark>${trailing}`; }); } // DOM references (some are not yet active) const username = "Username"; const handle = "@username"; const checkExtras = true; // Set to false to ignore username/handle in checks const val = value; const extraText = checkExtras ? `${username} ${handle}` : ""; let highlightText = val; let highlightUsername = username; let highlightHandle = handle; let blocked = false; let reason = null; let source = ""; let matchedWord = ""; const matches = []; // First, check profile data (username and handle) for (const filter of filters) { for (const word of filter.words) { const regex = buildWordRegex(word); if (checkExtras && regex.test(username)) { blocked = filter.bypass !== "AvatarKage"; reason = filter; matchedWord = word; source = "Username"; highlightUsername = highlight(username, regex); break; } if (checkExtras && regex.test(handle)) { blocked = filter.bypass !== "AvatarKage"; reason = filter; matchedWord = word; source = "Handle"; highlightHandle = highlight(handle, regex); break; } } if (blocked) break; } // If username/handle clean, scan the input field for ALL matches if (!blocked) { for (const filter of filters) { for (const word of filter.words) { const regex = buildWordRegex(word); if (regex.test(val)) { matches.push({ regex, word, filter }); } } } if (matches.length > 0) { blocked = matches[0].filter.bypass !== "AvatarKage"; reason = matches[0].filter; matchedWord = matches[0].word; source = "Message"; // Highlight ONLY the first matched word in the input const { regex } = matches[0]; highlightText = highlight(highlightText, regex); // Highlight ALL matched words in the input //for (const { regex } of matches) { // highlightText = highlight(highlightText, regex); //} } } if (blocked) { let flaggedText; if (source === "Username") { const regex = buildWordRegex(matchedWord); flaggedText = highlightUsername.replace(regex, match => `<mark>${match}</mark>`); } else if (source === "Handle") { const regex = buildWordRegex(matchedWord); flaggedText = highlightHandle.replace(regex, match => `<mark>${match}</mark>`); } else { flaggedText = highlightText.replace(/\n/g, '<br>'); } return { data: [ { text: highlightText.replace(/\n/g, '<br>'), word: matchedWord, filter: JSON.stringify( Object.fromEntries(Object.entries(reason).filter(([key]) => key !== "words"))), source: source } ] }; } else { return null; } } // Export for CommonJS if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = autoMod; } // Export for ES Modules if (typeof exports === 'undefined' || typeof exports !== 'object') { if (typeof globalThis !== 'undefined') { globalThis.autoMod = autoMod; } else if (typeof window !== 'undefined') { window.autoMod = autoMod; } else if (typeof self !== 'undefined') { self.autoMod = autoMod; } }