mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 01:24:36 +00:00
e99bf6360c
## Description: Clan tag was removed when overwriting profane username. The local player still sees the name they put in though, and are assigned to a team based on the clan tag. Other player's browsers don't assign them to their team because the clan tag isn't visible to them. **Fixes:** - GameRunner.ts > username.ts: fetch clan tag before potentially overwriting bad username. Prepend non-profane clan tag back to the name string afterwards. - Util.ts: added getClanTagOriginalCase; we can't use getClanTag in censorNameWithClanTag because it returns all caps and we needed to retain the orginal capitalization. Explained in code comment. - Game.ts: no changes. By keeping the getClanTag in PlayerInfo contructor, TeamAssignment still gets clan param to correctly assign clan teams, other players get to see the clan tag of the "BeNicer" player, and GameServer archivegame() and LocalServer endGame() can still do getClanTag to have the same data at the end of the game, and test files keep working. **Screencap after fix:** https://github.com/user-attachments/assets/564c0ffd-577e-4653-ba33-155d2135a9f0 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tryout33
143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
import {
|
|
RegExpMatcher,
|
|
collapseDuplicatesTransformer,
|
|
englishDataset,
|
|
englishRecommendedTransformers,
|
|
resolveConfusablesTransformer,
|
|
resolveLeetSpeakTransformer,
|
|
skipNonAlphabeticTransformer,
|
|
} from "obscenity";
|
|
import { translateText } from "../../client/Utils";
|
|
import { getClanTagOriginalCase, sanitize, simpleHash } from "../Util";
|
|
|
|
const matcher = new RegExpMatcher({
|
|
...englishDataset.build(),
|
|
...englishRecommendedTransformers,
|
|
...resolveConfusablesTransformer(),
|
|
...skipNonAlphabeticTransformer(),
|
|
...collapseDuplicatesTransformer(),
|
|
...resolveLeetSpeakTransformer(),
|
|
});
|
|
|
|
export const MIN_USERNAME_LENGTH = 3;
|
|
export const MAX_USERNAME_LENGTH = 27;
|
|
|
|
const validPattern = /^[a-zA-Z0-9_[\] 🐈🍀üÜ]+$/u;
|
|
|
|
const shadowNames = [
|
|
"NicePeopleOnly",
|
|
"BeKindPlz",
|
|
"LearningManners",
|
|
"StayClassy",
|
|
"BeNicer",
|
|
"NeedHugs",
|
|
"MakeFriends",
|
|
];
|
|
|
|
export function fixProfaneUsername(username: string): string {
|
|
if (isProfaneUsername(username)) {
|
|
return shadowNames[simpleHash(username) % shadowNames.length];
|
|
}
|
|
return username;
|
|
}
|
|
|
|
export function isProfaneUsername(username: string): boolean {
|
|
return matcher.hasMatch(username);
|
|
}
|
|
|
|
/**
|
|
* Sanitizes and censors profane usernames and clan tags.
|
|
* Profane username is overwritten, profane clan tag is removed.
|
|
*
|
|
* Preserves non-profane clan tag:
|
|
* prevents desync after clan team assignment because local player's own clan tag and name aren't overwritten
|
|
*
|
|
* Removing bad clan tags won't hurt existing clans nor cause desyncs:
|
|
* - full name including clan tag was overwritten in the past, if any part of name was bad
|
|
* - only each seperate local player name with a profane clan tag will remain, no clan team assignment
|
|
*
|
|
* Examples:
|
|
* - "GoodName" -> "GoodName"
|
|
* - "Good$Name" -> "GoodName"
|
|
* - "BadName" -> "Censored"
|
|
* - "[CLAN]GoodName" -> "[CLAN]GoodName"
|
|
* - "[CLaN]BadName" -> "[CLaN] Censored"
|
|
* - "[BAD]GoodName" -> "GoodName"
|
|
* - "[BAD]BadName" -> "Censored"
|
|
*/
|
|
export function censorNameWithClanTag(username: string): string {
|
|
const sanitizedUsername = sanitize(username);
|
|
|
|
// Don't use getClanTag because that returns upperCase and if original isn't, str replace `[{$clanTag}]` won't match
|
|
const clanTag = getClanTagOriginalCase(sanitizedUsername);
|
|
|
|
const nameWithoutClan = clanTag
|
|
? sanitizedUsername.replace(`[${clanTag}]`, "").trim()
|
|
: sanitizedUsername;
|
|
|
|
const clanTagIsProfane = clanTag ? isProfaneUsername(clanTag) : false;
|
|
const usernameIsProfane = isProfaneUsername(nameWithoutClan);
|
|
|
|
const censoredNameWithoutClan = usernameIsProfane
|
|
? fixProfaneUsername(nameWithoutClan)
|
|
: nameWithoutClan;
|
|
|
|
// Restore clan tag if it existed and is not profane
|
|
if (clanTag && !clanTagIsProfane) {
|
|
if (usernameIsProfane) {
|
|
return `[${clanTag}] ${censoredNameWithoutClan}`;
|
|
}
|
|
return sanitizedUsername;
|
|
}
|
|
|
|
// Don't restore profane or nonexistent clan tag
|
|
return censoredNameWithoutClan;
|
|
}
|
|
|
|
export function validateUsername(username: string): {
|
|
isValid: boolean;
|
|
error?: string;
|
|
} {
|
|
if (typeof username !== "string") {
|
|
return { isValid: false, error: translateText("username.not_string") };
|
|
}
|
|
|
|
if (username.length < MIN_USERNAME_LENGTH) {
|
|
return {
|
|
isValid: false,
|
|
error: translateText("username.too_short", {
|
|
min: MIN_USERNAME_LENGTH,
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (username.length > MAX_USERNAME_LENGTH) {
|
|
return {
|
|
isValid: false,
|
|
error: translateText("username.too_long", {
|
|
max: MAX_USERNAME_LENGTH,
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (!validPattern.test(username)) {
|
|
return {
|
|
isValid: false,
|
|
error: translateText("username.invalid_chars", {
|
|
max: MAX_USERNAME_LENGTH,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// All checks passed
|
|
return { isValid: true };
|
|
}
|
|
|
|
export function sanitizeUsername(str: string): string {
|
|
const sanitized = Array.from(str)
|
|
.filter((ch) => validPattern.test(ch))
|
|
.join("")
|
|
.slice(0, MAX_USERNAME_LENGTH);
|
|
return sanitized.padEnd(MIN_USERNAME_LENGTH, "x");
|
|
}
|