Logic Script
El lenguaje de programación nativo de Ayami — ligero, seguro y hecho para automatizaciones en Discord. Escribe scripts, reacciona a eventos, envía embeds y gestiona los datos del servidor con código limpio y directo.
Introducción
Logic Script es un lenguaje interpretado creado para automatizaciones dentro de servidores de Discord. Los scripts son archivos .logic que reaccionan a eventos de Discord — mensajes, miembros que entran, clics en botones — usando el bloque on.
Cada servidor tiene su propio entorno aislado: scripts, base de datos y configuraciones están completamente separados. Logic Script se ejecuta dentro del bot en un sandbox seguro — sin acceso al sistema operativo ni a recursos externos.
Logic Script no reemplaza al Logic Builder — lo amplía. Los scripts pueden ser llamados desde flujos visuales y disparar eventos personalizados entre sí.
-- Meu primeiro script
on messageCreate(message)
if message.content == "!ola" then
message.reply("Olá! 👋")
end
end
Variables
Usa let para variables mutables y const para constantes. El alcance es léxico — las variables declaradas dentro de un bloque no existen fuera de él.
let nome = "Ayami"
let pontos = 0
let ativo = true
const PREFIX = "!"
-- Reatribuição (só funciona com let)
pontos = pontos + 10
Tipos de datos
| Tipo | Ejemplo | Descripción |
|---|---|---|
| number | 42, 3.14 | Enteros y decimales |
| string | "texto", 'outro' | Texto entre comillas simples o dobles |
| bool | true, false | Booleano |
| nil | nil | Ausencia de valor |
| array | [1, 2, 3] | Lista ordenada |
| object | { nome: "A" } | Mapa clave → valor |
| function | function() end | Las funciones son valores de primera clase |
let lista = ["maçã", "pera", "uva"]
let pessoa = { nome: "Ayami", pontos: 100 }
print(lista[0]) -- "maçã"
print(pessoa.nome) -- "Ayami"
print(type(42)) -- "number"
Operadores
| Op. | Descripción | Ejemplo |
|---|---|---|
| + | Suma | 2 + 3 → 5 |
| - | Resta | 10 - 4 → 6 |
| * | Multiplicación | 3 * 4 → 12 |
| / | División | 10 / 2 → 5 |
| % | Módulo | 10 % 3 → 1 |
| .. | Concatenación | "Oi " .. "Ayami" |
| Op. | Descripción |
|---|---|
| == | Igual |
| != | Diferente |
| < / > | Menor / Mayor |
| <= / >= | Menor o igual / Mayor o igual |
| and | Y lógico |
| or | O lógico |
| not | Negación |
Control de flujo
if pontos >= 100 then
print("Ouro")
elseif pontos >= 50 then
print("Prata")
else
print("Bronze")
end
-- while
let i = 1
while i <= 5 do
print(i)
i = i + 1
end
-- for numérico: for var = início, fim [, passo] do
for i = 1, 10 do
print(i)
end
-- for in (iteração)
let frutas = ["maçã", "pera", "uva"]
for fruta in frutas do
print(fruta)
end
Usa break para salir de un bucle y continue para saltar a la siguiente iteración.
Funciones
-- Declaração
function somar(a, b)
return a + b
end
print(somar(3, 7)) -- 10
-- Funções anônimas (valores de primeira classe)
let dobrar = function(x)
return x * 2
end
print(dobrar(5)) -- 10
Import / Export
Organiza tu código en varios archivos. Usa export para exponer funciones e import para usarlas en otro script.
export function addCoins(user, qtd)
db.user(user).add("coins", qtd)
return db.user(user).get("coins")
end
import { addCoins } from "economia.logic"
on command("trabalhar", message, args)
let ganho = random(10, 100)
let total = addCoins(message.author, ganho)
message.reply("💰 +" .. ganho .. " coins! Total: " .. total)
end
Eventos (on)
Los scripts reaccionan a eventos de Discord usando el bloque on. Para comandos por prefijo, el primer argumento es una cadena con el nombre del comando.
on nomeDoEvento(param1, param2)
-- código aqui
end
-- Comando por prefixo (ex: !ping)
on command("ping", message, args)
message.reply("🏓 Pong!")
end
| Evento | Parámetros | Se dispara cuando |
|---|---|---|
| messageCreate | message | Se envía un mensaje |
| messageDelete | message | Se elimina un mensaje |
| messageEdit | message | Se edita un mensaje |
| memberJoin | member | Un miembro entra al servidor |
| memberLeave | member | Un miembro sale del servidor |
| buttonClick | interaction | Se hace clic en un botón |
| selectMenu | interaction | Se selecciona una opción de menú |
| command | "nome", message, args | Se ejecuta un comando por prefijo |
| reactionAdd | emoji, user, message | Se añade una reacción |
| reactionRemove | emoji, user, message | Se elimina una reacción |
| voiceJoin | member | Un miembro entra a voz |
| voiceLeave | member | Un miembro sale de voz |
| banAdd | user | Un usuario es baneado |
| banRemove | user | Se elimina un ban |
| channelCreate | channel | Se crea un canal |
| channelDelete | channel | Se elimina un canal |
Objetos de Discord
messageCreate, sendMessage() y message.reply().getUser() o message.author.getInteraction(). Usado en buttonClick y selectMenu.opts.ephemeral: true = privado.onSubmit(fn)UI — Embed, Button, SelectMenu, Modal
Todos los métodos se pueden encadenar. Embed acepta todos los campos que admite Discord.
let embed = new EmbedBuilder()
embed.setTitle("Título")
embed.setDescription("Descrição")
embed.setColor("#A9D6FF")
embed.setThumbnail("https://url.png")
embed.setFooter({ text: "Ayami" })
embed.addFields(
{ name: "Campo", value: "Valor", inline: true }
)
embed.setTimestamp(nil) -- nil = agora
sendMessage(message.channel, "", { embed: embed })
let btn = Button()
btn.setLabel("Clique aqui")
btn.setStyle("primary") -- primary | secondary | success | danger | link
btn.setCustomId("meu_botao")
btn.setEmoji("🎉")
let menu = SelectMenu()
menu.setCustomId("meu_menu")
menu.setPlaceholder("Escolha uma opção")
menu.addOptions(
{ label: "Opção 1", value: "op1" },
{ label: "Opção 2", value: "op2" }
)
sendMessage(message.channel, "Escolha:", {
components: [btn, menu]
})
on buttonClick(interaction)
if interaction.customId == "meu_botao" then
interaction.reply("✅ Clicou!", { ephemeral: true })
end
end
on selectMenu(interaction)
let valor = interaction.values[0]
interaction.reply("Escolheu: " .. valor)
end
on buttonClick(interaction)
if interaction.customId == "abrir_feedback" then
let modal = Modal()
modal.setTitle("Feedback")
modal.addTextInput("nota", "Nota de 1 a 5", { style: "short" })
modal.addTextInput("comentario", "Comentário", { style: "long", required: false })
modal.onSubmit(function(interaction, fields)
interaction.reply("Recebido! Nota: " .. fields.nota)
end)
interaction.showModal(modal)
end
end
Solo funciona en respuesta a una interacción existente (buttonClick/selectMenu, o dentro de .onClick() de un Button/SelectMenu temporal) — no se puede abrir desde on(command).
Base de datos
Base de datos persistente por servidor. Los datos sobreviven entre ejecuciones y reinicios.
-- Global (por servidor)
db.set("chave", "valor")
db.get("chave")
db.has("chave") -- → bool
db.delete("chave")
-- Por usuário
db.user(user).set("coins", 100)
db.user(user).get("coins")
db.user(user).add("coins", 50) -- soma
db.user(user).sub("coins", 10) -- subtrai
-- Por servidor (guild)
db.guild().set("config", { prefix: "!" })
db.guild().get("config")
Funciones integradas
| Función | Descripción |
|---|---|
| length(s) | Longitud de la cadena |
| contains(s, sub) | Si contiene una subcadena |
| replace(s, de, para) | Reemplazar texto |
| split(s, sep) | Dividir en un array |
| join(arr, sep) | Unir un array en una cadena |
| lower(s) / upper(s) | Minúsculas / mayúsculas |
| trim(s) | Elimina espacios |
| tostring(v) | Convierte a cadena |
| tonumber(s) | Convierte a número |
| Función | Descripción |
|---|---|
| random(min, max) | Entero aleatorio entre min y max |
| round(n) | Redondea |
| floor(n) / ceil(n) | Piso / techo |
| abs(n) | Valor absoluto |
| max(a, b, …) / min(a, b, …) | Valor mayor / menor |
| Función | Descripción |
|---|---|
| push(arr, v) | Añade al final |
| pop(arr) | Elimina y devuelve el último |
| insert(arr, i, v) | Inserta en la posición i |
| remove(arr, i) | Elimina en la posición i |
| size(v) | Tamaño del array u objeto |
| keys(obj) | Claves del objeto |
| values(obj) | Valores del objeto |
| Función | Descripción |
|---|---|
| print(…) | Muestra en la consola del dashboard |
| type(v) | Tipo del valor como cadena |
| wait(ms) | Espera N ms (máx. 30 000ms/ejec.) |
| json.encode(v) | Objeto → cadena JSON |
| json.decode(s) | Cadena JSON → objeto |
| Función | Descripción |
|---|---|
| schedule(tempo, canal, texto, opts?) | Programa un mensaje — tiempo: ms, "1h" o "9:00". Con {recurrente:true} se repite |
| cancelSchedule(id) | Cancela una programación pendiente |
| listSchedules() | Lista las programaciones pendientes del servidor |
| cooldown(chave, tempo) | true si puede actuar ahora (y lo marca); false si está en espera |
| cooldownRestante(chave) | Ms restantes del cooldown |
Sandbox y Límites
Lo que no existe en Logic Script: acceso al sistema de archivos, módulos de Node.js (require), red externa fuera de las APIs de Discord, acceso a otros servidores o bibliotecas externas.
| Límite | Valor | Descripción |
|---|---|---|
| Pasos por ejecución | 50 000 | Evita bucles infinitos |
| Profundidad de recursión | 200 | Evita desbordamiento de pila |
| Total de wait() por ejecución | 30 000ms | Límite acumulado de espera |
| Scripts simultáneos por guild | 20 | Límite de concurrencia |
| TTL de los logs | 7 dias | Los logs expiran automáticamente |
Ejemplos completos
on memberJoin(member)
let canal = getChannel("ID_DO_CANAL")
let guild = getGuild()
let embed = new EmbedBuilder()
embed.setTitle("Bem-vindo(a)! 🌸")
embed.setDescription("Olá, <@" .. member.id .. ">!\nVocê é o membro nº " .. guild.memberCount)
embed.setColor("#A9D6FF")
embed.setFooter({ text: "Ayami Hoshiori" })
embed.setTimestamp(nil)
canal.send("", { embed: embed })
end
on command("trabalhar", message, args)
let ganho = random(10, 100)
db.user(message.author).add("coins", ganho)
let total = db.user(message.author).get("coins")
message.reply("💰 +" .. ganho .. " coins! Saldo: " .. total)
end
on command("saldo", message, args)
let raw = db.user(message.author).get("coins")
let total = raw or 0
message.reply("💳 Saldo: **" .. total .. " coins**")
end
on command("transferir", message, args)
let valor = tonumber(args[0])
let alvo = args[1]
if valor <= 0 or alvo == nil then
message.reply("❌ Use: !transferir <valor> <@usuário>")
return
end
let saldo = db.user(message.author).get("coins") or 0
if saldo < valor then
message.reply("❌ Saldo insuficiente.")
return
end
db.user(message.author).sub("coins", valor)
db.user(alvo).add("coins", valor)
message.reply("✅ Transferência de **" .. valor .. " coins** realizada!")
end
on command("painel", message, args)
let btn1 = Button()
btn1.setLabel("🎨 Designer")
btn1.setStyle("primary")
btn1.setCustomId("cargo_designer")
let btn2 = Button()
btn2.setLabel("💻 Dev")
btn2.setStyle("secondary")
btn2.setCustomId("cargo_dev")
let embed = new EmbedBuilder()
embed.setTitle("🎭 Selecione seu cargo")
embed.setDescription("Clique no botão da sua área.")
embed.setColor("#7C8FFF")
message.reply("", { embed: embed, components: [btn1, btn2] })
end
on buttonClick(interaction)
let user = getUser()
let id = interaction.customId
if id == "cargo_designer" then
user.addRole("ID_CARGO_DESIGNER")
interaction.reply("✅ Cargo Designer adicionado!", { ephemeral: true })
elseif id == "cargo_dev" then
user.addRole("ID_CARGO_DEV")
interaction.reply("✅ Cargo Dev adicionado!", { ephemeral: true })
end
end
¿Listo para empezar?
Añade a Ayami a tu servidor y abre el editor en el Dashboard para crear tus primeros scripts.