Bot de roles para Discord: botones para que cada uno elija sus roles (Python y JavaScript)
Un bot de roles publica un mensaje con botones y cada persona de tu servidor de Discord pulsa los que quiere: el juego al que juega, si quiere avisos de eventos, su región… Aquí está entero en discord.py y en discord.js, unas 70 líneas: un comando /panel-roles para publicar el mensaje y los botones que dan y quitan cada rol. Sin base de datos y sin el lío de los roles por reacción.
Botones, no reacciones
Durante años los «roles por reacción» fueron lo normal: reaccionas con un emoji y el bot te da el rol. Funcionan, pero tienen pegas: el bot necesita el intent de reacciones, tiene que guardar qué emoji va con qué rol de qué mensaje (y recordarlo tras reiniciarse), y quien reacciona no sabe si ha funcionado hasta que mira sus roles.
Los botones resuelven las tres cosas. Cada botón lleva escrito dentro el id de su rol, así que el bot no guarda nada: cuando alguien pulsa, Discord le manda el botón pulsado y el bot sabe qué rol dar, aunque el mensaje se publicara hace meses y el bot se haya reiniciado mil veces. Y la respuesta («Ya tienes @Minecraft») le sale solo a quien ha pulsado, sin llenar el canal.
Si tu servidor de Discord es de comunidad, Discord trae también su propia «Incorporación», con preguntas al entrar que dan roles, sin bot. El bot sirve para todos los servidores, para cambiar de roles en cualquier momento desde un canal y para tener varios paneles (juegos, avisos, colores) donde quieras.
¿Prefieres no montarlo tú? Hosting de bots de Discord desde 1,50 € al mes, sin permanencia y con devolución íntegra en 72 horas. O crea un bot de Discord gratis, sin tarjeta.
1. Crea el bot en Discord
Crea la aplicación y saca el token
En discord.com/developers/applications, «New Application»; en la pestaña «Bot», «Reset Token» y copia el token. Es la contraseña del bot: no lo compartas ni lo subas a GitHub. Si es tu primer bot, el proceso paso a paso está en tu primer bot en Python.
Invítalo con los permisos justos
En «OAuth2» → «URL Generator», marca «bot» y «applications.commands», y en los permisos «Send Messages», «Embed Links» y «Manage Roles». Abre el enlace que sale y elige tu servidor. No hace falta ningún intent especial («Privileged Gateway Intents» se quedan apagados).
Pon su rol por encima de los que va a dar
En «Ajustes del servidor» → «Roles», arrastra el rol del bot por encima de los roles que se podrán coger. Un bot no puede dar un rol que esté a su altura o por encima, aunque tenga «Manage Roles». Los roles de otros bots y @everyone no se pueden dar nunca.
2. El código en Python (discord.py)
Tres ficheros en una carpeta. El token va en .env, sin comillas. Hace falta Python 3.10 o más nuevo.
discord.py
python-dotenvDISCORD_TOKEN=pega-aquí-tu-tokenimport os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv() # lee el token del fichero .env
# Solo hacen falta los intents por defecto: los botones llegan como interacciones.
client = discord.Client(intents=discord.Intents.default())
tree = app_commands.CommandTree(client)
@tree.command(name="panel-roles", description="Publica un mensaje con botones para coger roles")
@app_commands.guild_only()
@app_commands.default_permissions(manage_roles=True) # solo lo ve quien gestiona roles
@app_commands.describe(
titulo="Lo que pone encima de los botones",
rol1="Un rol que se podrá coger",
rol2="Otro rol (opcional)",
rol3="Otro rol (opcional)",
rol4="Otro rol (opcional)",
rol5="Otro rol (opcional)",
)
async def panel_roles(
interaction: discord.Interaction,
titulo: str,
rol1: discord.Role,
rol2: discord.Role | None = None,
rol3: discord.Role | None = None,
rol4: discord.Role | None = None,
rol5: discord.Role | None = None,
):
roles = [r for r in (rol1, rol2, rol3, rol4, rol5) if r]
mio = interaction.guild.me.top_role
# Un bot no puede dar un rol igual o más alto que el suyo, ni los de otros bots o @everyone.
imposibles = [r.name for r in roles if r >= mio or r.managed or r.is_default()]
if imposibles:
await interaction.response.send_message(
f"No puedo dar {', '.join(imposibles)}: pon mi rol por encima en «Ajustes del servidor» → «Roles».",
ephemeral=True,
)
return
vista = discord.ui.View(timeout=None)
for rol in roles:
# El id del rol va en el propio botón: no hace falta base de datos.
vista.add_item(discord.ui.Button(label=rol.name, custom_id=f"rol:{rol.id}"))
tarjeta = discord.Embed(title=titulo, description="Pulsa un botón para ponerte o quitarte el rol.", color=0x5B4AE8)
await interaction.channel.send(embed=tarjeta, view=vista)
await interaction.response.send_message("Panel publicado.", ephemeral=True)
@client.event
async def on_interaction(interaction: discord.Interaction):
# Llega cada clic en un botón, también en paneles publicados antes de reiniciar el bot.
if interaction.type is not discord.InteractionType.component:
return
custom_id = interaction.data.get("custom_id", "")
if not custom_id.startswith("rol:") or interaction.guild is None:
return
rol = interaction.guild.get_role(int(custom_id[4:]))
if rol is None:
await interaction.response.send_message("Ese rol ya no existe.", ephemeral=True)
return
try:
if rol in interaction.user.roles:
await interaction.user.remove_roles(rol, reason="Panel de roles")
texto = f"Te he quitado {rol.mention}."
else:
await interaction.user.add_roles(rol, reason="Panel de roles")
texto = f"Ya tienes {rol.mention}."
except discord.Forbidden:
texto = "No puedo darte ese rol: avisa a quien gestiona el servidor (mi rol tiene que estar por encima)."
await interaction.response.send_message(texto, ephemeral=True)
@client.event
async def on_ready():
await tree.sync() # publica /panel-roles
print(f"Conectado como {client.user}")
client.run(os.environ["DISCORD_TOKEN"])3. El mismo bot en JavaScript (discord.js)
Con Node.js 18 o más nuevo. Las respuestas que solo ve quien pulsa se marcan con MessageFlags.Ephemeral, que es la forma actual en discord.js 14 (la vieja, ephemeral: true, avisa de que va a desaparecer).
{
"name": "bot-roles",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"discord.js": "^14.17.0",
"dotenv": "^16.4.0"
}
}DISCORD_TOKEN=pega-aquí-tu-tokenrequire("dotenv").config(); // lee el token del fichero .env
const {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
Client,
EmbedBuilder,
Events,
GatewayIntentBits,
InteractionContextType,
MessageFlags,
PermissionFlagsBits,
SlashCommandBuilder,
} = require("discord.js");
// Solo hace falta Guilds: los botones llegan como interacciones.
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const comando = new SlashCommandBuilder()
.setName("panel-roles")
.setDescription("Publica un mensaje con botones para coger roles")
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) // solo lo ve quien gestiona roles
.addStringOption((o) => o.setName("titulo").setDescription("Lo que pone encima de los botones").setRequired(true))
.addRoleOption((o) => o.setName("rol1").setDescription("Rol").setRequired(true));
for (let n = 2; n <= 5; n++) comando.addRoleOption((o) => o.setName(`rol${n}`).setDescription("Otro rol (opcional)"));
const privado = (content) => ({ content, flags: MessageFlags.Ephemeral });
client.on(Events.InteractionCreate, async (i) => {
if (!i.inCachedGuild()) return;
if (i.isChatInputCommand() && i.commandName === "panel-roles") {
const roles = [1, 2, 3, 4, 5].map((n) => i.options.getRole(`rol${n}`)).filter(Boolean);
const mio = i.guild.members.me.roles.highest;
// Un bot no puede dar un rol igual o más alto que el suyo, ni los de otros bots o @everyone.
const imposibles = roles.filter((r) => r.managed || r.id === i.guildId || r.comparePositionTo(mio) >= 0);
if (imposibles.length) {
const nombres = imposibles.map((r) => r.name).join(", ");
return i.reply(privado(`No puedo dar ${nombres}: pon mi rol por encima en «Ajustes del servidor» → «Roles».`));
}
// El id del rol va en el propio botón: no hace falta base de datos.
const fila = new ActionRowBuilder().addComponents(
roles.map((r) => new ButtonBuilder().setCustomId(`rol:${r.id}`).setLabel(r.name).setStyle(ButtonStyle.Secondary)),
);
const tarjeta = new EmbedBuilder()
.setColor(0x5b4ae8)
.setTitle(i.options.getString("titulo"))
.setDescription("Pulsa un botón para ponerte o quitarte el rol.");
await i.channel.send({ embeds: [tarjeta], components: [fila] });
return i.reply(privado("Panel publicado."));
}
// Llega cada clic en un botón, también en paneles publicados antes de reiniciar el bot.
if (i.isButton() && i.customId.startsWith("rol:")) {
const rol = i.guild.roles.cache.get(i.customId.slice(4));
if (!rol) return i.reply(privado("Ese rol ya no existe."));
let texto;
try {
if (i.member.roles.cache.has(rol.id)) {
await i.member.roles.remove(rol, "Panel de roles");
texto = `Te he quitado ${rol}.`;
} else {
await i.member.roles.add(rol, "Panel de roles");
texto = `Ya tienes ${rol}.`;
}
} catch {
texto = "No puedo darte ese rol: avisa a quien gestiona el servidor (mi rol tiene que estar por encima).";
}
await i.reply(privado(texto));
}
});
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set([comando.toJSON()]); // publica /panel-roles
console.log(`Conectado como ${c.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);4. Publica tu primer panel
/panel-roles solo lo ven quienes tienen el permiso de gestionar roles. Si quieres que lo use otra gente, se cambia sin tocar el código en «Ajustes del servidor» → «Integraciones» → tu bot.
Arranca el bot
En tu ordenador: «pip install -r requirements.txt» y «python app.py» (o «npm install» y «node index.js»). Espera a que diga «Conectado como…». El comando puede tardar unos minutos en aparecer la primera vez.
Crea los roles
En «Ajustes del servidor» → «Roles», crea los que se podrán coger (por ejemplo Minecraft, Valheim y Avisos de eventos) y deja el del bot por encima de todos.
Escribe /panel-roles en el canal
Ponle un título («¿A qué juegas?») y elige de uno a cinco roles. El bot publica el mensaje con un botón por rol y te contesta «Panel publicado.» solo a ti. Si algún rol está por encima del suyo, te lo dice y no publica nada.
Pruébalo
Pulsa un botón: te da el rol y te lo dice. Vuelve a pulsarlo y te lo quita. Para más de cinco roles, publica otro panel debajo (por ejemplo uno de juegos y otro de avisos).
Si algo no va
«No puedo darte ese rol»: el rol del bot ha quedado por debajo (por ejemplo, alguien movió los roles después de publicar el panel) o le han quitado «Manage Roles». Súbelo y vuelve a pulsar; el panel no hay que publicarlo otra vez.
Al pulsar sale que la interacción ha fallado («This interaction failed», con Discord en inglés): el bot está apagado. Los botones siguen ahí aunque el bot no esté, pero nadie contesta. En cuanto vuelve a estar encendido, los paneles de antes funcionan solos.
«Ese rol ya no existe»: se borró el rol. Borra ese panel y publica uno nuevo con los roles de ahora. Si solo le cambias el nombre al rol, el botón sigue funcionando, pero enseña el nombre viejo hasta que publiques otro panel.
Un rol de avisos sirve para mencionarlo en los anuncios: solo le llega a quien lo ha cogido. En «Ajustes del servidor» → «Roles» → ese rol decides si cualquiera puede mencionarlo o solo quien gestiona el servidor. Y para que un rol de juego abra canales, dale permiso de ver esos canales en su configuración.
5. Déjalo encendido siempre (gratis)
Con los botones pasa lo mismo que con cualquier bot: si vive en tu ordenador, cuando lo apagas los paneles siguen en el canal pero al pulsar sale que la interacción ha fallado, y quien entra a tu servidor de noche se queda sin sus roles.
En NapNodes: crea el bot gratis (nace en Node.js; si es el de Python, en «Configuración» → «Lenguaje», «Pasar a Python»), sube los tres ficheros en «Archivos» y pulsa «Encender». Las librerías de requirements.txt o package.json se instalan solas al arrancar, y app.py o index.js es el fichero de inicio que viene puesto. Como el bot no guarda nada, puedes moverlo, reinstalarlo o pasarlo de Python a JavaScript y los paneles publicados siguen funcionando.
El bot gratis tiene 256 MB, de sobra para un bot de roles, y hay que renovarlo con un clic cada semana: si se te pasa, se apaga hasta que lo enciendes (y a los 14 días sin renovar se borra, con aviso antes). Si no quieres acordarte, el plan Mini (512 MB) está encendido siempre sin renovar nada (precios de los bots). Todo el montaje, en la guía de hosting para tu bot.
Ideas para ampliarlo
Roles excluyentes (elegir uno solo, como colores o regiones): al dar uno, quita los demás del mismo panel, que están en los otros botones del mensaje. Un menú desplegable en vez de botones, para paneles de hasta 25 roles en un solo mensaje. Emojis en los botones (setEmoji en discord.js, emoji= en discord.py). O un rol que solo se pueda coger con cierta antigüedad en el servidor, comprobando la fecha de entrada del miembro antes de darlo.
Preguntas frecuentes
¿Cómo hago un bot de autoroles para Discord?
Con un comando que publica un mensaje con botones, uno por rol, y un evento que recibe cada clic: si la persona ya tiene el rol, se lo quita; si no, se lo da. El código completo, en Python y en JavaScript, está en esta guía.
¿Es mejor roles por reacción o con botones?
Con botones. El bot no necesita guardar qué emoji va con qué rol ni el intent de reacciones, funciona igual tras reiniciarse y contesta en privado a quien pulsa si le ha dado o quitado el rol.
¿Por qué mi bot no da los roles?
Casi siempre es la jerarquía: el rol del bot tiene que estar por encima del rol que da, y el bot necesita el permiso «Manage Roles». Los roles de otros bots y @everyone no se pueden dar.
¿Siguen funcionando los botones si reinicio el bot?
Sí, con el código de esta guía: cada botón lleva dentro el id de su rol y el bot atiende cualquier clic, sea de un panel nuevo o de uno publicado antes de reiniciar. Solo hace falta que el bot esté encendido.
¿Cuántos roles caben en un panel?
Cinco con el código de esta guía, que usa una fila de botones. Para más, publica varios paneles, o cambia los botones por un menú desplegable, que admite hasta 25 opciones.
¿Te ha servido esta guía?
¿Lo montamos?
Hosting de bots de Discord desde 1,50 € al mes, sin sobreventa: tu RAM es solo tuya. Y si quieres empezar sin pagar, tienes un bot de Discord gratis, sin tarjeta.
Herramienta gratis: Texto de colores en Discord. Escribe en Discord en rojo, verde o amarillo, con fondo, negrita y subrayado: el bloque ansi listo para pegar, y la línea para tu bot.
Sigue leyendo
- Bot de niveles para Discord: XP por mensajes, ranking y roles (Python y JavaScript)
Haz tu propio bot de niveles de Discord en Python o JavaScript: XP por mensajes contra el spam, /nivel, /ranking y roles al subir, guardado en SQLite.
- Bot de bienvenida para Discord: mensaje al entrar y rol automático (Python y JavaScript)
Haz un bot de Discord que salude a quien entra y le dé un rol solo, en Python o JavaScript: el permiso que siempre falta, la jerarquía de roles y las normas.
- Cómo hacer un bot de Discord en Python (discord.py), paso a paso
Haz tu primer bot de Discord en Python con discord.py: crear la aplicación, el token, comandos con barra (/hola) y tenerlo encendido siempre, gratis.