Files
website/client/src/lib/shardEvents.js
wtclaude 12d50fd615
All checks were successful
PR Checks / bot-install (pull_request) Successful in 13s
PR Checks / client-build (pull_request) Successful in 22s
PR Checks / server-tests (pull_request) Successful in 11m13s
chore(quality): resolve SonarQube code smells across website
Clears the 124 CODE_SMELL findings from the SonarQube scan (server, client,
and bot). All changes are behaviour-preserving refactors — no route, protocol,
schema, or config changes — verified against the full server (381) and client
(43) test suites plus a clean client build.

By rule:
- S3776 (20, cognitive complexity): extract helpers/handlers so each function
  drops under the threshold — shard model upsert builders, page/wiki update,
  block validation, notification stream mapping (dispatch table), SSO mobile
  login, shard ingest deps, uo-link socket backfill/connect, the bot slash-
  command dispatchers + discord manager, and the Shard/UserDetail/HeroEditor/
  CharacterStats React components.
- S4624 (34, nested template literals): pull inner templates into locals /
  a withQs() helper; rewrite shardEvents.describe() as a formatter table.
- S3358 (35, nested ternaries): lift to if/else vars, lookup maps, small
  components, or guarded JSX expressions.
- S6479 (12, array-index React keys): key by stable content instead of index
  (two in-editor lists left as-is; index matches their by-index edit model).
- S6353 (6): [0-9]/[^0-9] -> \d/\D.  S125 (5): reword state-shape comments that
  parsed as code.  S3800/S3782 (botScore): JSDoc-type PATH_WEIGHTS tuples.
- S6481 (2): memoize Auth/Site context values (and SiteContext brand).
- S4144: dedupe HeroEditor upload handler into useImageUpload().
- S1126 (2), S6035, S5869 (redundant A-Z under /i), S5843 (town-name regex ->
  prefix list): assorted one-liners.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 04:35:39 -05:00

