# Comandos, cartões e botões

Além de mensagens de texto, um bot pode:

- ter **comandos com "/"**: quem digita `/` no app vê a lista dos comandos dos bots do canal,
  com descrição e campos para preencher;
- mandar **cartões**: título, texto, campos lado a lado, imagens e uma borda colorida;
- pôr **botões** nas mensagens, que avisam o bot quando alguém clica;
- responder **só para a pessoa** que usou o comando (a resposta não fica no canal).

Funciona no app do computador, na web e no Android.

## Comandos

O bot cadastra a lista inteira de comandos de uma vez, normalmente ao conectar:

```ts
await client.setCommands([
  {
    name: 'dado',
    description: 'Rola um dado',
    options: [{ name: 'lados', description: 'Quantos lados', type: 'integer', min_value: 2, max_value: 100 }],
  },
  { name: 'ping', description: 'Responde pong' },
]);
```

Os comandos aparecem em todos os canais que o bot vê, em todos os servidores dele. Cadastrar de
novo troca a lista inteira.

| campo | |
|---|---|
| `name` | 1 a 32 letras minúsculas, números, `_` ou `-`. |
| `description` | 1 a 100 caracteres. |
| `options` | Até 25 campos (abaixo). Os obrigatórios vêm antes dos opcionais. |

Cada opção:

| campo | |
|---|---|
| `name`, `description` | Como no comando. |
| `type` | `string`, `integer`, `number`, `boolean`, `user` (alguém do servidor), `channel` (um canal de texto) ou `role` (um cargo). |
| `required` | Se precisa ser preenchida. |
| `choices` | Até 25 `{ name, value }`: o app mostra uma lista em vez de um campo livre. Para `string`, `integer` e `number`. |
| `min_value`, `max_value` | Limites para `integer` e `number`. |

O servidor confere o que a pessoa preencheu antes de avisar o bot: tipo, obrigatórios, limites e
escolhas. Um bot pode ter até 100 comandos.

## Quando alguém usa um comando

O bot recebe o evento `INTERACTION_CREATE`, e só ele recebe. Na biblioteca, é o evento
`interaction`:

```ts
client.on('interaction', async (i) => {
  if (i.command === 'dado') {
    const lados = i.option<number>('lados') ?? 6;
    await i.reply(`🎲 ${1 + Math.floor(Math.random() * lados)}`);
  }
});
```

| | |
|---|---|
| `i.type` | `'command'` ou `'button'`. |
| `i.command` | O nome do comando. |
| `i.option(nome)` | O valor de uma opção; `undefined` se ficou vazia. `user`, `channel` e `role` chegam como ids. |
| `i.user`, `i.member` | Quem usou, e como membro do servidor. |
| `i.channelId`, `i.guildId` | Onde. |
| `i.reply(...)` | Responde com uma mensagem. |
| `i.update(...)` | Só em cliques: muda a mensagem do botão. |

O app mostra "Bot está pensando…" até a resposta chegar, e "não respondeu" depois de 15
segundos sem nada. O bot tem até **15 minutos** para responder, e pode responder mais de uma
vez (as respostas seguintes são mensagens novas). A resposta aparece no canal com "Fulano usou
/dado" em cima.

Se o bot estiver offline, o app avisa quem tentou usar o comando, e nada é enviado.

### Resposta só para a pessoa

```ts
await i.reply({ content: 'Você não tem permissão para isso.', ephemeral: true });
```

Só quem usou o comando vê, com "Só você vê esta mensagem · Dispensar". Ela não fica guardada:
some ao recarregar o app. Não leva botões.

## Cartões

```ts
await client.send(channelId, {
  cards: [
    {
      author: { name: 'Previsão do tempo' },
      title: 'São Paulo',
      url: 'https://exemplo.com/sp',
      description: 'Nublado, com **chuva** à tarde.',
      color: 0xa594f9,
      fields: [
        { name: 'Máxima', value: '27 °C', inline: true },
        { name: 'Mínima', value: '18 °C', inline: true },
      ],
      thumbnail: { url: 'https://exemplo.com/nuvem.png' },
      footer: { text: 'Atualizado' },
      timestamp: Date.now(),
    },
  ],
});
```

