Pular para o conteúdo

Interações

Alguém acionou algo do seu App, e você tem poucos segundos para dizer o que faz. A entrega chega pelo Gateway ou por um POST assinado no seu endpoint — e nunca pelos dois.

O seu código

app.onInteraction(async (interaction) => {
  await interaction.reply("pong");
});

É o mesmo handler nos dois caminhos de entrega — pelo Gateway ou por POST assinado no seu endpoint. O SDK esconde a diferença de propósito: ela é de transporte, e trocar de caminho não pode ser reescrever o bot.

app.onInteraction(async (interaction) => {
  await interaction.defer();                    // "estou pensando"
  const resposta = await algoLento();
  await interaction.deferredReply(resposta);
  await interaction.followUp("e mais isto", { private: true });
});

decline() existe para quando o App decide não responder — é diferente de sumir, e quem acionou vê a frase certa.

app.onInteraction(async (interaction) => {
  await interaction.defer();                              // fecha o ACK dentro dos 3 s
  await interaction.deferredReply(await algoLento());     // 60 s a partir daqui
});

O ciclo, em uma volta

pessoa aciona
  → o Trivo grava a interação e ENTREGA ao App
  → o App CONFIRMA em segundos: reply, defer, modal ou decline
     (no autocomplete: suggest ou decline, em 1 s)
  → se foi defer, o App RESPONDE em até 60 s
  → e pode acrescentar até 5 SEGUIMENTOS em 10 minutos

A máquina é de mão única: confirmada, uma interação não volta a estar aberta. A transição é atômica no servidor, contra as duas portas de confirmação ao mesmo tempo — a primeira que chegar ganha, e a segunda recebe INTERACTION_ALREADY_CONFIRMED.

Os tipos

O campo type da interação diz o que foi acionado. O vocabulário é fechado:

TipoO que é
commandUm comando de barra.
command_on_userUm comando sobre uma pessoa, pelo menu de clique direito.
command_on_messageUm comando sobre uma mensagem, pelo menu de clique direito.
autocompleteO cliente pedindo sugestões enquanto a pessoa digita. Prazo bem menor.
componentUm botão ou menu de seleção de uma resposta anterior.
formUm formulário enviado.

Referência de transporte

Caminho 1: pelo Gateway

O que viaja no fio, para transparência, depuração e compatibilidade — não é um segundo jeito de construir App. Se você está escrevendo um App, o SDK já faz isto.

Se o seu App tem uma conexão aberta e nenhum endpoint HTTP ativo, a interação chega como o evento interaction_create, da família interactions — e o SDK a entrega ao seu onInteraction já embrulhada:

{
  "type": "interaction_create",
  "serverId": "1234567890123456789",
  "appId": "9876543210987654321",
  "interaction": {
    "id": "3f6b1f7a-0e2c-4a1d-9b8e-5c4d3e2f1a0b",
    "type": "command",
    "serverId": "1234567890123456789",
    "channelId": "1111111111111111111",
    "userId": "2222222222222222222",
    "data": { },
    "expiresInMs": 3000
  }
}

expiresInMs é duração, e não instante: o relógio do prazo é o do servidor. Uma reentrega pela janela de retomada chega com o valor já descontado, e pode chegar em zero — nesse caso a confirmação será recusada, e está certo assim.

Caminho 2: o seu endpoint

O outro caminho de entrega é um POST assinado no seu servidor, e ele substitui o Gateway em vez de somar-se a ele: com o endpoint ativo, a interação vai por lá e só por lá. Configurar, provar que ele está de pé e entender os quatro estados é assunto de Endpoint de interações; o que prova que a entrega é nossa está em Assinatura das entregas.

O que não muda é o seu código: o onInteraction acima é o mesmo nos dois caminhos, e é para isso que o SDK esconde a diferença.

Responder

await interaction.reply("pong");                 // confirma e responde
await interaction.reply("só para você", { private: true });
await interaction.defer();                       // confirma sem responder ainda
await interaction.deferredReply("pronto");       // depois do defer, em até 60 s
await interaction.followUp("e mais isto");       // até 5, em 10 minutos
await interaction.decline();                     // confirma dizendo que não faz
await interaction.showModal({                    // abre um formulário
  id: "sugestao",
  title: "Mandar sugestão",
  fields: [
    { type: "text", id: "titulo", label: "Título", required: true, maxLength: 60 },
    { type: "paragraph", id: "corpo", label: "Detalhes" },
  ],
});
await interaction.suggest([{ name, value }]);    // só em autocomplete

