# Callbacks de componentes
> Trate botões e selects junto da definição visual, com IDs tipados e contexto de interação entregue pelo Core.

- Área: Componentes
- URL humana: https://ninenity.vercel.app/doc/callbacks
- URL Markdown: https://ninenity.vercel.app/doc/markdown/callbacks
- Pacotes e conceitos: setCallback, customId tipado, Auto defer

## Como o callback chega ao Core

1. **O builder registra** — Ao combinar `.setCustomId()` e `.setCallback()`, o ComponentBuilder guarda a função somente em memória.
2. **O Discord envia o clique** — A interação chega ao `InteractionCreate` com o mesmo `customId`.
3. **O Core resolve** — O runtime encontra o callback, valida o contexto e entrega `interaction`, `id` e `context`.
4. **A resposta é concluída** — Se o callback não responder, o Core chama `deferUpdate()` para evitar o estado de falha no componente.

> **Callbacks não são serializados:** Tokens e JSON preservam o layout, mas nunca código executável. Depois de importar um template, conecte os callbacks no código do bot ou use a exportação TypeScript do Builder Studio.

## Callback de botão

Defina o `customId` antes ou depois do callback; o registro acontece quando ambos estiverem presentes.

### approve-button.ts

```text

const approveButton = new ButtonBuilder()
  .setCustomId('project:approve')
  .setLabel('Aprovar projeto')
  .setStyle(ButtonStyle.Success)
  .setCallback(async (interaction, id, context) => {
    await approveProject({
      action: String(id),
      userId: interaction.user.id,
      guildId: interaction.guildId
    })

    await interaction.update({
      content: 'Projeto aprovado com sucesso.',
      components: []
    })

    console.log('[Component] customId=' + context.customId)
  })
```


O primeiro argumento é a interação real. O segundo é a parte lógica extraída do `customId`. O terceiro contém o ID completo, valores de select e o tipo do componente.

## IDs segmentados e inferência

O separador `:` transforma segmentos nomeados em um objeto tipado no callback.

### typed-id.ts

```text

new ButtonBuilder()
  .setCustomId('project:42:archive')
  .setLabel('Arquivar')
  .setStyle(ButtonStyle.Secondary)
  .setCallback(async (interaction, id) => {
    // Para IDs segmentados, o editor infere as partes disponíveis.
    console.log('[Project] callback=' + JSON.stringify(id))
    await interaction.deferUpdate()
  })
```


> **Escolha IDs estáveis:** Use IDs curtos que expressem domínio e ação, como `ticket:close` ou `profile:edit`. Dados sensíveis e payloads grandes pertencem ao banco, não ao `customId`.

## Callback de select

Os valores selecionados estão em `interaction.values` e também no `context` normalizado.

### environment-select.ts

```text

const environmentSelect = new StringSelectMenuBuilder()
  .setCustomId('project:environment')
  .setPlaceholder('Escolha o ambiente')
  .addOptions(
    option => option.setLabel('Produção').setValue('production'),
    option => option.setLabel('Desenvolvimento').setValue('development')
  )
  .setCallback(async (interaction, _id, context) => {
    const environment = context.value
    if (!environment) return

    await projectRepository.setEnvironment(
      interaction.guildId,
      environment
    )

    await interaction.update({
      content: 'Ambiente alterado para ' + environment,
      components: []
    })
  })
```


### seleção múltipla

```text

new UserSelectMenuBuilder()
  .setCustomId('team:members')
  .setMinValues(1)
  .setMaxValues(3)
  .setCallback(async (interaction, _id, context) => {
    const userIds = context.values
    await teamRepository.replaceMembers(interaction.guildId, userIds)
    await interaction.reply({
      content: userIds.length + ' membros selecionados.',
      flags: 64
    })
  })
```


## Reply, update ou deferUpdate?

| Método | Resultado |
| --- | --- |
| `interaction.update()` | Substitui a mensagem que contém o componente |
| `interaction.reply()` | Cria uma resposta separada ao clique |
| `interaction.deferUpdate()` | Confirma o clique sem mudar a mensagem |
| Nenhum | O Core faz `deferUpdate()` automaticamente |

### ação demorada

```text

.setCallback(async interaction => {
  await interaction.deferReply({ flags: 64 })

  const result = await runLongOperation()

  await interaction.editReply(
    result.ok ? 'Operação concluída.' : 'Não foi possível concluir.'
  )
})
```


## Autorização continua sendo sua regra

O runtime resolve o callback, mas a permissão de negócio precisa ser verificada no handler.

### callback protegido

```text

.setCallback(async interaction => {
  const member = interaction.member
  const canManage = member?.permissions?.has('ManageGuild')

  if (!canManage) {
    await interaction.reply({
      content: 'Você não pode executar esta ação.',
      flags: 64
    })
    return
  }

  await resetGuildConfiguration(interaction.guildId)
  await interaction.update({ content: 'Configuração restaurada.', components: [] })
})
```


- Valide permissões no momento do clique, não apenas ao montar a mensagem.
- Nunca confie em IDs ou valores recebidos sem validar existência e escopo.
- Para fluxos privados, prefira respostas efêmeras iniciadas pelo próprio usuário.
- Evite callbacks duplicados para o mesmo `customId` em módulos diferentes.
