Visão geral
Scripts são pequenos programas em Lua que ampliam seu servidor. Eles rodam em um processo sandbox isolado de Lua 5.5, totalmente separado do bot. Um script nunca acessa o banco de dados, o sistema de arquivos ou outros servidores. Toda capacidade passa por uma API do host em lista de permissões, que devolve o controle ao bot via IPC.
Sem script carregado, não existe processo.
As chamadas bot:* e msg:* têm as permissões verificadas pelo bot, não pelo Lua.
AutoMod, boas-vindas e tickets sempre rodam. Scripts complementam, não substituem.
Um script que trava é descarregado ou colocado em quarentena, e o bot continua funcionando.
Não há editor no navegador. Escreva o .lua em um editor de verdade e envie na página Scripts do seu servidor.
Primeiros passos
- 1
Escreva um arquivo .lua localmente
VS Code, Neovim ou qualquer editor. - 2
Verifique localmente (opcional)
script_runner --check file.luaimprimecheck: ok. - 3
Envie
Abra a página Scripts do seu servidor e cole o código ou envie o arquivo .lua (até 64 KiB). - 4
Teste
Valida tamanho, codificação, bytecode e APIs proibidas sem executar nada. - 5
Ative
Aguarde a aprovação da versão pela equipe e depois clique em Ativar. O bot carrega o script em segundos. - 6
Defina um canal de log
Receba ali a saída debot:loge as notificações de erro.
CLI local
O runner é o mesmo binário que o bot usa.
- script_runner --check file.lua
Verificação estática de sintaxe/APIs proibidas sem executar. Imprime 'check: ok'.
- script_runner --eval 'code'
Executa um trecho na sandbox localmente. Erros imprimem tipo + mensagem.
- script_runner --self-test
Executa os autotestes da sandbox, do protocolo e do socket.
Ciclo de vida e revisão
Os scripts têm versões. Uma versão é congelada quando você a envia e é identificada pelo seu SHA-256. Apenas versões aprovadas podem ser ativadas, e apenas versões ativas são carregadas pelo bot.
- 1
Escrever
Crie o arquivo .lua localmente no VS Code, Neovim ou qualquer editor. Não há editor no navegador, por decisão de projeto. - 2
Testar / enviar
Cole o código ou envie o arquivo .lua na página Scripts do servidor (≤ 64 KiB, sem binário). O teste valida tamanho, codificação, bytecode e APIs proibidas sem executar nada. - 3
Revisão
Todo novo script e toda nova versão começam como pendentes. A equipe aprova ou rejeita a revisão exata do código antes que ela possa rodar. - 4
Ativar
Você ativa uma versão aprovada. A ativação de uma versão pendente ou rejeitada é recusada. Apenas versões ativas são carregadas pelo bot. - 5
Executar
O bot carrega a versão (código + SHA-256, verificado pelo runner) em uma sandbox isolada de Lua 5.5. Os eventos são distribuídos apenas aos handlers registrados com bot:on. - 6
Atualizar / reverter
Envie uma nova versão (pendente de novo) ou ative uma versão aprovada mais antiga para reverter. O armazenamento KV permanece. - 7
Desativar / excluir
Desativar interrompe o carregamento imediatamente; 'Desativar todos' afeta todos os scripts ativos do servidor. Excluir remove o script, suas versões, o KV e os erros.
Status do script
Exibido como selo em cada script.
- rascunho
Criado, ainda sem versão ativa.
- pendente
Aguardando algo (ex.: a primeira aprovação); nunca roda como rascunho.
- ativo
Uma versão aprovada está ativada e carregada pelo bot.
- rejeitado
A equipe rejeitou; o script não será carregado.
- desativado
Desativado manualmente (por você, pela equipe, por 'Desativar todos' ou por redução de cota): descarregado.
- em quarentena
Desligamento automático após abuso de limites (inundação de taxa, inundação da fila, falhas repetidas). Descarregado e precisa da atenção da equipe.
Revisão de versões
Toda revisão de código passa pela revisão da equipe.
- pendente
A equipe ainda não decidiu. Não pode ser ativada.
- aprovada
Pode ser ativada. Novos envios de scripts instalados da loja pulam a fila (a versão da listagem já foi aprovada).
- rejeitada
A equipe recusou esta revisão do código. Envie uma nova versão.
Cota: 2 scripts ativos por servidor no plano gratuito, 10 com premium. Se a cota diminuir, os scripts ativos mais antigos acima dela são desativados automaticamente e você é avisado pelo canal de log do script.
Handlers e eventos
Registre handlers com bot:on(event, fn). O script informa seus eventos registrados ao carregar, e apenas esses eventos são entregues a ele. Nomes de eventos desconhecidos fazem o carregamento falhar, e um script pode registrar no máximo 8 handlers por evento.
bot:on("message_create", function(msg)
if msg.content == "!ping" then
msg:reply("pong")
end
end)Eventos do tipo mensagem (message_*, automod_check) recebem um objeto de mensagem com métodos, veja Objeto de mensagem.
Chamadas ao host cedem a execução: enquanto um handler espera por bot:kv_get e chamadas parecidas, nenhum segundo evento roda no mesmo script. Novos eventos esperam em uma fila de 32; no estouro, o evento mais antigo é descartado, e uma inundação contínua coloca o script em quarentena.
guild_id é sempre injetado no payload. Os eventos só são entregues a handlers registrados com bot:on. Nomes de eventos desconhecidos fazem o carregamento falhar com um erro de policy. Não existe evento voice_state.
Payloads de eventos
Todo payload traz guild_id. Os campos são tabelas em snake_case minúsculo.
- message_create
id, guild_id, channel_id, author_id, author, content, attachments, referenced_id
Disparado antes de tags/respostas automáticas. Anexos: até 8 entradas { id, filename, size }. referenced_id é 0 quando não há referência de resposta.
- message_update
same as message_create
- message_delete
channel_id, id
- message_delete_bulk
channel_id, ids
ids é um array com os ids das mensagens excluídas.
- guild_member_add
user_id
Contas de bots são ignoradas.
- guild_member_remove
user_id
- guild_member_update
user_id
- reaction_add
channel_id, id, author_id, user_id, emoji, emoji_id
Reações de bots são ignoradas. emoji é o nome; emoji_id é 0 para 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
Disparado antes da mensagem nativa de boas-vindas. Apenas entradas que não são bots.
- goodbye_before
user_id
Disparado antes da mensagem nativa de despedida.
- ticket_created
ticket_id, user_id, channel_id
- automod_check
content, channel_id, author_id, id
Disparado antes do AutoMod nativo. msg:escalate() exclui a mensagem em andamento; a negação nativa sempre prevalece; msg:allow() não faz nada.
- webhooks_update
channel_id
- emoji_update
count, ids
ids limitado a 8.
- tick
unix
A cada 60 segundos, entregue apenas a scripts que registraram bot:on("tick"). unix é o horário unix.
APIs do host
Estas chamadas cedem a execução ao bot via IPC. Limites de taxa por script: 30 chamadas ao host / 10 s, mais 5 envios ao Discord / 10 s (reply, channel_send, react, edit, delete, pin, escalate, http_get). Leituras de cache como member_get e guild_get nunca acessam o Discord diretamente.
- bot:log(msg)
Escreve no canal de log do script e no log de erros. Conta para o limite de 30 chamadas / 10 s.
- bot:kv_get(key)
Retorna o valor JSON armazenado ou nil. Chaves: 1–64 caracteres [A-Za-z0-9_-].
- bot:kv_set(key, value)
Armazena até 8 KiB de JSON. 256 chaves por script, 1 MiB por script (8 MiB no premium), 8 MiB por servidor.
- bot:kv_del(key)
Exclui uma chave.
- bot:kv_keys()
Retorna até 256 chaves, ordenadas.
- bot:kv_incr(key, delta?)
Incrementa um número JSON armazenado; delta padrão 1, limitado a ±1.000.000.000.
- bot:db_exec(sql, binds?)
Executa uma instrução de escrita ou de esquema (CREATE TABLE/INDEX, INSERT, UPDATE, DELETE, DROP) no banco de dados de scripts do servidor. Binds opcionais: um array de nil, boolean, number ou string, até 8. Retorna { changes, last_insert_rowid }. Problemas de cota, ocupação ou limite retornam { error, kind } em vez de lançar erro; instruções proibidas lançam um erro de policy.
- bot:db_query(sql, binds?)
Executa um SELECT com binds opcionais e retorna até 50 linhas, cada uma uma tabela indexada pelo nome da coluna. Sempre use LIMIT. Em caso de falha, retorna { error, kind }, então verifique rows.error.
- bot:http_get(url)
Apenas HTTPS GET. O host precisa estar na lista de permissões do servidor. Retorna { status, body, truncated }; body limitado a 16 KiB. A espera não conta para os 100 ms de tempo real do Lua.
- bot:channel_send(channel_id, text, embed?)
Envia para qualquer canal do próprio servidor em que o bot possa escrever. Mesmas regras de conteúdo de msg:reply.
- bot:channel_get(channel_id)
Leitura de cache: { id, name, type, parent_id }. Apenas o próprio servidor.
- bot:member_get(user_id)
Leitura de cache: { id, nick, bot, roles (até 20) }.
- bot:role_get(role_id)
Leitura de cache: { id, name, position, color }.
- bot:guild_get()
Cache do próprio servidor: { id, name, owner_id, member_count }.
- bot:self()
O usuário do bot: { id, username, bot }.
- bot:has_role(user_id, role_id)
Leitura de cache, retorna { has: true|false }.
- bot:channels()
Lista em cache dos canais do servidor (máx. 50).
- bot:roles()
Lista em cache dos cargos do servidor (máx. 50).
- bot:now()
Horário unix em segundos.
- bot:id()
O UUID deste script. Não cede a execução e não conta para os limites de taxa.
- bot:message_get(channel_id, message_id)
Leitura do cache de mensagens do bot (sem busca na rede).
- bot:can_send(channel_id)
Retorna { ok: true|false } indicando se o bot pode enviar naquele canal.
O que bot:http_get aceita
- Somente HTTPS
URLs http:// são negadas. Sem userinfo (user:pass@host).
- Lista de permissões do servidor
O host precisa estar na lista de permissões HTTP do servidor (página Scripts, até 32 hosts). Você mesmo a gerencia.
- Sem hosts de infraestrutura
IPs literais, localhost, *.local e *.internal são sempre negados.
- Resposta
{ status, body, truncated }, body limitado a 16 KiB. Tempo limite de 3 s. Compartilha o limite de 5 por 10 s dos envios ao Discord.
Objeto de mensagem
Eventos de mensagem recebem uma tabela msg mais os campos do payload. Os métodos agem apenas sobre a mensagem do evento em andamento; não é possível buscar e editar mensagens arbitrárias.
Regras de conteúdo em todo lugar: texto de 1 a 2000 bytes, no máximo um embed { title, description, color }, menções resolvem apenas para o autor do evento, @everyone e @here são removidos. Sem arquivos, sem figurinhas.
- msg:reply(text, embed?)
Responde no canal do evento. Menções resolvem apenas para o autor do evento; @everyone / @here são removidos. Texto de 1–2000 bytes, no máximo um embed.
- msg:react(emoji)
Adiciona uma reação à mensagem do evento.
- msg:unreact(emoji)
Remove a própria reação do script.
- msg:edit(text, embed?)
Edita a mensagem em andamento. Mesmas regras de conteúdo do reply.
- msg:delete()
Exclui a mensagem em andamento.
- msg:pin() / msg:unpin()
Fixa/desafixa a mensagem em andamento.
- msg:escalate()
Somente automod_check: exclui a mensagem em andamento (ação extra, com limite de taxa). As decisões do AutoMod nativo sempre prevalecem.
- msg:allow()
Somente automod_check: marca a mensagem como permitida. Isso não desfaz uma negação nativa; na prática, é um marcador sem efeito.
Limites e cotas
Todos os limites ficam em um único arquivo de configuração compartilhado pelo bot e pelo runner. Eles são aplicados em tempo de execução, não apenas documentados aqui. Os números são os padrões atuais.
- Linguagem
Lua 5.5, apenas trechos de texto (bytecode é rejeitado). Sem io/os.file/require/load/debug/coroutine.
- Processamento
~100 ms de tempo real por handler, 10.000.000 de instruções (count hook).
- Memória
2 MB por script no gratuito, 8 MB no premium.
- Tamanho do código
64 KiB de .lua em UTF-8, sem bytes NUL, sem bytecode.
- Chamadas ao host
30 por 10 s por script (bot:log incluído).
- Envios ao Discord
5 por 10 s por script (reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 8 negações em 10 s → quarentena.
- Handlers
Máx. 8 por evento por script. Nomes de eventos desconhecidos são rejeitados ao carregar.
- Fila de eventos
Profundidade 32 por script, descarta o mais antigo no estouro. 8+ descartes em 10 s → quarentena.
- HTTP
HTTPS + lista de permissões do servidor, tempo limite de 3 s, body de 16 KiB, sem hosts IP/localhost/internos, sem userinfo.
- KV
Chaves 1–64 [A-Za-z0-9_-], valores ≤ 8 KiB de JSON, 256 chaves/script, 1 MiB/script (8 MiB no premium), 8 MiB/servidor.
- Banco de dados
Um arquivo SQLite por servidor, compartilhado por todos os seus scripts: 1 MiB (8 MiB no premium), uma instrução por chamada, SQL ≤ 2 KiB, ≤ 8 binds, ≤ 50 linhas, 20 ms por instrução, 64 tabelas e índices. Sem ATTACH, PRAGMA, transações, triggers, views ou WITH. Todo script do servidor pode ler e excluir todas as tabelas.
- Scripts ativos
2 por servidor no gratuito, 10 no premium. Os scripts ativos mais antigos que excederem são desativados automaticamente.
- Versões
Quantidade ilimitada por script; toda nova versão fica pendente até a equipe aprovar.
- Menções
Apenas o autor do evento. @everyone / @here removidos. Sem arquivos, sem figurinhas.
- Isolamento
3 falhas (timeout/memory/policy/runtime) → worker isolado próprio (máx. 4), senão quarentena + descarregamento.
- Log de erros
Últimas 24 h visíveis no painel, entradas apagadas após 7 dias.
- Proibido
Sem voice_state, sem APIs mod_* (ban/kick/timeout), sem acesso entre servidores, sem banco de dados/sistema de arquivos, sem monetização.
- AutoMod
O AutoMod nativo roda primeiro e sempre prevalece sobre os scripts.
Validação
Antes de uma versão ser armazenada, o backend executa verificações estáticas. O teste faz o mesmo sem salvar. A própria sandbox é a segunda linha de defesa: globais proibidas simplesmente não existem em tempo de execução.
- Tamanho / codificação
Não vazio, ≤ 64 KiB, UTF-8 sem bytes NUL.
- Bytecode
Qualquer coisa que comece com a assinatura de bytecode do Lua é rejeitada.
- APIs proibidas
A verificação estática rejeita: load(, loadfile, dofile, require(, os.execute, io.open, debug., string.dump, loadstring, getfenv, setfenv, package., coroutine., ffi., jit.
- Sandbox em tempo de execução
Mesmo que algo passe pela verificação estática, a sandbox simplesmente não expõe essas globais: a chamada falha com um erro de policy.
Built-ins disponíveis: a biblioteca padrão de Lua sem acesso a arquivos. string, table, math, tostring, tonumber, type, pairs, ipairs, os.time, os.clock, os.difftime e bot.
Erros e quarentena
Todo erro de handler é registrado com um tipo e exibido na página de detalhes do script (últimas 24 h). As entradas são apagadas após 7 dias.
- runtime
Um erro de Lua no seu handler (acesso a nil, argumentos inválidos, …). A mensagem inclui o texto do erro de Lua.
- timeout
O handler excedeu os 100 ms de tempo real ou uma chamada IPC expirou (1 s).
- memory
O script excedeu seu limite de memória.
- policy
O script tentou algo não permitido: evento desconhecido, handlers demais, API não permitida, payload grande demais.
Abusos repetidos escalam automaticamente: 3 falhas (timeout, memory, policy, runtime) movem o script para um worker isolado próprio. Se o mau comportamento continuar, ou se não houver vaga isolada livre (máx. 4), o script é colocado em quarentena: descarregado e marcado no painel. Inundações de taxa (8 envios ao Discord negados ou 8 descartes na fila em 10 s) causam quarentena imediata. Você é avisado pelo canal de log do script ou por DM.
Lado operacional: o bot pode ser pausado globalmente por um kill switch sem reinício, e um sinal scripts:updated recarrega versões alteradas em segundos. Ambos são ações da equipe.
Loja
A loja permite compartilhar scripts publicamente e instalar scripts de outros servidores. Publicar e instalar criam cópias. Instalar uma listagem cria um script independente no seu servidor, sem vínculo com o código do autor.
- Publicar
Exige um script ativo com uma versão aprovada, uma descrição (10–512 caracteres) e 1–8 tags ([a-z0-9-], 1–24 caracteres). Você precisa aceitar os termos da loja uma vez antes da primeira publicação.
- Instalar
Instalar copia a listagem para o seu servidor como um script próprio (rascunho, versão pré-aprovada, nome próprio com sufixo se já estiver em uso). O KV não é copiado. O contador de instalações da listagem aumenta.
- Despublicar
Remove a listagem da loja. As cópias existentes permanecem e continuam rodando; apenas não recebem mais atualizações.
- Sem dinheiro
Vendas, paywalls, links de doação/cripto e monetizações parecidas na descrição ou no código são rejeitados na publicação e podem fazer o script ser banido.
- Denúncias
Qualquer pessoa com acesso ao servidor pode denunciar uma listagem informando um motivo. A equipe vê uma fila de denúncias abertas e as fecha após a revisão.
- Limites
A lista da loja mostra até 50 listagens por consulta (pesquisa, tag, ordenação por instalações ou mais recentes). A cota normal de scripts do servidor vale para instalações.
Exemplos
Selecione um exemplo para ver o código.