@avatarkage/automod
Version:
A new and nearly unbreakable auto-moderation module.
312 lines (276 loc) โข 13.7 kB
JavaScript
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;
}
}