mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 19:12:06 +00:00
5e6c90d9bb
## Description: Overhauls the Main Menu UI, visit https://menu.openfront.dev to see everything. ## 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: w.o.n
130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
import {
|
|
RegExpMatcher,
|
|
collapseDuplicatesTransformer,
|
|
englishDataset,
|
|
englishRecommendedTransformers,
|
|
resolveConfusablesTransformer,
|
|
resolveLeetSpeakTransformer,
|
|
skipNonAlphabeticTransformer,
|
|
} from "obscenity";
|
|
import { translateText } from "../../client/Utils";
|
|
import { UsernameSchema } from "../Schemas";
|
|
import { getClanTagOriginalCase, 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 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 separate local player name with a profane clan tag will remain, no clan team assignment
|
|
*
|
|
* Examples:
|
|
* - "GoodName" -> "GoodName"
|
|
* - "BadName" -> "Censored"
|
|
* - "[CLAN]GoodName" -> "[CLAN]GoodName"
|
|
* - "[CLaN]BadName" -> "[CLaN] Censored"
|
|
* - "[BAD]GoodName" -> "GoodName"
|
|
* - "[BAD]BadName" -> "Censored"
|
|
*/
|
|
export function censorNameWithClanTag(username: string): string {
|
|
// Don't use getClanTag because that returns upperCase and if original isn't, str replace `[{$clanTag}]` won't match
|
|
const clanTag = getClanTagOriginalCase(username);
|
|
|
|
const nameWithoutClan = clanTag
|
|
? username.replace(`[${clanTag}]`, "").trim()
|
|
: username;
|
|
|
|
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) {
|
|
return `[${clanTag.toUpperCase()}] ${censoredNameWithoutClan}`;
|
|
}
|
|
|
|
// Don't restore profane or nonexistent clan tag
|
|
return censoredNameWithoutClan;
|
|
}
|
|
|
|
export function validateUsername(username: string): {
|
|
isValid: boolean;
|
|
error?: string;
|
|
} {
|
|
const parsed = UsernameSchema.safeParse(username);
|
|
|
|
if (!parsed.success) {
|
|
const errType = parsed.error.issues[0].code;
|
|
|
|
if (errType === "invalid_type") {
|
|
return { isValid: false, error: translateText("username.not_string") };
|
|
}
|
|
|
|
if (errType === "too_small") {
|
|
return {
|
|
isValid: false,
|
|
error: translateText("username.too_short", {
|
|
min: MIN_USERNAME_LENGTH,
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (errType === "too_big") {
|
|
return {
|
|
isValid: false,
|
|
error: translateText("username.too_long", {
|
|
max: MAX_USERNAME_LENGTH,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// Invalid regex, or any other issue
|
|
else {
|
|
return { isValid: false, error: translateText("username.invalid_chars") };
|
|
}
|
|
}
|
|
|
|
// All checks passed
|
|
return { isValid: true };
|
|
}
|