feat(rust): chat titles, BetterChat group styles, the voice and popups (phase 17)

PLAN.md §33, D134-D143. Protocol 12.

- Chat titles (D135-D137): per-server rules (stat, top N, text, colour)
  that rank the current wipe, and a mode (first | all | up to N). Worked
  out once in model/titles and read three ways: pushed whole to the game by
  a new titleSync loop (on change, restart or wipe), and on every
  leaderboard row as `titles`. Admin: PUT /servers/:id/titles.
- Group styles (D138, D139): a site group may carry all twelve BetterChat
  fields (rust_perm_group_chat). They ride perm.sync with `expect` from the
  pushed ledger, which gains a value column; a field changed in game is a
  `chat-field` drift row with the game's value, adopted into the style or
  put back. A withdrawn style is one `chat-group` retirement, never for
  `default`, cleared from the ledger only once BetterChat removed it.
- The voice (D140): one fleet setting naming a styled group; news and
  rust.announce chat lines carry its format and the plugin says them with
  no sender. Admin: GET/PUT /voice.
- Popups (D141, D142): rust.announce gains `delivery` (still version 1,
  from rust.options.delivery); each server gains news_delivery beside the
  news switch; `popup-unavailable` is not retried.
- GET /servers/:id/integrations reads, live, which optional mods a server
  has loaded. README lists BetterChat and PopupNotifications as optional.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-25 17:48:22 -05:00
parent fb5a581a94
commit 1b70cef5be
43 changed files with 3371 additions and 72 deletions

View File

@@ -38,17 +38,18 @@ rows here; the website core never learns there is more than one.
| Public | `GET /api/v1/public/rust/servers` — every server and what it last reported |
| Public | `GET …/servers/:id` — one server, or a `404`; the only route under `:id` that can say a server does not exist |
| Public | `GET …/servers/:id/events` — the feed, served from a default-deny allowlist (`server/catalogue.js`) |
| Public | `GET …/servers/:id/leaderboard` — per wipe, or all-time as those rows summed |
| Public | `GET …/servers/:id/leaderboard` — per wipe, or all-time as those rows summed; each row carries the player's chat titles |
| Public | `GET …/servers/:id/wipes` and `…/online` |
| Public | `GET …/servers/:id/clans` — the server's clans, best score first (public: names nobody) |
| Public | `GET /api/v1/public/rust/clans/:externalId` — one clan, and its roster inside the roster audience |
| Player | `GET /api/v1/player/rust/servers` — the server list, on the authenticated tier |
| Admin | `GET/PUT/DELETE /api/v1/admin/rust/servers` and `POST …/:id/test` — the `PUT` carries the wipe schedule |
| Admin | `GET/PUT /api/v1/admin/rust/visibility` — who may see who is online, fleet-wide and per server |
| Admin | `PUT …/servers/:id/titles`, `GET …/servers/:id/integrations`, `GET/PUT /api/v1/admin/rust/voice` — chat titles, the optional mods a server has, and the announcement voice |
| Pages | `/rust` — the server list, and the module's landing page |
| Pages | `/rust/servers/:id` — one server: feed, leaderboard, who is on, wipes, clans |
| Pages | `/rust/clans/:externalId` — one clan, with core's Team notify, activity and forum in three module slots |
| Pages | `/admin/rust/servers` — add, edit, test and remove servers, and set each one's wipe schedule |
| Pages | `/admin/rust/servers` — add, edit, test and remove servers, set each one's wipe schedule and chat titles, and choose the announcement voice |
| Discord | `/status`, `/wipe`, `/top`, `/online`, `/clan` — read-only, answered from this module's tables |
| Teams | The deployment's Team provider: a first-party Rust clan is a Team |
| Slot | `site.footer.status` — a live server/player count in core's footer |
@@ -81,6 +82,23 @@ forced wipe (first Thursday, 19:00 UK time) — plus an optional one-off date th
computed wipe. It is computed on every read and never stored, so it cannot go stale after a wipe.
The server list, the server page, the Android app and `/wipe` all show it.
**Two uMod plugins are optional, and the module works without either** (`docs/modules/rust/PLAN.md`
§33). The bridge plugin's hard requirements are **Kits** and **ZoneManager**.
- **BetterChat** (LaserHydra, 5.2.15). With it: **chat titles** an operator sets per server — "top 3
playtime", "#1 kills" on the current wipe — shown in game chat and beside the name on the web and
app leaderboards; and a **chat style** on any permission group this site authors, all twelve of
BetterChat's group fields, mirrored like the group's permissions (a field changed in game is
reported, never overwritten). Without it the titles still show on the web and the app, and the
styles wait until it is installed.
- **PopupNotifications** (k1lly0u, 0.2.1). With it: `rust.announce` and a server's news posts can be
a popup instead of a chat line. Without it a popup is refused with a sentence saying so, and chat
works as before.
News and event lines said in chat can wear one styled group's title and colours — the
**announcement voice**, chosen in Admin → Rust servers. The line is said by the bridge plugin with no
player as its sender, so it looks the same with or without BetterChat.
**The Discord commands answer from the tables, never from a game server**, inside core's three-second
budget. Every refusal is private. **An answer narrower than public goes to the caller alone:** a
moderator's `/online` in a public channel shows the names to the moderator, never to the channel, and