| campo | limite | |
|---|---|---|
| `title` | 256 | Em negrito; vira link se houver `url`. |
| `description` | 4096 | Markdown, como uma mensagem. |
| `url` | | Link do título (`http(s)`). |
| `color` | | A cor da borda, `0xRRGGBB`. |
| `author` | nome 256 | `{ name, url?, icon_url? }`, uma linha pequena em cima. |
| `fields` | 25 campos; nome 256, valor 1024 | `{ name, value, inline? }`. Os `inline` ficam lado a lado. |
| `image` | | `{ url }`: imagem grande embaixo. |
| `thumbnail` | | `{ url }`: imagem pequena ao lado do texto. |
| `footer` | 2048 | `{ text, icon_url? }`. |
| `timestamp` | | Em ms; aparece no rodapé. |

Até 5 cartões por mensagem, com até 6000 caracteres somando todos. Um cartão precisa de pelo
menos título, texto, campo, imagem ou autor.

As imagens vão por links `http(s)` (ou por imagens enviadas ao Zynko, `/api/media/...`). O servidor
busca as imagens pelas pessoas, como faz com as prévias de links, então os sites não ficam sabendo
quem viu. Só bots mandam cartões e botões; as mensagens de pessoas não levam.

## Botões

```ts
await client.send(channelId, {
  content: 'Votação: pizza ou hambúrguer?',
  components: [
    [
      { style: 'primary', emoji: '🍕', label: 'Pizza', custom_id: 'voto:pizza' },
      { style: 'secondary', emoji: '🍔', label: 'Hambúrguer', custom_id: 'voto:burger' },
      { style: 'link', label: 'Cardápio', url: 'https://exemplo.com/cardapio' },
    ],
  ],
});
```

`components` são fileiras de botões: até 5 fileiras com até 5 botões cada.

| campo | |
|---|---|
| `style` | `primary` (lilás), `secondary` (cinza), `success` (verde), `danger` (vermelho) ou `link`. |
| `label` | Até 80 caracteres. Precisa de `label` ou `emoji`. |
| `emoji` | Um emoji antes do texto. |
| `custom_id` | Até 100 caracteres, diferente em cada botão. Volta para o bot no clique. Todos menos `link`. |
| `url` | Só no `link`: abre o endereço, sem avisar o bot. |
| `disabled` | Aparece apagado e não clica. |

Um clique chega como uma interação do tipo `button`:

```ts
client.on('interaction', async (i) => {
  if (i.type !== 'button') return;
  if (i.customId?.startsWith('voto:')) {
    votos.set(i.user.id, i.customId.slice(5));
    await i.update({ content: `Votação: pizza ou hambúrguer? (${votos.size} votos)` });
  }
});
```

`i.update(...)` muda o texto, os cartões ou os botões da mensagem que foi clicada (o que você
mandar; o resto fica). Para tirar os botões, mande `components: []`. Também dá para responder
com `i.reply(...)`, como num comando.

## Editar depois

O bot edita as próprias mensagens com cartões e botões:

```ts
const msg = await client.send(channelId, { content: 'Contando…' });
await msg.edit({ content: 'Pronto!', cards: [{ title: 'Resultado', description: '42' }], components: [] });
```

## Pela API, sem biblioteca

| ação | rota |
|---|---|
| Cadastrar os comandos | `PUT /users/@me/commands` com a lista |
| Ver os comandos | `GET /users/@me/commands` |
| Responder | `POST /interactions/{id}/{token}/callback` |

O `INTERACTION_CREATE` traz `id` e `token`. A resposta leva `type` (`"message"`, o padrão, ou
`"update"` para mudar a mensagem do botão), `content`, `cards`, `components` e `ephemeral`. Os
detalhes estão em [Rotas](https://docs.zynko.org/api/rotas.md#interacoes) e [Eventos](https://docs.zynko.org/api/eventos.md#interaction-create).