129 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Shared formatting for shard events — used by the public Shard page, the
// Activity feed, and the admin live feed. One place decides how each kind reads
// and which category/badge it belongs to.
function nameOf(who) {
if (!who) return 'Someone'
if (typeof who === 'string') return who
return who.name || who.acct || 'Someone'
}
const n = (v) => Number(v || 0).toLocaleString()
// A one-line human description of each event kind, keyed by kind. Each formatter
// takes the payload and returns a string. Conditional suffixes are pulled into
// locals so no template literal is nested inside another.
const DESCRIBERS = {
'vendor.sale': (p) => {
const qty = p.amount > 1 ? ` ×${p.amount}` : ''
return `${p.itemType || 'An item'}${qty} sold for ${n(p.price)}gp`
},
'player.death': (p) => {
const by = p.killer ? ` by ${nameOf(p.killer)}` : ''
return `${nameOf(p.who)} was slain${by}`
},
'player.murdered': (p) => {
const by = p.murderer ? ` by ${nameOf(p.murderer)}` : ''
return `${nameOf(p.victim)} was murdered${by}`
},
'mob.killed': (p) => `${nameOf(p.killer)} killed ${nameOf(p.killed)}`,
'skill.gain': (p) => {
const base = p.base != null ? ` (${p.base})` : ''
return `${nameOf(p.who)} gained ${p.skill}${base}`
},
'fame.change': (p) => `${nameOf(p.who)}s fame changed to ${n(p.new)}`,
'karma.change': (p) => `${nameOf(p.who)}s karma changed to ${n(p.new)}`,
'quest.complete': (p) => `${nameOf(p.who)} completed “${p.quest}`,
'house.decay': (p) => {
const region = p.region ? `${p.region}` : ''
return `${p.name || 'A house'} is now ${p.to || p.stage}${region}`
},
'mob.login': (p) => `${nameOf(p.who)} entered the world`,
'mob.logout': (p) => `${nameOf(p.who)} left the world`,
'economy.supply': (p) => `Gold supply: ${n(p.gold)} across ${n(p.accounts)} accounts`,
'server.hello': (p) => `Shard online — ${n(p.accounts)} accounts, ${n(p.mobiles)} mobiles`,
'server.shutdown': () => 'Shard shut down',
'server.crashed': (p) => {
const err = p.error ? `: ${p.error}` : ''
return `Shard crashed${err}`
},
'champ.update': (p) => {
const where = p.name || p.type || 'A champion spawn'
if (p.status === 'active' && p.bossUp) {
const boss = p.boss ? ` (${p.boss})` : ''
return `${where}: boss is up${boss}`
}
if (p.status === 'active') {
const level = p.level != null ? ` — level ${p.level}` : ''
return `${where} is active${level}`
}
if (p.status === 'cooldown') return `${where} is on cooldown`
return `${where} is ${p.status || 'idle'}`
},
'champ.remove': () => `A champion spawn ended`,
// Support (help-page) queue + in-game moderation (admin channel only)
'page.new': (p) => `New ${p.type || 'help'} page from ${nameOf(p.sender)}`,
'page.updated': (p) => {
const claimed = p.handled ? ' (claimed)' : ''
return `Help page from ${nameOf(p.sender)} updated${claimed}`
},
'page.closed': (p) => `Help page ${p.pageId || ''} closed`,
'admin.audit': (p) => {
const on = p.target ? ` on ${p.target}` : ''
const origin = p.origin ? ` [${p.origin}]` : ''
return `${p.actor || 'Staff'} ${p.action || 'acted'}${on}${origin}`
},
// Staff / sensitive (admin channel only)
'audit.set': (p) =>
`${nameOf(p.staff) || 'Staff'} set ${p.prop} on ${p.target || p.targetSerial} (${p.old}${p.new})`,
'audit.command': (p) => {
const args = p.args ? ` ${p.args}` : ''
return `${nameOf(p.staff) || 'Staff'} ran ${p.command}${args}`
},
'cheat.fastwalk': (p) => {
const ip = p.ip ? ` (${p.ip})` : ''
return `Fast-walk flagged: ${nameOf(p.who)}${ip}`
},
'account.login.attempt': (p) => {
const ip = p.ip ? ` from ${p.ip}` : ''
return `Login attempt: ${p.acct}${ip}`
},
'gold.change': (p) => {
const sign = p.delta >= 0 ? '+' : ''
return `${p.acct}: gold ${sign}${n(p.delta)}${n(p.new)}`
},
}
// A one-line human description of an event. Accepts either a stored event
// (with .payload) or a raw live frame (fields at top level).
export function describe(ev) {
const fmt = DESCRIBERS[ev.kind]
return fmt ? fmt(ev.payload || ev) : ev.kind
}
// Category grouping for the filter tabs.
// Vendor sales are intentionally NOT a public category — they are owner-private
// (a linked player sees their own under the portal). The admin live feed still
// describes vendor.sale via describe() below.
export const CATEGORIES = [
{ id: 'all', label: 'All', kinds: null },
{ id: 'pvp', label: 'Deaths & PvP', kinds: ['player.death', 'player.murdered', 'mob.killed'] },
{ id: 'progress', label: 'Progression', kinds: ['skill.gain', 'fame.change', 'karma.change', 'quest.complete'] },
{ id: 'world', label: 'World', kinds: ['house.decay', 'mob.login', 'mob.logout', 'server.hello', 'server.shutdown', 'server.crashed', 'economy.supply'] },
]
const CATEGORY_OF = (() => {
const m = {}
for (const c of CATEGORIES) if (c.kinds) for (const k of c.kinds) m[k] = c.id
return m
})()
export function categoryOf(kind) {
return CATEGORY_OF[kind] || 'other'
}
// Short badge label for a kind (the part after the dot, title-cased-ish).
export function kindLabel(kind) {
return String(kind || '').replace(/[._]/g, ' ')
}