Responder a botones y menús
Cuando alguien pulsa un botón o elige algo en un menú, Discord envía a tu bot una interacción. Tu bot tiene que responder en menos de 3 segundos.
Cómo funciona
Sección titulada «Cómo funciona»- Tu bot envía un mensaje con un botón que tiene
custom_id: "contador". - Un usuario pulsa el botón.
- Discord envía a tu bot una interacción de tipo
MESSAGE_COMPONENT(3) con esecustom_id. - Tu bot mira el
custom_id, decide qué hacer y responde (mensaje nuevo, editar el mensaje o abrir un modal).
Lo que recibe tu bot
Sección titulada «Lo que recibe tu bot»Dentro de la interacción, el campo data trae la información del componente:
| Campo | Tipo | Descripción |
|---|---|---|
custom_id |
string | El custom_id que pusiste en el componente. |
component_type |
entero | Tipo del componente (2 = botón, 3 = string select…). |
values |
array de strings | Solo en menús: los valores elegidos. |
resolved |
objeto | Solo en menús de usuario/rol/canal: los datos completos (usuarios, roles, canales) de lo elegido. |
{ "type": 3, "id": "1234567890", "token": "aW50ZXJhY3Rpb24...", "data": { "custom_id": "contador", "component_type": 2, "id": 3 }, "message": { "id": "...", "flags": 32768, "components": [] }}Tipos de respuesta
Sección titulada «Tipos de respuesta»| Tipo | Valor | ¿Qué hace? | ¿Cuándo usarlo? |
|---|---|---|---|
CHANNEL_MESSAGE_WITH_SOURCE |
4 |
Envía un mensaje nuevo. | Responder con información (puede ser efímero). |
DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE |
5 |
“Pensando…” y respondes después. | Cuando tardarás más de 3 s en tener el mensaje. |
DEFERRED_UPDATE_MESSAGE |
6 |
Confirma el clic sin mostrar nada; editas después. | Procesos lentos que acaban editando el mensaje. |
UPDATE_MESSAGE |
7 |
Edita el mensaje donde está el botón. | Contadores, paginación, cambiar el estado. |
MODAL |
9 |
Abre un formulario (modal). | Pedir texto, archivos u opciones al usuario. |
Ejemplo completo: un contador
Sección titulada «Ejemplo completo: un contador»Cada vez que alguien pulsa el botón, el mensaje se actualiza con el nuevo número (respuesta tipo 7).
{ "flags": 32768, "components": [ { "type": 17, "accent_color": 5793266, "components": [ { "type": 10, "content": "### Contador\nHas pulsado el botón **3** veces." }, { "type": 1, "components": [ { "type": 2, "style": 1, "label": "+1", "custom_id": "contador:3" }, { "type": 2, "style": 4, "label": "Reiniciar", "custom_id": "reiniciar" } ] } ] } ]}Responde a la interacción con un UPDATE_MESSAGE (7). El cuerpo es el mensaje completo nuevo:
{ "type": 7, "data": { "flags": 32768, "components": [ { "type": 17, "accent_color": 5793266, "components": [ { "type": 10, "content": "### Contador\nHas pulsado el botón **4** veces." }, { "type": 1, "components": [ { "type": 2, "style": 1, "label": "+1", "custom_id": "contador:4" }, { "type": 2, "style": 4, "label": "Reiniciar", "custom_id": "reiniciar" } ] } ] } ] }}import { Events, MessageFlags, ContainerBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
function counterMessage(count) { const container = new ContainerBuilder() .setAccentColor(0x5865f2) .addTextDisplayComponents((text) => text.setContent(`### Contador\nHas pulsado el botón **${count}** veces.`), ) .addActionRowComponents((row) => row.setComponents( new ButtonBuilder().setCustomId(`contador:${count}`).setLabel('+1').setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId('reiniciar').setLabel('Reiniciar').setStyle(ButtonStyle.Danger), ), ); return { flags: MessageFlags.IsComponentsV2, components: [container] };}
client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isButton()) return;
if (interaction.customId.startsWith('contador:')) { const count = Number(interaction.customId.split(':')[1]) + 1; await interaction.update(counterMessage(count)); // tipo 7 }
if (interaction.customId === 'reiniciar') { await interaction.update(counterMessage(0)); }});import discord
def counter_view(count: int) -> discord.ui.LayoutView: plus = discord.ui.Button(label="+1", style=discord.ButtonStyle.primary, custom_id=f"contador:{count}") reset = discord.ui.Button(label="Reiniciar", style=discord.ButtonStyle.danger, custom_id="reiniciar")
async def on_plus(interaction: discord.Interaction): await interaction.response.edit_message(view=counter_view(count + 1)) # tipo 7
async def on_reset(interaction: discord.Interaction): await interaction.response.edit_message(view=counter_view(0))
plus.callback = on_plus reset.callback = on_reset
view = discord.ui.LayoutView() view.add_item( discord.ui.Container( discord.ui.TextDisplay(f"### Contador\nHas pulsado el botón **{count}** veces."), discord.ui.ActionRow(plus, reset), accent_colour=0x5865F2, ) ) return view
# Para enviarlo:# await channel.send(view=counter_view(0))func counterComponents(count int) []discordgo.MessageComponent { color := 0x5865F2 return []discordgo.MessageComponent{ discordgo.Container{ AccentColor: &color, Components: []discordgo.MessageComponent{ discordgo.TextDisplay{Content: fmt.Sprintf("### Contador\nHas pulsado el botón **%d** veces.", count)}, discordgo.ActionsRow{Components: []discordgo.MessageComponent{ discordgo.Button{Label: "+1", Style: discordgo.PrimaryButton, CustomID: fmt.Sprintf("contador:%d", count)}, discordgo.Button{Label: "Reiniciar", Style: discordgo.DangerButton, CustomID: "reiniciar"}, }}, }, }, }}
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { if i.Type != discordgo.InteractionMessageComponent { return }
customID := i.MessageComponentData().CustomID count := 0 if strings.HasPrefix(customID, "contador:") { count, _ = strconv.Atoi(strings.TrimPrefix(customID, "contador:")) count++ }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseUpdateMessage, // tipo 7 Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsIsComponentsV2, Components: counterComponents(count), }, })})Responder con un mensaje efímero
Sección titulada «Responder con un mensaje efímero»Un mensaje efímero solo lo ve quien pulsó el botón. Suma los flags IS_COMPONENTS_V2 (32768) y EPHEMERAL (64) = 32832.
{ "type": 4, "data": { "flags": 32832, "components": [ { "type": 10, "content": "✅ Te has apuntado al evento. ¡Nos vemos allí!" } ] }}await interaction.reply({ flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral, components: [new TextDisplayBuilder().setContent('✅ Te has apuntado al evento. ¡Nos vemos allí!')],});view = discord.ui.LayoutView()view.add_item(discord.ui.TextDisplay("✅ Te has apuntado al evento. ¡Nos vemos allí!"))await interaction.response.send_message(view=view, ephemeral=True)s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, // tipo 4 Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsIsComponentsV2 | discordgo.MessageFlagsEphemeral, Components: []discordgo.MessageComponent{ discordgo.TextDisplay{Content: "✅ Te has apuntado al evento. ¡Nos vemos allí!"}, }, },})Leer lo que se eligió en un menú
Sección titulada «Leer lo que se eligió en un menú»En los menús, los valores elegidos llegan en data.values:
{ "custom_id": "elegir_color", "component_type": 3, "values": ["rojo", "azul"]}if (interaction.isStringSelectMenu() && interaction.customId === 'elegir_color') { const colores = interaction.values; // ['rojo', 'azul']}select = discord.ui.Select(custom_id="elegir_color", options=[...])
async def on_select(interaction: discord.Interaction): colores = select.values # ['rojo', 'azul']
select.callback = on_selectdata := i.MessageComponentData()if data.CustomID == "elegir_color" { colores := data.Values // []string{"rojo", "azul"} _ = colores}Editar después de responder
Sección titulada «Editar después de responder»Si usaste DEFERRED_UPDATE_MESSAGE (6) o quieres cambiar la respuesta más tarde, edita la respuesta original:
PATCH /webhooks/{application.id}/{interaction.token}/messages/@originalY para enviar mensajes adicionales (follow-ups):
POST /webhooks/{application.id}/{interaction.token}Siguiente paso
Sección titulada «Siguiente paso»Aprende a pedir datos al usuario con modales.