Por baixo são três rotas, e elas respondem { "interaction": { "id", "state", "followups", "expiresAt" } } — mais messageId quando a resposta foi pública.

Editar ou apagar o que você acabou de responder

Uma resposta pública grava uma mensagem no canal, e o desfecho traz o endereço dela. É o que fecha o ciclo interaction → resposta → seguimento → edit/delete: as rotas de mensagem já aceitam credencial de App, e faltava só o App saber para onde apontar.

const { interaction: feito } = await interaction.reply("pong");
if (feito.messageId) {
  await app.rest.editMessage({
    serverId: interaction.serverId,
    channelId: interaction.channelId,
    messageId: feito.messageId,
    content: "pong (corrigido)",
  });
}

messageId é opcional porque ele legitimamente não vem: na resposta privada não existe linha em mensagens, e no defer e no decline não há resposta ainda. Devolver null ali sugeriria uma mensagem que se perdeu. deferredReply e followUp públicos também o trazem.

Referência de transporte

As três rotas por baixo dos sete gestos

O que viaja no fio, para transparência, depuração e compatibilidade — não é um segundo jeito de construir App. Se você está escrevendo um App, o SDK já faz isto.

RotaCorpoQuando
POST /api/interacoes/{interacao}/confirmartype: reply | defer | decline | modal | suggest. reply exige content não vazio; suggest exige choices, e só serve o autocomplete.A primeira resposta, dentro do prazo de confirmação.
POST /api/interacoes/{interacao}/respostacontent; opcionais private e components.Depois de defer, dentro de 60 s.
POST /api/interacoes/{interacao}/seguimentocontent; opcionais private e components.Até 5, em 10 minutos. Não muda o estado.

O atalho do corpo

No caminho HTTP, o corpo da sua resposta ao POST assinado é literalmente o mesmo JSON de /confirmar — e usá-lo economiza uma ida à rede dentro de um prazo de 3 segundos. Não é um mecanismo paralelo: é um atalho para uma rota que continua existindo, e as duas portas disputam a mesma transição atômica.

// a sua resposta ao POST do Trivo
{ "type": "reply", "content": "pong" }

Um corpo que não case com o esquema não vira erro: você respondeu 2xx, o contador de falhas do endpoint zera, mas a interação continua aberta e expira. Se você não consegue deduplicar entregas, não responda privado pelo corpo — confirme com {"type":"defer"} e mande a resposta pela rota REST.

O autocompletar

Quando alguém digita numa opção que você registrou com "autocomplete": true, o Trivo abre uma interação de tipo autocomplete e a entrega pelo mesmo caminho de todas as outras — mesma credencial, mesma assinatura, mesmo Gateway. O que muda é o relógio: 1 segundo, e não 3, porque do outro lado alguém está com o dedo no teclado.

// o que chega em data
{ "command": { "publicId": "5001", "name": "clima", "path": "clima" },
  "arguments": [{ "name": "cidade", "type": "string", "value": "mar" }],
  "focused": "cidade",
  "focusedType": "string",
  "focusedLimits": { "maxLength": 40 } }

// o que você responde, na mesma volta
{ "type": "suggest", "choices": [
  { "name": "Maringá", "value": "maringa" },
  { "name": "Marabá",  "value": "maraba" } ] }

Atrasar não é erro na cara de quem digita. Se você não responder a tempo, ou responder torto, a lista simplesmente não aparece — quem está escrevendo segue escrevendo. É de propósito: sugestão é ajuda, e ajuda que falha não pode virar susto no meio de uma frase. O que você perde é o balde e a reputação do endpoint, como em qualquer outra interação.

A resposta privada não é mensagem

Com private: true, o texto vira um evento endereçado a quem acionou. Ele não entra no histórico de ninguém, não persiste, não aparece em busca e não volta numa recarga. É o teto de 5 seguimentos que impede o token de virar canal de mensagem direta por fora do funil.