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:
70
server/model/permissions/voice.js
Normal file
70
server/model/permissions/voice.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// ── The voice the module's own lines are said in (phase 17, D140) ──────────
|
||||
//
|
||||
// One fleet setting (§33.4 reading 5): the name of a permission group that has
|
||||
// a BetterChat style, or nothing for plain chat. News lines and `rust.announce`
|
||||
// lines are then said in that group's title and colours — composed here, said by
|
||||
// our plugin with no player as the sender, so they look right whether or not
|
||||
// BetterChat is loaded.
|
||||
//
|
||||
// The style is read at the moment a line is said, never copied into the
|
||||
// setting: an operator who recolours the group changes the voice with it, and a
|
||||
// group whose style is taken away stops being a voice rather than leaving a
|
||||
// stale one behind.
|
||||
|
||||
const settingsDb = require('../visibility/visibility.db')
|
||||
const chatStyle = require('./chatStyle')
|
||||
const db = require('./permissions.db')
|
||||
const model = require('./permissions.model')
|
||||
|
||||
const VOICE_KEY = 'announce.voice'
|
||||
|
||||
/** The chosen group's name, or '' for plain chat. */
|
||||
async function chosen() {
|
||||
return (await settingsDb.getSetting(VOICE_KEY)) || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* The format a line is said in right now, or null for plain chat — which is
|
||||
* also the answer when the chosen group has since lost its style or gone.
|
||||
*/
|
||||
async function currentFormat() {
|
||||
const group = await chosen()
|
||||
if (!group) return null
|
||||
|
||||
const fields = await db.getGroupChat(group)
|
||||
return fields ? chatStyle.voiceFormat(fields) : null
|
||||
}
|
||||
|
||||
/** The setting, and every group that could be a voice, for the admin page. */
|
||||
async function describe() {
|
||||
const [voice, rows] = await Promise.all([chosen(), db.listGroupChat()])
|
||||
const options = []
|
||||
|
||||
for (const [group, fields] of model.chatByGroup(rows)) {
|
||||
const format = chatStyle.voiceFormat(fields)
|
||||
if (format) options.push({ group, title: fields.Title || group, format })
|
||||
}
|
||||
|
||||
return { voice, options: options.sort((a, b) => a.group.localeCompare(b.group)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose the voice. A group is accepted only when it has a style a voice can be
|
||||
* made from; '' goes back to plain chat. Resolves `{ ok }` or
|
||||
* `{ ok: false, message }`.
|
||||
*/
|
||||
async function choose(group, userId = null) {
|
||||
const name = model.normaliseName(group)
|
||||
|
||||
if (name) {
|
||||
const fields = await db.getGroupChat(name)
|
||||
if (!fields || !chatStyle.voiceFormat(fields)) {
|
||||
return { ok: false, message: `The group "${name}" has no chat style, so it cannot be a voice. Give it one under Permissions first.` }
|
||||
}
|
||||
}
|
||||
|
||||
await settingsDb.setSetting(VOICE_KEY, name, userId)
|
||||
return { ok: true, voice: name }
|
||||
}
|
||||
|
||||
module.exports = { VOICE_KEY, chosen, currentFormat, describe, choose }
|
||||
Reference in New Issue
Block a user