Registrar comandos
Comandos globais valem em todo servidor onde o bot está; comandos de servidor valem só ali e na hora (ótimos para testar). PUT substitui a lista inteira do escopo. Tipos: 1 chat (/nome), 2 menu de usuário, 3 menu de mensagem. Opções: 3 texto, 4 inteiro, 5 booleano, 6 usuário, 7 canal, 8 cargo, 10 número; com choices (até 25), minValue/maxValue, minLength/maxLength ou autocomplete. Limites: 100 comandos de chat, 15 de usuário e 15 de mensagem por escopo.
PUT /api/v1/applications/APP_ID/commands
[
{ "name": "dado", "description": "Rola um dado",
"options": [{ "type": 4, "name": "lados", "description": "Quantos lados", "minValue": 2, "maxValue": 100 }] },
{ "name": "jogo", "description": "Escolhe um jogo",
"options": [{ "type": 3, "name": "nome", "description": "Nome", "autocomplete": true }] },
{ "name": "kick", "description": "Só admins", "defaultMemberPermissions": "0" },
{ "type": 2, "name": "Ver perfil" }
]
defaultMemberPermissions esconde o comando de quem não tem aquelas permissões ("0" = só administradores).
A interação
Pelo gateway chega INTERACTION_CREATE com id, token, type (2 comando, 3 componente, 4 autocomplete, 5 modal), user, member (cargos e permissões), channelId, serverId e data com as opções já tipadas.
Responder
POST /api/v1/interactions/{id}/{token}/callback, sem Authorization (o token é a credencial), uma única vez e em até 3 segundos:
4mensagem (comflags: 64fica efêmera: só quem usou vê, e ela nunca entra no histórico do canal).5adiada: aparece “pensando…”; depois edite comPATCH /webhooks/{appId}/{token}/messages/@original.6confirma um clique sem mudar nada ·7edita a mensagem do componente.8escolhas do autocomplete (até 25) ·9abre um modal.
Depois da resposta inicial, o token vale 15 minutos para follow-ups (POST /webhooks/{appId}/{token}) e para editar ou apagar essas mensagens. Se o app não responder a tempo, a pessoa vê “A interação falhou”.
Botões, menus e modais
Linhas (type 1) com até 5 botões ou 1 menu de seleção. Botões: 1 primário, 2 secundário, 3 sucesso, 4 perigo (todos com customId) e 5 link (com url, não gera interação). Menus de texto: até 25 opções, minValues/maxValues. Modais: título e até 5 campos de texto (curto ou parágrafo), cada um em uma linha.
Receber por HTTP
Salve uma URL de interações no portal e as interações passam a chegar por POST no seu servidor, assinadas com Ed25519: valide X-Signature-Ed25519 sobre X-Signature-Timestamp + corpo com a chave pública do app e responda 401 se não bater. Ao salvar, o Falafy manda um PING (responda {"type":1}) e um pedido com assinatura errada (precisa dar 401). A resposta pode vir no próprio corpo da resposta HTTP.
import crypto from 'node:crypto';
import http from 'node:http';
const PUBLIC_KEY = process.env.FALAFY_PUBLIC_KEY; // hex, no portal
const spki = Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(PUBLIC_KEY, 'hex')]);
const key = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' });
http.createServer((req, res) => {
let raw = '';
req.on('data', (c) => (raw += c));
req.on('end', () => {
const sig = req.headers['x-signature-ed25519'];
const ts = req.headers['x-signature-timestamp'];
const ok = sig && ts && crypto.verify(null, Buffer.from(ts + raw), key, Buffer.from(sig, 'hex'));
if (!ok) return res.writeHead(401).end();
const i = JSON.parse(raw);
res.writeHead(200, { 'content-type': 'application/json' });
if (i.type === 1) return res.end(JSON.stringify({ type: 1 })); // PING -> PONG
res.end(JSON.stringify({ type: 4, data: { content: 'Olá!', flags: 64 } }));
});
}).listen(8080);