Resumen
Los scripts son pequeños programas en Lua que amplían tu servidor. Se ejecutan en un proceso aislado con un sandbox de Lua 5.5, completamente separado del bot. Un script nunca toca la base de datos, el sistema de archivos ni otros servidores. Cada capacidad pasa por una API del host incluida en una lista blanca que cede el control al bot mediante IPC.
Si no hay ningún script cargado, no existe ningún proceso.
El bot comprueba las capacidades de las llamadas bot:* y msg:*, no Lua.
AutoMod, la bienvenida y los tickets siempre se ejecutan. Los scripts complementan, no sustituyen.
Un script que falla se descarga o se pone en cuarentena; el bot sigue funcionando.
No hay editor en el navegador. Escribe .lua en un editor de verdad y súbelo en la página Scripts de tu servidor.
Primeros pasos
- 1
Escribe un archivo .lua en local
VS Code, Neovim o cualquier editor. - 2
Compruébalo en local (opcional)
script_runner --check file.luamuestracheck: ok. - 3
Súbelo
Abre la página Scripts de tu servidor y pega el código o sube el archivo .lua (hasta 64 KiB). - 4
Simulación
Valida el tamaño, la codificación, el bytecode y las APIs prohibidas sin ejecutar nada. - 5
Activa
Espera a que el staff apruebe la versión y pulsa Activar. El bot la carga en segundos. - 6
Define un canal de registro
Recibe ahí la salida debot:logy las notificaciones de errores.
CLI local
El runner es el mismo binario que usa el bot.
- script_runner --check file.lua
Comprobación estática de sintaxis y APIs prohibidas sin ejecutar. Muestra «check: ok».
- script_runner --eval 'code'
Ejecuta un fragmento en el sandbox en local. Los errores muestran el tipo y el mensaje.
- script_runner --self-test
Ejecuta las autopruebas del sandbox, del protocolo y del socket.
Ciclo de vida y revisión
Los scripts tienen versiones. Una versión se congela al enviarla y se identifica por su SHA-256. Solo se pueden activar las versiones aprobadas, y el bot solo carga las versiones activas.
- 1
Escribir
Crea el archivo .lua en local con VS Code, Neovim o cualquier editor. No hay editor en el navegador, y es a propósito. - 2
Simulación / subida
Pega el código o sube el archivo .lua en la página Scripts del servidor (≤ 64 KiB, sin binarios). La simulación valida el tamaño, la codificación, el bytecode y las APIs prohibidas sin ejecutar nada. - 3
Revisión
Cada script nuevo y cada versión nueva empieza como pending. El staff aprueba o rechaza la revisión exacta del código antes de que pueda ejecutarse. - 4
Activar
Activas una versión aprobada. Se rechaza la activación de una versión pending o rejected. El bot solo carga las versiones activas. - 5
Ejecutar
El bot carga la versión (código + SHA-256, verificado por el runner) en un sandbox aislado de Lua 5.5. Los eventos solo se reparten a los manejadores registrados con bot:on. - 6
Actualizar / revertir
Sube una versión nueva (vuelve a quedar pending) o activa una versión aprobada anterior para revertir. El almacenamiento KV se conserva. - 7
Desactivar / eliminar
Desactivar detiene la carga de inmediato; «Desactivar todo» afecta a todos los scripts activos del servidor. Eliminar borra el script, sus versiones, el KV y los errores.
Estado del script
Se muestra como una insignia en cada script.
- draft
Creado, aún sin versión activa.
- pending
A la espera de algo (p. ej., la primera aprobación); nunca se ejecuta como borrador.
- active
Una versión aprobada está activada y cargada por el bot.
- rejected
El staff lo rechazó; el script no se cargará.
- disabled
Desactivado manualmente (por ti, por el staff, con «Desactivar todo» o por la cuota): descargado.
- quarantined
Apagado automático por abuso de los límites (inundación de frecuencia, inundación de cola, fallos repetidos). Descargado y requiere la atención del staff.
Revisión de versiones
Cada revisión del código pasa por una revisión del staff.
- pending
El staff aún no ha decidido. No se puede activar.
- approved
Se puede activar. Las nuevas subidas de scripts de la tienda instalados se saltan la cola (la versión de la publicación ya estaba aprobada).
- rejected
El staff rechazó esta revisión del código. Sube una versión nueva en su lugar.
Cuota: 2 scripts activos por servidor gratis, 10 con premium. Si la cuota se reduce, los scripts activos más antiguos que la superen se desactivan automáticamente y se te avisa mediante el canal de registro del script.
Manejadores y eventos
Registra manejadores con bot:on(event, fn). El script informa de los eventos registrados al cargarse y solo se le entregan esos eventos. Los nombres de evento desconocidos hacen fallar la carga, y un script puede registrar como máximo 8 manejadores por evento.
bot:on("message_create", function(msg)
if msg.content == "!ping" then
msg:reply("pong")
end
end)Los eventos de tipo mensaje (message_*, automod_check) pasan un objeto de mensaje con métodos; consulta Objeto de mensaje.
Las llamadas al host ceden el control: mientras un manejador espera a bot:kv_get y llamadas similares, no se ejecuta un segundo evento en el mismo script. Los eventos nuevos esperan en una cola de 32; si se desborda, se descarta el evento más antiguo, y una saturación sostenida pone el script en cuarentena.
guild_id siempre se inyecta en la carga. Los eventos solo se entregan a los manejadores registrados con bot:on. Los nombres de evento desconocidos hacen fallar la carga con un error de policy. No existe un evento voice_state.
Cargas de los eventos
Cada carga incluye guild_id. Los campos son tablas en snake_case y minúsculas.
- message_create
id, guild_id, channel_id, author_id, author, content, attachments, referenced_id
Se activa antes de las etiquetas y la respuesta automática. Adjuntos: hasta 8 entradas { id, filename, size }. referenced_id es 0 cuando no hay referencia de respuesta.
- message_update
same as message_create
- message_delete
channel_id, id
- message_delete_bulk
channel_id, ids
ids es un array con los IDs de los mensajes eliminados.
- guild_member_add
user_id
Se omiten las cuentas de bot.
- guild_member_remove
user_id
- guild_member_update
user_id
- reaction_add
channel_id, id, author_id, user_id, emoji, emoji_id
Se omiten las reacciones de bots. emoji es el nombre; emoji_id es 0 para los emojis Unicode.
- reaction_remove
channel_id, id, author_id, user_id, emoji, emoji_id
- channel_create
id, name, type
- channel_update
id, name, type
- channel_delete
id, name
- role_create
id, name
- role_update
id, name
- role_delete
id, name
- thread_create
id, name
- thread_update
id, name
- thread_delete
id
- invite_create
code, uses
- invite_delete
code
- guild_ban_add
user_id
- guild_ban_remove
user_id
- welcome_before
user_id, username
Se activa antes del mensaje de bienvenida nativo. Solo para entradas que no son bots.
- goodbye_before
user_id
Se activa antes del mensaje de despedida nativo.
- ticket_created
ticket_id, user_id, channel_id
- automod_check
content, channel_id, author_id, id
Se activa antes del AutoMod nativo. msg:escalate() elimina el mensaje en curso; la denegación nativa siempre gana; msg:allow() no hace nada.
- webhooks_update
channel_id
- emoji_update
count, ids
ids limitado a 8.
- tick
unix
Cada 60 segundos, solo se entrega a los scripts que registraron bot:on("tick"). unix es la hora Unix.
APIs del host
Estas llamadas ceden el control al bot mediante IPC. Límites de frecuencia por script: 30 llamadas al host / 10 s, más 5 envíos a Discord / 10 s (reply, channel_send, react, edit, delete, pin, escalate, http_get). Las lecturas de caché como member_get y guild_get nunca llegan directamente a Discord.
- bot:log(msg)
Escribe en el canal de registro del script y en el registro de errores. Cuenta para el límite de 30 llamadas / 10 s.
- bot:kv_get(key)
Devuelve el valor JSON almacenado o nil. Claves: 1–64 caracteres [A-Za-z0-9_-].
- bot:kv_set(key, value)
Almacena hasta 8 KiB de JSON. 256 claves por script, 1 MiB por script (8 MiB premium), 8 MiB por servidor.
- bot:kv_del(key)
Elimina una clave.
- bot:kv_keys()
Devuelve hasta 256 claves, ordenadas.
- bot:kv_incr(key, delta?)
Incrementa un número JSON almacenado; delta por defecto 1, limitado a ±1.000.000.000.
- bot:db_exec(sql, binds?)
Ejecuta una sentencia de escritura o de esquema (CREATE TABLE/INDEX, INSERT, UPDATE, DELETE, DROP) en la base de datos de scripts del servidor. Binds opcionales: un array de nil, boolean, number o string, hasta 8. Devuelve { changes, last_insert_rowid }. Los problemas de cuota, ocupación o límites devuelven { error, kind } en lugar de lanzar un error; las sentencias prohibidas lanzan un error de policy.
- bot:db_query(sql, binds?)
Ejecuta un SELECT con binds opcionales y devuelve hasta 50 filas, cada una una tabla indexada por nombre de columna. Usa siempre LIMIT. Si falla, devuelve { error, kind }, así que comprueba rows.error.
- bot:http_get(url)
Solo GET por HTTPS. El host debe estar en la lista de permitidos del servidor. Devuelve { status, body, truncated }; el cuerpo está limitado a 16 KiB. La espera no cuenta para el límite de 100 ms de tiempo real de Lua.
- bot:channel_send(channel_id, text, embed?)
Envía a cualquier canal del propio servidor en el que el bot pueda escribir. Mismas reglas de contenido que msg:reply.
- bot:channel_get(channel_id)
Lectura de caché: { id, name, type, parent_id }. Solo el propio servidor.
- bot:member_get(user_id)
Lectura de caché: { id, nick, bot, roles (hasta 20) }.
- bot:role_get(role_id)
Lectura de caché: { id, name, position, color }.
- bot:guild_get()
Caché del propio servidor: { id, name, owner_id, member_count }.
- bot:self()
El usuario del bot: { id, username, bot }.
- bot:has_role(user_id, role_id)
Lectura de caché, devuelve { has: true|false }.
- bot:channels()
Lista en caché de los canales del servidor (máx. 50).
- bot:roles()
Lista en caché de los roles del servidor (máx. 50).
- bot:now()
Hora Unix en segundos.
- bot:id()
El UUID de este script. No cede el control ni cuenta para los límites de frecuencia.
- bot:message_get(channel_id, message_id)
Lectura de la caché de mensajes del bot (sin consulta de red).
- bot:can_send(channel_id)
Devuelve { ok: true|false } según si el bot puede enviar mensajes en ese canal.
Qué acepta bot:http_get
- Solo HTTPS
Las URL http:// se deniegan. Sin userinfo (user:pass@host).
- Lista de permitidos del servidor
El host debe estar en la lista de permitidos HTTP del servidor (página Scripts, hasta 32 hosts). La gestionas tú.
- Sin hosts de infraestructura
Las IP literales, localhost, *.local y *.internal siempre se deniegan.
- Respuesta
{ status, body, truncated }, cuerpo limitado a 16 KiB. Tiempo de espera de 3 s. Comparte el bucket de 5 por 10 s de Discord.
Objeto de mensaje
Los eventos de mensaje pasan una tabla msg además de los campos de la carga. Los métodos solo actúan sobre el mensaje del evento en curso; no se pueden obtener ni editar mensajes arbitrarios.
Reglas de contenido en todos los casos: texto de 1 a 2000 bytes, como máximo un embed { title, description, color }, las menciones solo se resuelven para el autor del evento y se eliminan @everyone y @here. Sin archivos ni stickers.
- msg:reply(text, embed?)
Responde en el canal del evento. Las menciones solo se resuelven para el autor del evento; se eliminan @everyone / @here. Texto de 1 a 2000 bytes, como máximo un embed.
- msg:react(emoji)
Añade una reacción al mensaje del evento.
- msg:unreact(emoji)
Quita la reacción del propio script.
- msg:edit(text, embed?)
Edita el mensaje en curso. Mismas reglas de contenido que reply.
- msg:delete()
Elimina el mensaje en curso.
- msg:pin() / msg:unpin()
Fija o desfija el mensaje en curso.
- msg:escalate()
Solo en automod_check: elimina el mensaje en curso (acción extra, con límite de frecuencia). Las decisiones del AutoMod nativo siempre ganan.
- msg:allow()
Solo en automod_check: marca el mensaje como permitido. No puede deshacer una denegación nativa; en la práctica es un marcador sin efecto.
Límites y cuotas
Todos los límites están en un único archivo de configuración que comparten el bot y el runner. Se aplican en tiempo de ejecución, no solo se documentan aquí. Las cifras son los valores por defecto actuales.
- Lenguaje
Lua 5.5, solo fragmentos de texto (el bytecode se rechaza). Sin io/os.file/require/load/debug/coroutine.
- Cómputo
~100 ms de tiempo real por manejador, 10.000.000 de instrucciones (count hook).
- Memoria
2 MB por script gratis, 8 MB premium.
- Tamaño del código
64 KiB de .lua en UTF-8, sin bytes NUL, sin bytecode.
- Llamadas al host
30 cada 10 s por script (incluido bot:log).
- Envíos a Discord
5 cada 10 s por script (reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 8 denegaciones en 10 s → cuarentena.
- Manejadores
Máx. 8 por evento y script. Los nombres de evento desconocidos se rechazan al cargar.
- Cola de eventos
Profundidad de 32 por script, se descarta el más antiguo al desbordarse. 8 o más descartes en 10 s → cuarentena.
- HTTP
HTTPS + lista de permitidos del servidor, tiempo de espera de 3 s, cuerpo de 16 KiB, sin IP/localhost/hosts internos, sin userinfo.
- KV
Claves de 1–64 [A-Za-z0-9_-], valores ≤ 8 KiB de JSON, 256 claves/script, 1 MiB/script (8 MiB premium), 8 MiB/servidor.
- Base de datos
Un archivo SQLite por servidor, compartido por todos sus scripts: 1 MiB (8 MiB premium), una sentencia por llamada, SQL ≤ 2 KiB, ≤ 8 binds, ≤ 50 filas, 20 ms por sentencia, 64 tablas e índices. Sin ATTACH, PRAGMA, transacciones, triggers, vistas ni WITH. Todos los scripts del servidor pueden leer y eliminar todas las tablas.
- Scripts activos
2 por servidor gratis, 10 premium. Los scripts activos más antiguos que excedan el límite se desactivan automáticamente.
- Versiones
Número ilimitado por script; cada versión nueva queda pendiente hasta que el staff la aprueba.
- Menciones
Solo el autor del evento. Se eliminan @everyone / @here. Sin archivos ni stickers.
- Aislamiento
3 fallos (timeout/memory/policy/runtime) → worker aislado propio (máx. 4); si no, cuarentena + descarga.
- Registro de errores
Últimas 24 h visibles en el panel; las entradas se purgan a los 7 días.
- Prohibido
Sin voice_state, sin APIs mod_* (ban/kick/timeout), sin acceso entre servidores, sin base de datos/sistema de archivos externos, sin monetización.
- AutoMod
El AutoMod nativo se ejecuta primero y siempre gana a los scripts.
Validación
Antes de guardar una versión, el backend ejecuta comprobaciones estáticas. La simulación hace lo mismo sin guardar. El propio sandbox es la segunda línea de defensa: los globales prohibidos sencillamente no existen en tiempo de ejecución.
- Tamaño / codificación
No vacío, ≤ 64 KiB, UTF-8 sin bytes NUL.
- Bytecode
Se rechaza cualquier cosa que empiece con la firma de bytecode de Lua.
- APIs prohibidas
El análisis estático rechaza: load(, loadfile, dofile, require(, os.execute, io.open, debug., string.dump, loadstring, getfenv, setfenv, package., coroutine., ffi., jit.
- Sandbox en ejecución
Incluso si algo se cuela en el análisis estático, el sandbox sencillamente no expone esos globales: la llamada falla con un error de policy.
Funciones integradas disponibles: la biblioteca estándar de Lua sin acceso a archivos. string, table, math, tostring, tonumber, type, pairs, ipairs, os.time, os.clock, os.difftime y bot.
Errores y cuarentena
Cada error de un manejador se registra con un tipo y se muestra en la página de detalle del script (últimas 24 h). Las entradas se purgan a los 7 días.
- runtime
Un error de Lua en tu manejador (acceso a nil, argumentos incorrectos, …). El mensaje incluye el texto del error de Lua.
- timeout
El manejador superó el límite de 100 ms de tiempo real o una llamada IPC agotó el tiempo de espera (1 s).
- memory
El script superó su límite de memoria.
- policy
El script intentó algo no permitido: evento desconocido, demasiados manejadores, API no permitida o carga demasiado grande.
El abuso repetido escala automáticamente: 3 fallos (timeout, memory, policy, runtime) trasladan el script a su propio worker aislado. Si el mal comportamiento continúa, o no hay ningún hueco aislado libre (máx. 4), el script se pone en cuarentena: se descarga y se marca en el panel. Las inundaciones de frecuencia (8 envíos a Discord denegados u 8 descartes de cola en 10 s) provocan cuarentena inmediata. Se te avisa mediante el canal de registro del script o por mensaje directo.
Lado de operaciones: el bot se puede pausar globalmente con un interruptor de emergencia sin reiniciar, y una señal scripts:updated recarga las versiones modificadas en segundos. Ambas son acciones del staff.
Tienda
La tienda te permite compartir scripts públicamente e instalar scripts de otros servidores. Tanto publicar como instalar crean copias. Al instalar una publicación se crea un script independiente en tu servidor, que no sigue vinculado al código del autor.
- Publicar
Requiere un script activo con una versión aprobada, una descripción (10–512 caracteres) y de 1 a 8 etiquetas ([a-z0-9-], 1–24 caracteres). Debes aceptar una vez los términos de la tienda antes de la primera publicación.
- Instalar
Al instalar, la publicación se copia a tu servidor como un script propio (borrador, versión ya aprobada, nombre propio con sufijo si está ocupado). El KV no se copia. El contador de instalaciones de la publicación aumenta.
- Despublicar
Quita la publicación de la tienda. Las copias existentes se mantienen y siguen funcionando; simplemente no reciben más actualizaciones.
- Sin dinero
Las ventas, los muros de pago, los enlaces de donaciones o criptomonedas y otras formas de monetización similares en la descripción o el código se rechazan al publicar y pueden provocar el bloqueo del script.
- Reportes
Cualquier persona con acceso al servidor puede reportar una publicación indicando un motivo. El staff ve una cola de reportes abiertos y los cierra tras la revisión.
- Límites
La lista de la tienda muestra hasta 50 publicaciones por consulta (búsqueda, etiqueta, orden por instalaciones o novedad). Para las instalaciones se aplica la cuota normal de scripts del servidor.
Ejemplos
Selecciona un ejemplo para ver su código.