Merge branch 'v30'

This commit is contained in:
evanpelle
2026-04-06 20:38:22 -07:00
19 changed files with 1187 additions and 479 deletions
+50 -17
View File
@@ -6,7 +6,6 @@ import {
pattern,
resolveConfusablesTransformer,
resolveLeetSpeakTransformer,
skipNonAlphabeticTransformer,
toAsciiLowerCaseTransformer,
} from "obscenity";
import countries from "resources/countries.json";
@@ -47,31 +46,52 @@ export const shadowNames = [
"AlmostPottyTrained",
];
export function createMatcher(bannedWords: string[]): RegExpMatcher {
const customDataset = new DataSet<{ originalWord: string }>().addAll(
function buildDataset(bannedWords: string[], dedup: boolean) {
const dataset = new DataSet<{ originalWord: string }>().addAll(
englishDataset,
);
for (const word of bannedWords) {
try {
customDataset.addPhrase((phrase) =>
phrase.setMetadata({ originalWord: word }).addPattern(pattern`${word}`),
const w = dedup ? word.toLowerCase().replace(/(.)\1+/g, "$1") : word;
dataset.addPhrase((phrase) =>
phrase.setMetadata({ originalWord: word }).addPattern(pattern`${w}`),
);
} catch (e) {
console.error(`Invalid banned word pattern "${word}": ${e}`);
}
}
return dataset.build();
}
return new RegExpMatcher({
...customDataset.build(),
export function createMatcher(bannedWords: string[]): RegExpMatcher {
const baseTransformers = [
toAsciiLowerCaseTransformer(),
resolveConfusablesTransformer(),
resolveLeetSpeakTransformer(),
];
// substringMatcher: literal patterns, no collapse — catches "niggertesting" as a substring
// collapseMatcher: deduped patterns + collapse transformer — catches "niiiigger", "hiiitler"
const substringMatcher = new RegExpMatcher({
...buildDataset(bannedWords, false),
blacklistMatcherTransformers: baseTransformers,
});
const collapseMatcher = new RegExpMatcher({
...buildDataset(bannedWords, true),
blacklistMatcherTransformers: [
toAsciiLowerCaseTransformer(),
resolveConfusablesTransformer(),
resolveLeetSpeakTransformer(),
...baseTransformers,
collapseDuplicatesTransformer(),
skipNonAlphabeticTransformer(),
],
});
return {
hasMatch: (input: string) =>
input.toLowerCase().includes("kkk") ||
substringMatcher.hasMatch(input) ||
collapseMatcher.hasMatch(input),
getAllMatches: (input: string, sorted?: boolean) => [
...substringMatcher.getAllMatches(input, sorted),
...collapseMatcher.getAllMatches(input, sorted),
],
} as unknown as RegExpMatcher;
}
/**
@@ -100,9 +120,19 @@ function censorWithMatcher(
? shadowNames[simpleHash(username) % shadowNames.length]
: username;
const clanTagIsProfane = clanTag ? matcher.hasMatch(clanTag) : false;
const censoredClanTag =
clanTag && !clanTagIsProfane ? clanTag.toUpperCase() : null;
const clanTagIsProfane = clanTag
? matcher.hasMatch(clanTag) || clanTag.toLowerCase() === "ss"
: false;
const usernameIsProfane = matcher.hasMatch(nameWithoutClan);
const censoredName = usernameIsProfane
? shadowNames[simpleHash(nameWithoutClan) % shadowNames.length]
: nameWithoutClan;
// Restore clan tag only if it's clean, otherwise remove it entirely
if (clanTag && !clanTagIsProfane) {
return `[${clanTag.toUpperCase()}] ${censoredName}`;
}
return { username: censoredName, clanTag: censoredClanTag };
}
@@ -242,8 +272,11 @@ export class PrivilegeCheckerImpl implements PrivilegeChecker {
}
}
// Default matcher with no custom banned words (just englishDataset)
const defaultMatcher = createMatcher([]);
// Words the englishDataset misses or only catches as standalone tokens.
// These are always enforced even when the remote banned-words list is unavailable.
const baselineBannedWords = ["nigger", "nigga", "chink", "spic", "kike"];
const defaultMatcher = createMatcher(baselineBannedWords);
export class FailOpenPrivilegeChecker implements PrivilegeChecker {
isAllowed(flares: string[], refs: PlayerCosmeticRefs): CosmeticResult {