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:
@@ -140,6 +140,90 @@ module.exports = {
|
||||
stale: { type: 'boolean', example: false },
|
||||
nextWipe: { $ref: '#/components/schemas/RustNextWipe' },
|
||||
schedule: { $ref: '#/components/schemas/RustWipeSchedule' },
|
||||
titles: { $ref: '#/components/schemas/RustTitleSettings' },
|
||||
titlePush: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
description: 'What the last push of titles to this server found, since this site last started: how many players hold one and whether BetterChat was there to show them. Null before the first push.',
|
||||
properties: {
|
||||
count: { type: 'integer', example: 4 },
|
||||
betterChat: { type: 'boolean', example: true },
|
||||
at: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RustTitleSettings: {
|
||||
type: 'object',
|
||||
description: 'One server’s chat titles (phase 17, D135–D136): rules that rank the current wipe, in precedence order, and how many titles a player shows.',
|
||||
properties: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
enum: ['first', 'all', 'upto'],
|
||||
description: '`first` shows the first rule a player meets, `all` every one, `upto` at most `max`.',
|
||||
example: 'first',
|
||||
},
|
||||
max: { type: 'integer', minimum: 1, maximum: 5, example: 2 },
|
||||
rules: {
|
||||
type: 'array',
|
||||
maxItems: 10,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
stat: { type: 'string', enum: ['kills', 'npckills', 'playtime'], example: 'kills' },
|
||||
topN: { type: 'integer', minimum: 1, maximum: 10, example: 1 },
|
||||
text: {
|
||||
type: 'string',
|
||||
maxLength: 24,
|
||||
description: 'The title. `[`, `]`, `<`, `>`, `{` and `}` are taken out on save, so a title carries no markup of its own.',
|
||||
example: 'Top Killer',
|
||||
},
|
||||
color: { type: 'string', example: '#ff8800' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RustIntegrations: {
|
||||
type: 'object',
|
||||
description: 'Which optional mods a server has loaded right now (GET /admin/rust/servers/{id}/integrations), read live from the game.',
|
||||
properties: {
|
||||
ok: { type: 'boolean', example: true },
|
||||
status: { type: 'string', description: 'The sidecar client’s one-word status when the game could not be asked.', example: 'ok' },
|
||||
integrations: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
description: 'Null when the game could not be asked, or its plugin is older than protocol 12.',
|
||||
properties: {
|
||||
betterChat: {
|
||||
type: 'object',
|
||||
properties: { loaded: { type: 'boolean', example: true }, version: { type: 'string', example: '5.2.15' } },
|
||||
},
|
||||
popupNotifications: {
|
||||
type: 'object',
|
||||
properties: { loaded: { type: 'boolean', example: false } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RustVoice: {
|
||||
type: 'object',
|
||||
description: 'The voice news and `rust.announce` lines are said in (D140): a styled permission group, or none for plain chat.',
|
||||
properties: {
|
||||
voice: { type: 'string', description: 'A group name, or empty for plain chat.', example: 'staff' },
|
||||
options: {
|
||||
type: 'array',
|
||||
description: 'Every group that has a chat style — the only groups that can be a voice.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
group: { type: 'string', example: 'staff' },
|
||||
title: { type: 'string', example: '[Staff]' },
|
||||
format: { type: 'string', description: 'The line the voice makes, with `{message}` where the words go.', example: '[#55aaff][+15][Staff][/+][/#] [#ffffff][+15]{message}[/+][/#]' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RustLink: {
|
||||
@@ -309,6 +393,13 @@ module.exports = {
|
||||
example: '*',
|
||||
},
|
||||
permissions: { type: 'array', items: { type: 'string', example: 'kits.vip' } },
|
||||
chat: {
|
||||
type: 'object',
|
||||
nullable: true,
|
||||
description: 'The group’s BetterChat style — all twelve fields as text — or null for a group without one (D138).',
|
||||
additionalProperties: { type: 'string' },
|
||||
example: { Title: '[VIP]', TitleColor: '#ffaa55', ChatFormat: '{Title} {Username}: {Message}' },
|
||||
},
|
||||
members: {
|
||||
type: 'array',
|
||||
items: {
|
||||
@@ -366,6 +457,18 @@ module.exports = {
|
||||
description: 'The state of the mirror, per configured server.',
|
||||
items: { $ref: '#/components/schemas/RustPermissionSyncState' },
|
||||
},
|
||||
chatFields: {
|
||||
type: 'array',
|
||||
description: 'The twelve BetterChat group fields a style carries, with each one’s type and BetterChat’s default, for the style editor.',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', example: 'TitleColor' },
|
||||
type: { type: 'string', enum: ['int', 'title', 'color', 'size', 'bool', 'format'], example: 'color' },
|
||||
default: { type: 'string', nullable: true, example: '#55aaff' },
|
||||
},
|
||||
},
|
||||
},
|
||||
drift: {
|
||||
type: 'array',
|
||||
description: 'What a game holds that the site did not author. Reported, never undone.',
|
||||
@@ -376,9 +479,15 @@ module.exports = {
|
||||
serverId: { type: 'string', example: 'main' },
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: 'One of `grant`, `member`, `group-permission`.',
|
||||
description: 'One of `grant`, `member`, `group-permission`, or `chat-field` for a style field changed in game.',
|
||||
example: 'grant',
|
||||
},
|
||||
detail: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'For `chat-field`, the value the game holds now. Null for every other kind.',
|
||||
example: '#ff0000',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
description: 'A Steam id, or a group name.',
|
||||
@@ -660,6 +769,12 @@ module.exports = {
|
||||
name: { type: 'string', example: 'Main · Vanilla' },
|
||||
enabled: { type: 'boolean', example: true },
|
||||
on: { type: 'boolean', example: false },
|
||||
delivery: {
|
||||
type: 'string',
|
||||
enum: ['chat', 'popup'],
|
||||
description: 'Where the post goes when `on`: chat, or a popup — which needs PopupNotifications on that server (D142).',
|
||||
example: 'chat',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -924,6 +1039,12 @@ module.exports = {
|
||||
additionalProperties: { type: 'boolean' },
|
||||
example: { main: true },
|
||||
},
|
||||
newsDelivery: {
|
||||
type: 'object',
|
||||
description: 'A server id to where a news post goes on it: `chat` or `popup` (D142).',
|
||||
additionalProperties: { type: 'string', enum: ['chat', 'popup'] },
|
||||
example: { main: 'popup' },
|
||||
},
|
||||
map: {
|
||||
type: 'object',
|
||||
description: 'The live map’s switches. `fleet` maps a layer to an audience and `mates` to true or false; `servers` maps a server id to the same shape, where null follows the fleet.',
|
||||
|
||||
Reference in New Issue
Block a user