Ruperth commited on
Commit
e535f52
·
1 Parent(s): 73c3af5

fix: spread random usernames so adjacent YouTube comments stop sharing suffixes

Browse files

The earlier DJB2 hash produced near-identical bucket positions for seeds that only differed in the trailing index, which is why ten consecutive fetched comments often ended up with the same noun and the same _official or _skater suffix. Replaces the hash with FNV-1a plus three independent mix steps and almost doubles both word lists so the rail now shows visibly distinct authors.

Files changed (1) hide show
  1. frontend/src/utils/toxicity.ts +37 -9
frontend/src/utils/toxicity.ts CHANGED
@@ -57,6 +57,10 @@ const ADJECTIVES = [
57
  "brave", "fuzzy", "shiny", "sleepy", "quick", "noble", "loud", "humble",
58
  "stormy", "sunny", "rainy", "frosty", "spicy", "salty", "sweet", "crazy",
59
  "mighty", "silent", "epic", "cosmic", "rusty", "neon", "wandering", "lone",
 
 
 
 
60
  ];
61
 
62
  const NOUNS = [
@@ -64,23 +68,47 @@ const NOUNS = [
64
  "lynx", "shark", "raven", "viper", "phoenix", "dragon", "jaguar", "koala",
65
  "moose", "lion", "owl", "octopus", "rabbit", "hawk", "badger", "robin",
66
  "cosmonaut", "drifter", "ninja", "wizard", "rider", "pilot", "skater", "gamer",
 
 
 
 
67
  ];
68
 
69
- const SUFFIXES = ["", "_", "_", "_yt", "_hd", "_real", "99", "01", "_xd", "_v2", "_official", ""];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  /**
72
  * Generate a YouTube-style random username, deterministic per seed.
 
 
 
73
  */
74
  export function randomUsername(seed: string | number | undefined | null): string {
75
  const str = seed == null ? "anon" : String(seed);
76
- let hash = 5381;
77
- for (let i = 0; i < str.length; i++) {
78
- hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0;
79
- }
80
- const adj = ADJECTIVES[hash % ADJECTIVES.length] ?? "anon";
81
- const noun = NOUNS[Math.floor(hash / 32) % NOUNS.length] ?? "user";
82
- const suffix = SUFFIXES[Math.floor(hash / 1024) % SUFFIXES.length] ?? "";
83
  const needsNum = suffix === "" || suffix.endsWith("_");
84
- const num = needsNum ? String(hash % 1000) : "";
85
  return `${adj}_${noun}${suffix}${num}`;
86
  }
 
57
  "brave", "fuzzy", "shiny", "sleepy", "quick", "noble", "loud", "humble",
58
  "stormy", "sunny", "rainy", "frosty", "spicy", "salty", "sweet", "crazy",
59
  "mighty", "silent", "epic", "cosmic", "rusty", "neon", "wandering", "lone",
60
+ "electric", "atomic", "midnight", "golden", "silver", "crimson", "violet", "azure",
61
+ "ancient", "feral", "tidal", "lunar", "solar", "ember", "frost", "iron",
62
+ "velvet", "wicked", "groovy", "funky", "chill", "vibrant", "moody", "savage",
63
+ "tropical", "alpine", "urban", "rogue", "stellar", "nomadic", "boreal", "obsidian",
64
  ];
65
 
66
  const NOUNS = [
 
68
  "lynx", "shark", "raven", "viper", "phoenix", "dragon", "jaguar", "koala",
69
  "moose", "lion", "owl", "octopus", "rabbit", "hawk", "badger", "robin",
70
  "cosmonaut", "drifter", "ninja", "wizard", "rider", "pilot", "skater", "gamer",
71
+ "barista", "hacker", "painter", "poet", "sailor", "ranger", "archer", "dancer",
72
+ "chef", "cyclist", "writer", "drummer", "diver", "climber", "biker", "florist",
73
+ "comet", "nebula", "nova", "pulsar", "quasar", "asteroid", "meteor", "orbiter",
74
+ "knight", "monk", "scribe", "merchant", "wanderer", "voyager", "explorer", "pirate",
75
  ];
76
 
77
+ const SUFFIXES = [
78
+ "", "", "", "", "",
79
+ "_", "_x", "_yt", "_hd", "_real", "_official", "_tv", "_live", "_xd", "_v2",
80
+ "99", "01", "07", "23", "42",
81
+ "_irl", "_pro",
82
+ ];
83
+
84
+ function hashString(str: string): number {
85
+ let h = 0x811c9dc5;
86
+ for (let i = 0; i < str.length; i++) {
87
+ h ^= str.charCodeAt(i);
88
+ h = Math.imul(h, 0x01000193) >>> 0;
89
+ }
90
+ return h >>> 0;
91
+ }
92
+
93
+ function mix(h: number, salt: number): number {
94
+ let x = (h ^ Math.imul(salt + 1, 0x85ebca6b)) >>> 0;
95
+ x = Math.imul(x ^ (x >>> 13), 0xc2b2ae35) >>> 0;
96
+ return (x ^ (x >>> 16)) >>> 0;
97
+ }
98
 
99
  /**
100
  * Generate a YouTube-style random username, deterministic per seed.
101
+ * Three independent hash mixes pick adjective, noun, and suffix so that
102
+ * seeds that share a prefix (e.g. `yt-VIDEOID-0`, `yt-VIDEOID-1`) still
103
+ * produce visibly different usernames.
104
  */
105
  export function randomUsername(seed: string | number | undefined | null): string {
106
  const str = seed == null ? "anon" : String(seed);
107
+ const base = hashString(str);
108
+ const adj = ADJECTIVES[mix(base, 1) % ADJECTIVES.length] ?? "anon";
109
+ const noun = NOUNS[mix(base, 2) % NOUNS.length] ?? "user";
110
+ const suffix = SUFFIXES[mix(base, 3) % SUFFIXES.length] ?? "";
 
 
 
111
  const needsNum = suffix === "" || suffix.endsWith("_");
112
+ const num = needsNum ? String(mix(base, 4) % 1000) : "";
113
  return `${adj}_${noun}${suffix}${num}`;
114
  }