Callbacks de componentes
Trate botões e selects junto da definição visual, com IDs tipados e contexto de interação entregue pelo Core.
Como o callback chega ao Core#
- 1O builder registra
Ao combinar
.setCustomId()e.setCallback(), o ComponentBuilder guarda a função somente em memória. - 2O Discord envia o clique
A interação chega ao
InteractionCreatecom o mesmocustomId. - 3O Core resolve
O runtime encontra o callback, valida o contexto e entrega
interaction,idecontext. - 4A resposta é concluída
Se o callback não responder, o Core chama
deferUpdate()para evitar o estado de falha no componente.
Callback de botão#
Defina o customId antes ou depois do callback; o registro acontece quando ambos estiverem presentes.
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.
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() })Callback de select#
Os valores selecionados estão em interaction.values e também no context normalizado.
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: [] }) })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 |
.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.
.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
customIdem módulos diferentes.