Bot de sugerencias para Discord: /sugerir, votos con botones y respuesta del equipo (Python y JavaScript)
«¿Y si hacemos un evento los viernes?» se pierde en el chat en diez minutos. Con un canal de sugerencias, cada idea sale en su tarjeta, la comunidad vota con dos botones y el equipo la acepta, la rechaza o la deja en estudio con un motivo que ve todo el mundo. Este bot lo hace con tres comandos, en discord.py o en discord.js, sin base de datos y sin intents privilegiados.
Qué hace el bot
Cualquiera escribe /sugerir con su idea y el bot la publica en el canal de sugerencias como una tarjeta con número, autor, votos y estado. Debajo, 👍 y 👎: un voto por persona, se puede cambiar de opinión y pulsar otra vez el mismo botón lo quita. La tarjeta se actualiza al momento.
Quien gestiona el servidor elige el canal con /canal-sugerencias y contesta con /responder: «Aceptada», «Rechazada» o «En estudio», con un motivo opcional que sale en la tarjeta. Al aceptar o rechazar, los botones se apagan y la tarjeta cambia de color; «En estudio» deja seguir votando.
Todo va en sugerencias.json, junto al bot: un reinicio no pierde ni las sugerencias ni los votos, y los botones de las tarjetas viejas siguen funcionando. Para que nadie llene el canal, cada persona puede mandar una cada 2 minutos, y una sugerencia nunca menciona a nadie aunque lleve una @.
¿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.
Antes del bot: lo que Discord ya trae
Un canal de foro sirve para sugerencias sin bot: cada idea es una publicación con su conversación, y con etiquetas («Aceptada», «Rechazada») el equipo las ordena. Lo que no tiene es un recuento de votos claro ni una respuesta oficial que se vea de un vistazo.
Las encuestas de Discord sirven para elegir entre opciones que ya tienes, no para recoger ideas. Si quieres votos por idea, numeradas y con la decisión del equipo, es lo que hace este bot.
1. Crea el bot e invítalo
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 con calma está en tu primer bot en Python o en JavaScript. Los «Privileged Gateway Intents» se quedan apagados.
Invítalo con tres permisos
En «OAuth2» → «URL Generator», marca «bot» y «applications.commands», y en los permisos «View Channels», «Send Messages» y «Embed Links» (en total, el número 19456). Con la calculadora de permisos de bot los ves en castellano y te da el enlace.
Prepara el canal
Un canal de texto, por ejemplo #sugerencias, donde el bot pueda escribir. Si quieres que solo se sugiera con el comando, quita a @everyone «Enviar mensajes» en ese canal y dáselo al rol del bot: los comandos de barra siguen funcionando, porque los permite «Usar comandos de aplicaciones».
2. El bot 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 json
import os
import time
from pathlib import Path
from typing import Optional
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv() # lee el token del fichero .env
# Todo se guarda junto al bot: un reinicio no pierde ni las sugerencias ni los votos.
DATOS = Path("sugerencias.json")
ESPERA = 120 # segundos entre dos sugerencias de la misma persona
ESTADOS = {
"aceptada": ("Aceptada", "✅ Aceptada", discord.Color.green()),
"rechazada": ("Rechazada", "❌ Rechazada", discord.Color.red()),
"estudio": ("En estudio", "🔎 En estudio", discord.Color.gold()),
}
ABIERTA = ("Abierta", "💡 Abierta a votos", discord.Color.blurple())
CERRADAS = ("aceptada", "rechazada") # con estas ya no se vota
NADA = discord.AllowedMentions.none() # una sugerencia nunca avisa a nadie
ultima: dict[int, float] = {} # cuándo sugirió cada uno por última vez
def leer() -> dict:
return json.loads(DATOS.read_text("utf-8")) if DATOS.exists() else {}
def guardar(datos: dict) -> None:
DATOS.write_text(json.dumps(datos, indent=2, ensure_ascii=False), "utf-8")
def tarjeta(numero: str, s: dict) -> discord.Embed:
_, titulo, color = ESTADOS.get(s["estado"], ABIERTA)
e = discord.Embed(title=f"Sugerencia n.º {numero}", description=s["texto"], color=color)
e.add_field(name="De", value=f"<@{s['autor']}>")
e.add_field(name="Votos", value=f"👍 {len(s['si'])} · 👎 {len(s['no'])}")
e.add_field(name="Estado", value=titulo)
if s["motivo"]:
e.add_field(name="Respuesta del equipo", value=s["motivo"], inline=False)
return e
class Votos(discord.ui.View):
"""Los dos botones. custom_id fijo y timeout=None: siguen funcionando tras reiniciar."""
def __init__(self, cerrada: bool = False):
super().__init__(timeout=None)
for boton in self.children:
boton.disabled = cerrada
async def votar(self, interaction: discord.Interaction, lado: str):
datos = leer()
sugerencias = datos.get(str(interaction.guild_id), {}).get("sugerencias", {})
numero, s = next(((n, s) for n, s in sugerencias.items() if s["mensaje"] == interaction.message.id), (None, None))
if s is None or s["estado"] in CERRADAS:
await interaction.response.send_message("Esta sugerencia ya no admite votos.", ephemeral=True)
return
quien = interaction.user.id
otro = "no" if lado == "si" else "si"
if quien in s[otro]:
s[otro].remove(quien) # cambia de opinión: un solo voto por persona
if quien in s[lado]:
s[lado].remove(quien) # pulsar otra vez el mismo botón quita el voto
else:
s[lado].append(quien)
guardar(datos)
await interaction.response.edit_message(embed=tarjeta(numero, s))
@discord.ui.button(emoji="👍", style=discord.ButtonStyle.success, custom_id="sugerencia:si")
async def si(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.votar(interaction, "si")
@discord.ui.button(emoji="👎", style=discord.ButtonStyle.danger, custom_id="sugerencia:no")
async def no(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.votar(interaction, "no")
class Bot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default()) # sin intents privilegiados
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
self.add_view(Votos()) # vuelve a escuchar los botones de las sugerencias ya publicadas
await self.tree.sync() # publica los comandos
bot = Bot()
@bot.tree.command(name="canal-sugerencias", description="Elige el canal donde se publican las sugerencias")
@app_commands.guild_only()
@app_commands.default_permissions(manage_guild=True) # de serie, solo quien gestiona el servidor
@app_commands.describe(canal="El canal de sugerencias")
async def canal_sugerencias(interaction: discord.Interaction, canal: discord.TextChannel):
mios = canal.permissions_for(interaction.guild.me)
if not (mios.view_channel and mios.send_messages and mios.embed_links):
await interaction.response.send_message(
f"En {canal.mention} me faltan «Ver canal», «Enviar mensajes» o «Insertar enlaces».", ephemeral=True)
return
datos = leer()
datos.setdefault(str(interaction.guild_id), {"siguiente": 1, "sugerencias": {}})["canal"] = canal.id
guardar(datos)
await interaction.response.send_message(f"Las sugerencias irán a {canal.mention}.", ephemeral=True)
@bot.tree.command(name="sugerir", description="Manda una sugerencia para el servidor")
@app_commands.guild_only()
@app_commands.describe(idea="Tu sugerencia, en un mensaje")
async def sugerir(interaction: discord.Interaction, idea: app_commands.Range[str, 10, 1000]):
antes = ultima.get(interaction.user.id)
if antes is not None and time.monotonic() - antes < ESPERA:
quedan = int(ESPERA - (time.monotonic() - antes)) + 1
await interaction.response.send_message(f"Espera {quedan} s antes de mandar otra.", ephemeral=True)
return
datos = leer()
servidor = datos.get(str(interaction.guild_id))
canal = bot.get_channel(servidor["canal"]) if servidor and "canal" in servidor else None
if canal is None:
await interaction.response.send_message(
"Aún no hay canal de sugerencias: alguien que gestione el servidor tiene que usar /canal-sugerencias.", ephemeral=True)
return
# El número se reserva antes de publicar: dos sugerencias a la vez no lo comparten.
numero = str(servidor["siguiente"])
servidor["siguiente"] += 1
guardar(datos)
s = {"autor": interaction.user.id, "texto": idea, "si": [], "no": [], "estado": "abierta", "motivo": None,
"canal": canal.id}
try:
mensaje = await canal.send(embed=tarjeta(numero, s), view=Votos(), allowed_mentions=NADA)
except discord.HTTPException as error:
print(f"No he podido publicar la sugerencia {numero}: {error!r}")
await interaction.response.send_message(
"No he podido publicarla: revisa mis permisos en el canal de sugerencias.", ephemeral=True)
return
s["mensaje"] = mensaje.id
fresco = leer() # se relee: mientras se publicaba, alguien ha podido votar otra
fresco[str(interaction.guild_id)]["sugerencias"][numero] = s
guardar(fresco)
ultima[interaction.user.id] = time.monotonic()
await interaction.response.send_message(f"¡Gracias! Tu sugerencia n.º {numero} está en {canal.mention}.", ephemeral=True)
@bot.tree.command(name="responder", description="Acepta, rechaza o deja en estudio una sugerencia")
@app_commands.guild_only()
@app_commands.default_permissions(manage_guild=True)
@app_commands.describe(numero="El número de la sugerencia", estado="Qué decides", motivo="Por qué (sale en la tarjeta)")
@app_commands.choices(estado=[app_commands.Choice(name=nombre, value=clave) for clave, (nombre, _, _) in ESTADOS.items()])
async def responder(interaction: discord.Interaction, numero: app_commands.Range[int, 1], estado: app_commands.Choice[str],
motivo: Optional[app_commands.Range[str, 1, 1000]] = None):
datos = leer()
s = datos.get(str(interaction.guild_id), {}).get("sugerencias", {}).get(str(numero))
if s is None:
await interaction.response.send_message(f"No hay ninguna sugerencia con el número {numero}.", ephemeral=True)
return
s["estado"] = estado.value
s["motivo"] = motivo
guardar(datos)
try:
canal = bot.get_channel(s["canal"])
await canal.get_partial_message(s["mensaje"]).edit(embed=tarjeta(str(numero), s),
view=Votos(cerrada=estado.value in CERRADAS))
except (AttributeError, discord.HTTPException) as error:
print(f"No he podido cambiar la tarjeta {numero}: {error!r}")
await interaction.response.send_message("Guardado, pero no he podido cambiar la tarjeta: ¿la han borrado?", ephemeral=True)
return
await interaction.response.send_message(f"Sugerencia n.º {numero}: {estado.name.lower()}.", ephemeral=True)
@bot.event
async def on_ready():
print(f"Conectado como {bot.user}")
bot.run(os.environ["DISCORD_TOKEN"])El mismo bot en JavaScript (discord.js)
package.json, .env e index.js. Hace falta Node.js 20 o más nuevo.
{
"name": "bot-sugerencias",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"discord.js": "^14.16.0",
"dotenv": "^16.4.0"
}
}DISCORD_TOKEN=pega-aquí-tu-tokenrequire("dotenv").config(); // lee el token del fichero .env
const fs = require("node:fs");
const path = require("node:path");
const { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, EmbedBuilder, Events, GatewayIntentBits, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } = require("discord.js");
// Todo se guarda junto al bot: un reinicio no pierde ni las sugerencias ni los votos.
const DATOS = path.join(__dirname, "sugerencias.json");
const ESPERA = 120; // segundos entre dos sugerencias de la misma persona
const ESTADOS = {
aceptada: { nombre: "Aceptada", titulo: "✅ Aceptada", color: 0x57f287 },
rechazada: { nombre: "Rechazada", titulo: "❌ Rechazada", color: 0xed4245 },
estudio: { nombre: "En estudio", titulo: "🔎 En estudio", color: 0xfee75c },
};
const ABIERTA = { nombre: "Abierta", titulo: "💡 Abierta a votos", color: 0x5865f2 };
const CERRADAS = ["aceptada", "rechazada"]; // con estas ya no se vota
const NADA = { parse: [] }; // una sugerencia nunca avisa a nadie
const ultima = new Map(); // cuándo sugirió cada uno por última vez
const privado = { flags: MessageFlags.Ephemeral };
const leer = () => (fs.existsSync(DATOS) ? JSON.parse(fs.readFileSync(DATOS, "utf8")) : {});
const guardar = (datos) => fs.writeFileSync(DATOS, JSON.stringify(datos, null, 2));
function tarjeta(numero, s) {
const { titulo, color } = ESTADOS[s.estado] ?? ABIERTA;
const e = new EmbedBuilder().setTitle(`Sugerencia n.º ${numero}`).setDescription(s.texto).setColor(color).addFields(
{ name: "De", value: `<@${s.autor}>`, inline: true },
{ name: "Votos", value: `👍 ${s.si.length} · 👎 ${s.no.length}`, inline: true },
{ name: "Estado", value: titulo, inline: true },
);
if (s.motivo) e.addFields({ name: "Respuesta del equipo", value: s.motivo });
return e;
}
// Los dos botones. El customId es fijo: siguen funcionando tras reiniciar el bot.
const votos = (cerrada = false) => new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("sugerencia:si").setEmoji("👍").setStyle(ButtonStyle.Success).setDisabled(cerrada),
new ButtonBuilder().setCustomId("sugerencia:no").setEmoji("👎").setStyle(ButtonStyle.Danger).setDisabled(cerrada),
);
const comandos = [
new SlashCommandBuilder().setName("canal-sugerencias").setDescription("Elige el canal donde se publican las sugerencias")
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) // de serie, solo quien gestiona el servidor
.addChannelOption((o) => o.setName("canal").setDescription("El canal de sugerencias").setRequired(true).addChannelTypes(ChannelType.GuildText)),
new SlashCommandBuilder().setName("sugerir").setDescription("Manda una sugerencia para el servidor")
.setContexts(InteractionContextType.Guild)
.addStringOption((o) => o.setName("idea").setDescription("Tu sugerencia, en un mensaje").setRequired(true).setMinLength(10).setMaxLength(1000)),
new SlashCommandBuilder().setName("responder").setDescription("Acepta, rechaza o deja en estudio una sugerencia")
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addIntegerOption((o) => o.setName("numero").setDescription("El número de la sugerencia").setRequired(true).setMinValue(1))
.addStringOption((o) => o.setName("estado").setDescription("Qué decides").setRequired(true)
.addChoices(...Object.entries(ESTADOS).map(([value, e]) => ({ name: e.nombre, value }))))
.addStringOption((o) => o.setName("motivo").setDescription("Por qué (sale en la tarjeta)").setMaxLength(1000)),
];
async function elegirCanal(i) {
const canal = i.options.getChannel("canal");
const mios = canal.permissionsFor(i.guild.members.me);
if (!mios.has([PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.EmbedLinks])) {
await i.reply({ content: `En ${canal} me faltan «Ver canal», «Enviar mensajes» o «Insertar enlaces».`, ...privado });
return;
}
const datos = leer();
(datos[i.guildId] ??= { siguiente: 1, sugerencias: {} }).canal = canal.id;
guardar(datos);
await i.reply({ content: `Las sugerencias irán a ${canal}.`, ...privado });
}
async function sugerir(i) {
const antes = ultima.get(i.user.id);
if (antes !== undefined && Date.now() - antes < ESPERA * 1000) {
const quedan = Math.ceil((ESPERA * 1000 - (Date.now() - antes)) / 1000);
await i.reply({ content: `Espera ${quedan} s antes de mandar otra.`, ...privado });
return;
}
const datos = leer();
const servidor = datos[i.guildId];
const canal = servidor?.canal ? client.channels.cache.get(servidor.canal) : undefined;
if (!canal) {
await i.reply({ content: "Aún no hay canal de sugerencias: alguien que gestione el servidor tiene que usar /canal-sugerencias.", ...privado });
return;
}
// El número se reserva antes de publicar: dos sugerencias a la vez no lo comparten.
const numero = String(servidor.siguiente);
servidor.siguiente += 1;
guardar(datos);
const s = { autor: i.user.id, texto: i.options.getString("idea"), si: [], no: [], estado: "abierta", motivo: null, canal: canal.id };
let mensaje;
try {
mensaje = await canal.send({ embeds: [tarjeta(numero, s)], components: [votos()], allowedMentions: NADA });
} catch (error) {
console.error(`No he podido publicar la sugerencia ${numero}:`, error);
await i.reply({ content: "No he podido publicarla: revisa mis permisos en el canal de sugerencias.", ...privado });
return;
}
s.mensaje = mensaje.id;
const fresco = leer(); // se relee: mientras se publicaba, alguien ha podido votar otra
fresco[i.guildId].sugerencias[numero] = s;
guardar(fresco);
ultima.set(i.user.id, Date.now());
await i.reply({ content: `¡Gracias! Tu sugerencia n.º ${numero} está en ${canal}.`, ...privado });
}
async function votar(i) {
const lado = i.customId === "sugerencia:si" ? "si" : "no";
const otro = lado === "si" ? "no" : "si";
const datos = leer();
const entrada = Object.entries(datos[i.guildId]?.sugerencias ?? {}).find(([, s]) => s.mensaje === i.message.id);
if (!entrada || CERRADAS.includes(entrada[1].estado)) {
await i.reply({ content: "Esta sugerencia ya no admite votos.", ...privado });
return;
}
const [numero, s] = entrada;
s[otro] = s[otro].filter((id) => id !== i.user.id); // cambia de opinión: un solo voto por persona
if (s[lado].includes(i.user.id)) s[lado] = s[lado].filter((id) => id !== i.user.id); // pulsar otra vez el mismo botón quita el voto
else s[lado].push(i.user.id);
guardar(datos);
await i.update({ embeds: [tarjeta(numero, s)] });
}
async function responder(i) {
const numero = String(i.options.getInteger("numero"));
const estado = i.options.getString("estado");
const datos = leer();
const s = datos[i.guildId]?.sugerencias?.[numero];
if (!s) {
await i.reply({ content: `No hay ninguna sugerencia con el número ${numero}.`, ...privado });
return;
}
s.estado = estado;
s.motivo = i.options.getString("motivo");
guardar(datos);
try {
const canal = client.channels.cache.get(s.canal);
await canal.messages.edit(s.mensaje, { embeds: [tarjeta(numero, s)], components: [votos(CERRADAS.includes(estado))] });
} catch (error) {
console.error(`No he podido cambiar la tarjeta ${numero}:`, error);
await i.reply({ content: "Guardado, pero no he podido cambiar la tarjeta: ¿la han borrado?", ...privado });
return;
}
await i.reply({ content: `Sugerencia n.º ${numero}: ${ESTADOS[estado].nombre.toLowerCase()}.`, ...privado });
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] }); // sin intents privilegiados
client.on(Events.InteractionCreate, async (i) => {
try {
if (i.isButton() && i.customId.startsWith("sugerencia:")) await votar(i);
else if (i.isChatInputCommand() && i.commandName === "sugerir") await sugerir(i);
else if (i.isChatInputCommand() && i.commandName === "canal-sugerencias") await elegirCanal(i);
else if (i.isChatInputCommand() && i.commandName === "responder") await responder(i);
} catch (error) {
console.error(error);
}
});
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set(comandos.map((x) => x.toJSON())); // publica los comandos
console.log(`Conectado como ${c.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);3. Pruébalo
Quién ve /canal-sugerencias y /responder se cambia sin tocar el código en «Ajustes del servidor» → «Integraciones» → tu bot: por ejemplo, para que también contesten los moderadores.
Arranca el bot
En una terminal, en la carpeta:
pip install -r requirements.txtypython app.py(onpm installynode index.js). Cuando diga «Conectado como…», ya está. Los comandos pueden tardar unos minutos en salir la primera vez.Elige el canal
Escribe
/canal-sugerenciasy elige #sugerencias. Si le falta algún permiso allí, el bot te dice cuál.Manda una y vota
Con
/sugerirescribe una idea de al menos 10 caracteres. Sale la tarjeta n.º 1 con los dos botones: pulsa 👍 y luego 👎, y verás cómo se mueve tu voto. Pídele a otra cuenta que vote también.Contesta como el equipo
/respondercon el número 1, el estado «Aceptada» y un motivo como «Para el mes que viene». La tarjeta se pone verde, enseña la respuesta y los botones se apagan.
Por qué los votos no se pierden al reiniciar
Los botones de Discord solo llevan un identificador: el bot tiene que reconocerlo cuando alguien pulsa, también en tarjetas de hace semanas. Por eso los dos botones tienen un custom_id fijo (sugerencia:si y sugerencia:no) y la tarjeta se busca por el mensaje que se ha pulsado.
En discord.py, la vista se crea con timeout=None y se vuelve a registrar al arrancar con self.add_view(Votos()): sin esa línea, tras un reinicio los botones viejos responden «Esta interacción ha fallado». En discord.js no hace falta registrar nada: cada pulsación llega a InteractionCreate y el bot mira el customId.
Los votos se guardan como listas de quién ha votado, no como un número: así nadie vota dos veces y cambiar de opinión mueve el voto en vez de sumar otro.
Si algo no va
Al pulsar un botón sale «Esta interacción ha fallado»: el bot está apagado, o en Python falta self.add_view(Votos()) en setup_hook. Míralo en la consola.
/sugerir contesta que no hay canal: alguien con «Gestionar servidor» tiene que usar /canal-sugerencias antes. Si dice que no ha podido publicarla, el bot ha perdido «Ver canal», «Enviar mensajes» o «Insertar enlaces» en ese canal; la consola enseña el error (50013 es «Missing Permissions»).
/responder dice que no ha podido cambiar la tarjeta: la han borrado, o el bot ya no ve ese canal. La decisión queda guardada igualmente.
No salen los comandos: qué mirar cuando los comandos no aparecen. Si el bot está en línea y no contesta, mi bot no responde.
Ideas para ampliarlo
Un hilo por sugerencia para hablarla sin llenar el canal: tras publicar la tarjeta, mensaje.create_thread(...) en discord.py o mensaje.startThread(...) en discord.js (el bot necesita además «Create Public Threads»).
Que el autor se entere de la decisión: al responder, mándale un mensaje privado con el estado y el motivo (puede tenerlos cerrados: captura el error).
Más botones, como «Ver mis sugerencias», o un menú para elegir la categoría: botones y menús en un bot de Discord. Si el JSON se te queda pequeño, pásalo a SQLite con base de datos para tu bot.
4. Déjalo encendido siempre
Si el bot está apagado, /sugerir no responde y los botones fallan: tiene que estar encendido todo el tiempo, no solo cuando tu ordenador lo está. Opciones gratis y de pago, comparadas, en dónde alojar tu bot de Discord.
En NapNodes hay un bot gratis de 256 MB que no se apaga por estar vacío; se renueva cada 7 días con «Encender» (sus pegas, en esta guía). Créalo, elige Node.js o Python en «Configuración» → «Lenguaje», sube tus ficheros en «Archivos» y pulsa «Encender»: las librerías de requirements.txt o package.json se instalan solas al arrancar, y sugerencias.json se queda en la carpeta del bot entre reinicios. Para no tener que renovar, el plan Mini (512 MB) está encendido siempre (precios de los bots).
Preguntas frecuentes
¿Cómo hago un canal de sugerencias con votos en Discord?
Con un bot que publique cada sugerencia como una tarjeta con dos botones para votar y guarde quién ha votado. El código completo en Python y en JavaScript, con comandos para sugerir y contestar, está en esta guía.
¿Por qué los botones de mi bot dejan de funcionar al reiniciarlo?
Porque el bot ya no reconoce esos botones. Hace falta un identificador fijo en cada botón y, en discord.py, una vista sin tiempo límite que se vuelve a registrar al arrancar, como hace el bot de esta guía.
¿Qué permisos necesita un bot de sugerencias?
Ver canales, enviar mensajes e insertar enlaces (el número de permisos es 19456). Los botones no piden ningún permiso más.
¿Se puede votar dos veces?
No: el bot guarda quién ha votado cada cosa. Pulsar el otro botón cambia el voto y pulsar el mismo otra vez lo quita.
¿Dónde dejo el bot de sugerencias encendido siempre?
En un servidor. En NapNodes hay un bot gratis de 256 MB que se renueva con un clic cada semana, o el plan Mini, encendido siempre.
¿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: Permisos de un bot de Discord. Marca los permisos de tu bot en castellano y sal con el número y el enlace de invitación; o pega un número y mira qué permisos da.
Sigue leyendo
- Bot de roles para Discord: botones para que cada uno elija sus roles (Python y JavaScript)
Haz tu bot de autoroles para Discord en Python o JavaScript: un mensaje con botones para ponerse y quitarse roles, sin base de datos y sin roles por reacción.
- Bot de sorteos para Discord en Python: botón para participar, ganadores al azar y repetir
Haz tu bot de sorteos de Discord en Python: /sorteo con botón para participar, cuenta atrás, ganadores al azar y /resortear. Código completo, sin anuncios.
- Bot de verificación para Discord en Python: un botón y una pregunta antes de entrar
Haz tu bot de verificación de Discord con discord.py: botón «Verificarme», una pregunta contra bots y un rol que abre el servidor. Código completo y gratis.