Phase 7 of TEAMS.md. `api.registerSlashCommands` stops throwing: a module registers a command's DEFINITION and its HANDLER together, the bot pulls the definitions over the internal listener and runs none of our code, and the handler executes here — forced by the bot container having no `modules` volume, and the right boundary anyway. Registration validates what Discord would reject as a batch (names, description lengths, the four option types, required-before-optional), because the bot registers the whole set in one PUT and a single bad entry costs every command including the bot's own. Commands are not namespaced under their owner — there is no dot in Discord's name grammar — so collisions are first-come with the holder named. The dispatcher is the access boundary: `linked` has no Discord equivalent, so the platform-side permission default can only ever be advertising. It resolves the actor by `auth_providers.kind` rather than the id slug, treats a banned account as unlinked, bounds a handler under the bot's own timeout, and keeps `ok` outside the envelope so a handler cannot forge it. Liveness is asked at both the pull and the dispatch. The registries have no removal path, so a module an operator disables at runtime would otherwise keep a live handler behind a command Discord still advertises. Co-Authored-By: Claude <noreply@anthropic.com>
57 lines
2.5 KiB
JavaScript
57 lines
2.5 KiB
JavaScript
const botConfig = require('../../../model/botConfig/botConfig.model')
|
|
const slashCommands = require('../../../utils/slashCommands')
|
|
const log = require('../../../utils/logger')('internal')
|
|
|
|
// GET /internal/bot-config — called by the bot process on its own boot so a
|
|
// restart self-reconnects without any admin-panel interaction. Returns the
|
|
// DECRYPTED token — this route must never be reachable outside the private
|
|
// compose network (see requireInternalKey + deployment notes).
|
|
async function getBotConfig(req, res) {
|
|
try {
|
|
const config = await botConfig.getWithToken()
|
|
if (!config) return res.json({ enabled: false, token: null, guildId: null })
|
|
return res.json({ enabled: config.enabled, token: config.token, guildId: config.guildId })
|
|
} catch (err) {
|
|
log.error('internal.getBotConfig', err)
|
|
return res.status(500).json({ message: 'Internal Server Error' })
|
|
}
|
|
}
|
|
|
|
// GET /internal/commands — the registered slash-command definitions, pulled by
|
|
// the bot on `ready` and again whenever it is nudged (TEAMS.md §7.1).
|
|
//
|
|
// `version` is `modules.version()`, the counter every module state change bumps.
|
|
// The bot holds the value it registered with and re-PUTs only when it differs,
|
|
// which is what makes DEREGISTRATION free: the bot's single whole-set
|
|
// `REST.put(applicationGuildCommands)` means a module that is gone is simply
|
|
// absent from the next pull, with nobody having to remember to unregister it.
|
|
function listCommands(req, res) {
|
|
return res.json(slashCommands.definitions())
|
|
}
|
|
|
|
// POST /internal/commands/dispatch — run one command and answer with the
|
|
// envelope. Never 500s on a handler's behalf: `dispatch` catches per handler and
|
|
// reports `{ ok: false, reason }`, so the bot always has something to render and
|
|
// a module's failure is its own.
|
|
async function dispatchCommand(req, res) {
|
|
const { command, options, platform, platformUserId, guildId } = req.body || {}
|
|
if (!command) return res.status(400).json({ ok: false, reason: 'unknown' })
|
|
try {
|
|
const result = await slashCommands.dispatch({
|
|
command,
|
|
options: options && typeof options === 'object' ? options : {},
|
|
platform: platform || 'discord',
|
|
platformUserId,
|
|
guildId,
|
|
})
|
|
return res.json(result)
|
|
} catch (err) {
|
|
// dispatch() is documented never to throw; if it ever does, that is core's
|
|
// bug and not the module's, and it is logged as one.
|
|
log.error('internal.dispatchCommand', err)
|
|
return res.status(500).json({ ok: false, reason: 'error' })
|
|
}
|
|
}
|
|
|
|
module.exports = { getBotConfig, listCommands, dispatchCommand }
|