@homie-rocks/studio 0.14.4 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@
25
25
  * A player sees, downloads (/api/player/export) and deletes (/api/player/delete) everything of theirs.
26
26
  */
27
27
  import { handleFor } from './room.mjs';
28
+ import { isAiName } from './agents.mjs';
28
29
  import { cookieValue } from './stats.mjs';
29
30
  import { adoptStatements, bumpPlayerStats, dropPlayerData, fall, fallenList, getSave, listSaves, playerData, readPlayerStats, SAVE_LIMITS, savesSummary, wipeSaves, writeSaves } from './saves.mjs';
30
31
  import { bytesToB64url, randomToken, sha256Hex, verifyAssertion, verifyRegistration } from './webauthn.mjs';
@@ -147,8 +148,9 @@ const ROLE_WORDS = /^(?:the\s+)?(?:admin|administrator|mod|moderator|owner|staff
147
148
  /**
148
149
  * A display name, by the same rules as a room's typed names and handles: one line of at most 24 characters,
149
150
  * whitespace collapsed, no control, format or invisible characters (no zero-width or bidi tricks), at least one
150
- * letter or digit; never a role (admin, moderator, owner…) or the studio's own name, except for the owner. It is
151
- * always shown as text (escaped), never as markup. `max` lets a character's name run to 32.
151
+ * letter or digit; never a role (admin, moderator, owner…) or the studio's own name, except for the owner; and never
152
+ * ending in an AI or bot mark ("· AI", "(bot)"…), for anyone: only an agent's name says AI (0.16.0). It is always
153
+ * shown as text (escaped), never as markup. `max` lets a character's name run to 32.
152
154
  */
153
155
  export function cleanName(raw, { studio = '', owner = false, max = 24 } = {}) {
154
156
  const text = String(raw ?? '').normalize('NFKC')
@@ -161,6 +163,7 @@ export function cleanName(raw, { studio = '', owner = false, max = 24 } = {}) {
161
163
  if (!owner && (ROLE_WORDS.test(name) || (studio && name.toLowerCase() === String(studio).trim().toLowerCase()))) {
162
164
  return { ok: false, error: 'name-taken', message: 'that name belongs to the studio; pick another' };
163
165
  }
166
+ if (isAiName(name)) return { ok: false, error: 'name-ai', message: 'a person\'s name cannot end in "AI" or "bot": only an AI player is marked AI' };
164
167
  return { ok: true, name, cut: chars.length > max };
165
168
  }
166
169
 
@@ -299,7 +302,10 @@ async function meOf(env, player) {
299
302
  const e = await env.DB.prepare('SELECT email, verified_at FROM player_emails WHERE player = ?1').bind(player.id).first();
300
303
  if (e) email = { address: e.email, verified: Boolean(e.verified_at) };
301
304
  }
302
- return { id: player.id, name: player.name, named: Boolean(player.named), guest: Boolean(player.guest), owner: Boolean(player.owner), since: new Date(player.since).toISOString(), passkeys: Number(keys?.n) || 0, email };
305
+ // The servers this player belongs to (0.16.0; none before migration 0006).
306
+ let servers = [];
307
+ try { servers = ((await env.DB.prepare('SELECT game, server, role, home FROM server_members WHERE player = ?1 ORDER BY home DESC, seen_at DESC LIMIT 50').bind(player.id).all()).results ?? []).map((r) => ({ game: r.game, server: r.server, role: r.role, home: Number(r.home) === 1 })); } catch { servers = []; }
308
+ return { id: player.id, name: player.name, named: Boolean(player.named), guest: Boolean(player.guest), owner: Boolean(player.owner), since: new Date(player.since).toISOString(), passkeys: Number(keys?.n) || 0, email, servers };
303
309
  }
304
310
 
305
311
  const features = (env) => ({ accounts: Boolean(env.DB) && env.HOMIE_PREVIEW !== '1', email: Boolean(env.PLAYER_MAIL && env.PLAYER_MAIL_FROM) });
@@ -315,6 +321,8 @@ async function removePlayer(env, id) {
315
321
  env.DB.prepare('DELETE FROM player_emails WHERE player = ?1').bind(id),
316
322
  env.DB.prepare('DELETE FROM players WHERE id = ?1').bind(id),
317
323
  ]);
324
+ // Their servers' memberships too (0.16.0); a studio before migration 0006 has none to delete.
325
+ try { await env.DB.prepare('DELETE FROM server_members WHERE player = ?1').bind(id).run(); } catch { /* not migrated */ }
318
326
  await data.after();
319
327
  }
320
328