Five read-only commands registered with api.registerSlashCommands: /status, /wipe, /top, /online and /clan (D126). Every refusal is private, and any answer narrower than public (online names, a clan roster) goes to the caller alone (D127). No command asks a sidecar. The next wipe (D128, D130): six nullable columns on rust_servers, a pure nextWipe(row, now) with the zone arithmetic through Intl, computed on every read. The public server shape gains nextWipe; the admin shape gains the stored schedule; PUT /admin/rust/servers/:id takes the six fields and writes them only when wipeRule is present. server/commands joins ci/bundle.json, which checkBundle caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
61 lines
2.6 KiB
JavaScript
61 lines
2.6 KiB
JavaScript
// ── `/status [server]` — is it up, how full, when did it wipe ───────────────
|
|
//
|
|
// Public at every setting: everything here is on the public server list already.
|
|
// The count is a number and names nobody; the names are `/online`'s business.
|
|
|
|
const c = require('./common')
|
|
|
|
/** One line per server, for the fleet view. */
|
|
function summary(server, now) {
|
|
const parts = [server.online ? `Online · ${c.playerCount(server)}` : 'Offline']
|
|
if (server.wipedAt) parts.push(`wiped ${c.ago(server.wipedAt, now)}`)
|
|
return parts.join(' · ')
|
|
}
|
|
|
|
function detail(server, now) {
|
|
const fields = [
|
|
{ name: 'Status', value: server.online ? 'Online' : 'Offline', inline: true },
|
|
{ name: 'Players', value: server.online ? c.playerCount(server) : '—', inline: true },
|
|
]
|
|
if (server.worldSize) {
|
|
fields.push({ name: 'Map', value: server.seed != null ? `${server.worldSize} · seed ${server.seed}` : String(server.worldSize), inline: true })
|
|
}
|
|
if (server.wipedAt) fields.push({ name: 'Last wipe', value: c.when(server.wipedAt, now), inline: false })
|
|
const next = c.nextWipeText(server, now)
|
|
if (next) fields.push({ name: 'Next wipe', value: next, inline: false })
|
|
|
|
// An offline server says when it was last heard from, which is the difference
|
|
// between "down for a restart" and "gone for a week". A server nothing has ever
|
|
// heard from says so rather than printing the epoch.
|
|
if (!server.online) {
|
|
fields.push({ name: 'Last seen', value: server.lastSeenAt ? c.when(server.lastSeenAt, now) : 'never', inline: false })
|
|
}
|
|
return { title: server.name, url: c.serverUrl(server), fields }
|
|
}
|
|
|
|
async function handler({ options }) {
|
|
const now = Date.now()
|
|
const picked = c.pickServer(await c.listServers(now), options && options.server)
|
|
const refusal = c.pickRefusal(picked)
|
|
if (refusal) return refusal
|
|
if (picked.server) return detail(picked.server, now)
|
|
|
|
// An embed takes 25 fields. A fleet larger than that is a site that has a
|
|
// server list page, and the title links to it.
|
|
return {
|
|
title: 'Rust servers',
|
|
url: c.pageUrl('/rust'),
|
|
fields: picked.all.slice(0, 25).map((s) => ({ name: s.name, value: summary(s, now), inline: false })),
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
name: 'status',
|
|
description: 'Is a Rust server up, how many are on, and when it last wiped',
|
|
options: [{ name: 'server', type: 'string', description: 'Server name or id (all servers when left out)', required: false }],
|
|
// Everyone: nothing here is narrower than public, and the gates that DO matter
|
|
// in other commands are resolved inside their handlers.
|
|
access: 'everyone',
|
|
handler,
|
|
}
|