Logic Script
Ayami's native programming language — lightweight, safe, and built for Discord automations. Write scripts, react to events, send embeds, and manage server data with clean, direct code.
Introduction
Logic Script is an interpreted language built for automations inside Discord servers. Scripts are .logic files that react to Discord events — messages, members joining, button clicks — using the on block.
Every server has its own isolated environment: scripts, database, and settings are completely separate. Logic Script runs inside the bot in a secure sandbox — no access to the operating system or external resources.
Logic Script doesn't replace the Logic Builder — it extends it. Scripts can be called from visual flows and can fire custom events between each other.
-- Meu primeiro script
on messageCreate(message)
if message.content == "!ola" then
message.reply("Olá! 👋")
end
end
Variables
Use let for mutable variables and const for constants. Scope is lexical — variables declared inside a block don't exist outside it.
let nome = "Ayami"
let pontos = 0
let ativo = true
const PREFIX = "!"
-- Reatribuição (só funciona com let)
pontos = pontos + 10
Data types
| Type | Example | Description |
|---|---|---|
| number | 42, 3.14 | Integers and decimals |
| string | "texto", 'outro' | Text in single or double quotes |
| bool | true, false | Boolean |
| nil | nil | Absence of a value |
| array | [1, 2, 3] | Ordered list |
| object | { nome: "A" } | Key → value map |
| function | function() end | Functions are first-class values |
let lista = ["maçã", "pera", "uva"]
let pessoa = { nome: "Ayami", pontos: 100 }
print(lista[0]) -- "maçã"
print(pessoa.nome) -- "Ayami"
print(type(42)) -- "number"
Operators
| Op. | Description | Example |
|---|---|---|
| + | Addition | 2 + 3 → 5 |
| - | Subtraction | 10 - 4 → 6 |
| * | Multiplication | 3 * 4 → 12 |
| / | Division | 10 / 2 → 5 |
| % | Modulo | 10 % 3 → 1 |
| .. | Concatenation | "Oi " .. "Ayami" |
| Op. | Description |
|---|---|
| == | Equal |
| != | Not equal |
| < / > | Less than / Greater than |
| <= / >= | Less/greater than or equal to |
| and | Logical AND |
| or | Logical OR |
| not | Negation |
Control flow
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
Use break to exit a loop and continue to skip to the next iteration.
Functions
-- 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
Organize your code across multiple files. Use export to expose functions and import to use them in another 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
Events (on)
Scripts react to Discord events using the on block. For prefix commands, the first argument is a string with the command name.
on nomeDoEvento(param1, param2)
-- código aqui
end
-- Comando por prefixo (ex: !ping)
on command("ping", message, args)
message.reply("🏓 Pong!")
end
| Event | Parameters | Fires when |
|---|---|---|
| messageCreate | message | A message is sent |
| messageDelete | message | A message is deleted |
| messageEdit | message | A message is edited |
| memberJoin | member | A member joins the server |
| memberLeave | member | A member leaves the server |
| buttonClick | interaction | A button is clicked |
| selectMenu | interaction | A menu option is selected |
| command | "nome", message, args | A prefix command is run |
| reactionAdd | emoji, user, message | A reaction is added |
| reactionRemove | emoji, user, message | A reaction is removed |
| voiceJoin | member | A member joins a voice channel |
| voiceLeave | member | A member leaves a voice channel |
| banAdd | user | A user is banned |
| banRemove | user | A ban is removed |
| channelCreate | channel | A channel is created |
| channelDelete | channel | A channel is deleted |
Discord objects
messageCreate, sendMessage(), and message.reply().getUser() or message.author.getInteraction(). Used in buttonClick and selectMenu.opts.ephemeral: true = private.onSubmit(fn)UI — Embed, Button, SelectMenu, Modal
All methods can be chained. Embed accepts every field Discord supports.
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
Only works in response to an existing interaction (buttonClick/selectMenu, or inside a temporary Button/SelectMenu's .onClick()) — it can't be opened from on(command).
Database
Persistent, per-server database. Data survives across runs and restarts.
-- 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")
Built-in functions
| Function | Description |
|---|---|
| length(s) | String length |
| contains(s, sub) | Whether it contains a substring |
| replace(s, de, para) | Replace text |
| split(s, sep) | Split into an array |
| join(arr, sep) | Join array into a string |
| lower(s) / upper(s) | Lowercase / uppercase |
| trim(s) | Removes whitespace |
| tostring(v) | Converts to string |
| tonumber(s) | Converts to number |
| Function | Description |
|---|---|
| random(min, max) | Random integer between min and max |
| round(n) | Rounds |
| floor(n) / ceil(n) | Floor / ceiling |
| abs(n) | Absolute value |
| max(a, b, …) / min(a, b, …) | Largest / smallest value |
| Function | Description |
|---|---|
| push(arr, v) | Adds to the end |
| pop(arr) | Removes and returns the last item |
| insert(arr, i, v) | Inserts at position i |
| remove(arr, i) | Removes at position i |
| size(v) | Size of the array or object |
| keys(obj) | Object's keys |
| values(obj) | Object's values |
| Function | Description |
|---|---|
| print(…) | Prints to the dashboard console |
| type(v) | Value's type as a string |
| wait(ms) | Waits N ms (max 30,000ms/run) |
| json.encode(v) | Object → JSON string |
| json.decode(s) | JSON string → object |
| Function | Description |
|---|---|
| schedule(tempo, canal, texto, opts?) | Schedules a message — time: ms, "1h", or "9:00". Repeats with {recurring:true} |
| cancelSchedule(id) | Cancels a pending schedule |
| listSchedules() | Lists the server's pending schedules |
| cooldown(chave, tempo) | true if it can act now (and marks it); false if on cooldown |
| cooldownRestante(chave) | Remaining cooldown in ms |
Sandbox & Limits
What Logic Script doesn't have: filesystem access, Node.js modules (require), external network access outside Discord's APIs, access to other servers, or external libraries.
| Limit | Value | Description |
|---|---|---|
| Steps per run | 50 000 | Prevents infinite loops |
| Recursion depth | 200 | Prevents stack overflow |
| Total wait() per run | 30 000ms | Accumulated wait limit |
| Concurrent scripts per guild | 20 | Concurrency limit |
| Log TTL | 7 dias | Logs expire automatically |
Complete examples
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
Ready to get started?
Add Ayami to your server and open the editor in the Dashboard to write your first scripts.