sanitize profane usernames

This commit is contained in:
Evan
2025-02-08 19:00:35 -08:00
parent 1594a45dac
commit 2fa576c841
9 changed files with 92 additions and 37 deletions
+3 -4
View File
@@ -22,14 +22,13 @@ import { NameViewData } from "./game/Game";
import { GameUpdateType } from "./game/GameUpdates";
import { createGame } from "./game/GameImpl";
import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
import { GameConfig, Turn } from "./Schemas";
import { ClientID, GameConfig, Turn } from "./Schemas";
import { GameUpdateViewData } from "./game/GameUpdates";
import { andFN, manhattanDistFN, TileRef } from "./game/GameMap";
import { targetTransportTile } from "./Util";
export async function createGameRunner(
gameID: string,
gameConfig: GameConfig,
clientID: ClientID,
callBack: (gu: GameUpdateViewData) => void
): Promise<GameRunner> {
const config = getConfig(gameConfig);
@@ -40,7 +39,7 @@ export async function createGameRunner(
gameMap.nationMap,
config
);
const gr = new GameRunner(game as Game, new Executor(game, gameID), callBack);
const gr = new GameRunner(game as Game, new Executor(game, gameID, clientID), callBack);
gr.init();
return gr;
}
+12 -11
View File
@@ -12,6 +12,7 @@ import {
import {
AttackIntent,
BoatAttackIntentSchema,
ClientID,
GameID,
Intent,
Turn,
@@ -22,29 +23,26 @@ import { BotSpawner } from "./BotSpawner";
import { TransportShipExecution } from "./TransportShipExecution";
import { PseudoRandom } from "../PseudoRandom";
import { FakeHumanExecution } from "./FakeHumanExecution";
import { generateID, processName, sanitize, simpleHash } from "../Util";
import { sanitize, simpleHash } from "../Util";
import { AllianceRequestExecution } from "./alliance/AllianceRequestExecution";
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
import { BreakAllianceExecution } from "./alliance/BreakAllianceExecution";
import { TargetPlayerExecution } from "./TargetPlayerExecution";
import { EmojiExecution } from "./EmojiExecution";
import { DonateExecution } from "./DonateExecution";
import { NukeExecution } from "./NukeExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { WarshipExecution } from "./WarshipExecution";
import { PortExecution } from "./PortExecution";
import { MissileSiloExecution } from "./MissileSiloExecution";
import { DefensePostExecution } from "./DefensePostExecution";
import { CityExecution } from "./CityExecution";
import { TileRef } from "../game/GameMap";
import { MirvExecution } from "./MIRVExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
export class Executor {
// private random = new PseudoRandom(999)
private random: PseudoRandom = null;
constructor(private mg: Game, private gameID: GameID) {
constructor(
private mg: Game,
private gameID: GameID,
private clientID: ClientID
) {
// Add one to avoid id collisions with bots.
this.random = new PseudoRandom(simpleHash(gameID) + 1);
}
@@ -66,7 +64,10 @@ export class Executor {
case "spawn":
return new SpawnExecution(
new PlayerInfo(
sanitize(intent.name),
// Players see their original name, others see a sanitized version
intent.clientID == this.clientID
? sanitize(intent.name)
: fixProfaneUsername(sanitize(intent.name)),
intent.playerType,
intent.clientID,
intent.playerID
+33
View File
@@ -1,8 +1,41 @@
import {
RegExpMatcher,
englishDataset,
englishRecommendedTransformers,
} from "obscenity";
import { simpleHash } from "../Util";
const matcher = new RegExpMatcher({
...englishDataset.build(),
...englishRecommendedTransformers,
});
export const MIN_USERNAME_LENGTH = 3;
export const MAX_USERNAME_LENGTH = 20;
const validPattern = /^[a-zA-Z0-9_ ]+$/;
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);
}
export function validateUsername(username: string): {
isValid: boolean;
error?: string;
+4 -3
View File
@@ -27,14 +27,15 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
switch (message.type) {
case "heartbeat":
(await gameRunner).executeNextTick()
(await gameRunner).executeNextTick();
break;
case "init":
try {
gameRunner = createGameRunner(
message.gameID,
message.gameConfig,
gameUpdate,
message.clientID,
gameUpdate
).then((gr) => {
sendMessage({
type: "initialized",
@@ -71,7 +72,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
const actions = (await gameRunner).playerActions(
message.playerID,
message.x,
message.y,
message.y
);
sendMessage({
type: "player_actions_result",
+7 -2
View File
@@ -5,7 +5,7 @@ import {
PlayerProfile,
} from "../game/Game";
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
import { GameConfig, GameID, Turn } from "../Schemas";
import { ClientID, GameConfig, GameID, Turn } from "../Schemas";
import { generateID } from "../Util";
import { WorkerMessage } from "./WorkerMessages";
@@ -17,7 +17,11 @@ export class WorkerClient {
update: GameUpdateViewData | ErrorUpdate
) => void;
constructor(private gameID: GameID, private gameConfig: GameConfig) {
constructor(
private gameID: GameID,
private gameConfig: GameConfig,
private clientID: ClientID
) {
this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url));
this.messageHandlers = new Map();
@@ -65,6 +69,7 @@ export class WorkerClient {
id: messageId,
gameID: this.gameID,
gameConfig: this.gameConfig,
clientID: this.clientID,
});
// Add timeout for initialization
+2 -1
View File
@@ -1,5 +1,5 @@
import { GameUpdateViewData } from "../game/GameUpdates";
import { GameConfig, GameID, Turn } from "../Schemas";
import { ClientID, GameConfig, GameID, Turn } from "../Schemas";
import { PlayerActions, PlayerID, PlayerProfile } from "../game/Game";
export type WorkerMessageType =
@@ -28,6 +28,7 @@ export interface InitMessage extends BaseWorkerMessage {
type: "init";
gameID: GameID;
gameConfig: GameConfig;
clientID: ClientID;
}
export interface TurnMessage extends BaseWorkerMessage {