mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
Censor slurs split across clan tag and username
Catches names like "[HIT]LER" where neither the clan tag nor the username is profane on its own, but concatenating them forms a slur. Detected by running the matcher against clan+name and checking whether any match spans the clan/name boundary.
This commit is contained in:
+15
-4
@@ -124,13 +124,24 @@ function censorUsernameWithMatcher(
|
||||
? matcher.hasMatch(clanTag) || clanTag.toLowerCase() === "ss"
|
||||
: false;
|
||||
const usernameIsProfane = matcher.hasMatch(nameWithoutClan);
|
||||
// Catch slurs split across clan tag and username (e.g. "[HIT]LER", "[NIG]ger")
|
||||
// by looking for a match that spans the clan/name boundary.
|
||||
const combinedSlurAcrossBoundary = clanTag
|
||||
? matcher.getAllMatches(clanTag + nameWithoutClan).some(
|
||||
(match) =>
|
||||
// Match must start in the clan and extend into the name — otherwise
|
||||
// it's already handled by the clan-only or name-only checks above.
|
||||
match.startIndex < clanTag.length && match.endIndex >= clanTag.length,
|
||||
)
|
||||
: false;
|
||||
|
||||
const censoredName = usernameIsProfane
|
||||
? shadowNames[simpleHash(nameWithoutClan) % shadowNames.length]
|
||||
: nameWithoutClan;
|
||||
const censoredName =
|
||||
usernameIsProfane || combinedSlurAcrossBoundary
|
||||
? shadowNames[simpleHash(nameWithoutClan) % shadowNames.length]
|
||||
: nameWithoutClan;
|
||||
|
||||
// Restore clan tag only if it's clean, otherwise remove it entirely
|
||||
if (clanTag && !clanTagIsProfane) {
|
||||
if (clanTag && !clanTagIsProfane && !combinedSlurAcrossBoundary) {
|
||||
return `[${clanTag.toUpperCase()}] ${censoredName}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -230,6 +230,44 @@ describe("UsernameCensor", () => {
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
describe("clan tag + username combined forms a slur", () => {
|
||||
test("censors when clan+name combined forms hitler", () => {
|
||||
const result = checker.censorUsername("[HIT]LER");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
test("censors when clan+name combined forms hitler (split differently)", () => {
|
||||
const result = checker.censorUsername("[HI]TLER");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
test("censors when clan+name combined forms adolf", () => {
|
||||
const result = checker.censorUsername("[AD]OLF");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
test("censors when clan+name combined forms nigger", () => {
|
||||
const result = checker.censorUsername("[NIG]ger");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
test("censors when clan+name combined forms nigger (clean parts)", () => {
|
||||
const result = checker.censorUsername("[NI]gger");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
|
||||
test("censors leet speak combined across clan and name", () => {
|
||||
const result = checker.censorUsername("[N1G]g3r");
|
||||
expect(shadowNames).toContain(result);
|
||||
expect(result).not.toContain("[");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("returns deterministic shadow name for same input", () => {
|
||||
|
||||
Reference in New Issue
Block a user