Documentation

Lua 5.5 · API v0.1

Vue d'ensemble

Les scripts sont de petits programmes Lua qui étendent votre serveur. Ils s'exécutent dans un processus bac à sable Lua 5.5 isolé, complètement séparé du bot. Un script ne touche jamais à la base de données, au système de fichiers ni aux autres serveurs. Chaque capacité passe par une API hôte autorisée qui renvoie la main au bot via IPC.

Un bac à sable par script

Aucun script chargé, aucun processus.

Appels hôte limités en débit

Les appels bot:* et msg:* sont vérifiés par le bot selon les capacités, pas par Lua.

Les fonctions natives sont prioritaires

AutoMod, l'accueil et les tickets s'exécutent toujours. Les scripts complètent, ils ne remplacent pas.

Les erreurs restent isolées

Un script qui plante est déchargé ou mis en quarantaine, le bot continue de fonctionner.

Il n'y a pas d'éditeur dans le navigateur. Écrivez le .lua dans un vrai éditeur et importez-le depuis la page Scripts de votre serveur.

Pour commencer

  1. 1

    Écrire un fichier .lua en local

    VS Code, Neovim ou n'importe quel éditeur.
  2. 2

    Le vérifier en local (facultatif)

    script_runner --check file.lua affiche check: ok.
  3. 3

    L'importer

    Ouvrez la page Scripts de votre serveur, puis collez le code ou importez le fichier .lua (64 Kio max).
  4. 4

    Test à blanc

    Valide la taille, l'encodage, le bytecode et les API interdites sans rien exécuter.
  5. 5

    Activer

    Attendez l'approbation de la version par l'équipe, puis cliquez sur Activer. Le bot la charge en quelques secondes.
  6. 6

    Définir un salon de journalisation

    Vous y recevez la sortie de bot:log et les notifications d'erreur.

CLI locale

Le runner est le même binaire que celui utilisé par le bot.

  • script_runner --check file.lua

    Vérification statique de la syntaxe et des API interdites sans exécution. Affiche « check: ok ».

  • script_runner --eval 'code'

    Exécute un extrait dans le bac à sable en local. Les erreurs affichent le type + le message.

  • script_runner --self-test

    Exécute les autotests du bac à sable, du protocole et des sockets.

Cycle de vie et validation

Les scripts sont versionnés. Une version est figée au moment de sa soumission et identifiée par son SHA-256. Seules les versions approuvées peuvent être activées, et seules les versions actives sont chargées par le bot.

  1. 1

    Écrire

    Créez le fichier .lua en local dans VS Code, Neovim ou n'importe quel éditeur. Il n'y a volontairement pas d'éditeur dans le navigateur.
  2. 2

    Test à blanc / import

    Collez le code ou importez le fichier .lua sur la page Scripts du serveur (≤ 64 Kio, pas de binaire). Le test à blanc valide la taille, l'encodage, le bytecode et les API interdites sans rien exécuter.
  3. 3

    Validation

    Chaque nouveau script et chaque nouvelle version commence en attente. L'équipe approuve ou refuse la révision exacte du code avant qu'elle puisse s'exécuter.
  4. 4

    Activer

    Vous activez une version approuvée. L'activation d'une version en attente ou refusée est refusée. Seules les versions actives sont chargées par le bot.
  5. 5

    Exécuter

    Le bot charge la version (code + SHA-256, vérifié par le runner) dans un bac à sable Lua 5.5 isolé. Les événements ne sont distribués qu'aux gestionnaires enregistrés avec bot:on.
  6. 6

    Mise à jour / retour arrière

    Importez une nouvelle version (de nouveau en attente) ou activez une ancienne version approuvée pour revenir en arrière. Le stockage KV est conservé.
  7. 7

    Désactiver / supprimer

    Désactiver arrête immédiatement le chargement, « Tout désactiver » touche tous les scripts actifs du serveur. Supprimer retire le script, ses versions, son KV et ses erreurs.

État du script

Affiché sous forme de badge sur chaque script.

  • brouillon

    Créé, pas encore de version active.

  • en attente

    En attente de quelque chose (p. ex. la première approbation) — ne s'exécute jamais en tant que brouillon.

  • actif

    Une version approuvée est activée et chargée par le bot.

  • refusé

    Refusé par l'équipe ; le script ne sera pas chargé.

  • désactivé

    Désactivé manuellement (par vous, l'équipe, « Tout désactiver » ou suppression de quota) — déchargé.

  • en quarantaine

    Arrêt automatique après un abus de limites (surcharge de débit, surcharge de file, fautes répétées). Déchargé et nécessite l'attention de l'équipe.

Validation des versions

Chaque révision du code est examinée par l'équipe.

  • en attente

    L'équipe n'a pas encore décidé. Ne peut pas être activée.

  • approuvée

    Peut être activée. Les nouveaux imports de scripts de la boutique installés passent la file d'attente (la version de la fiche a déjà été approuvée).

  • refusée

    L'équipe a refusé cette révision du code. Importez plutôt une nouvelle version.

Quota : 2 scripts actifs par serveur en gratuit, 10 avec premium. Si le quota diminue, les plus anciens scripts actifs au-delà sont désactivés automatiquement et vous êtes prévenu via le salon de journalisation des scripts.

Gestionnaires et événements

Enregistrez des gestionnaires avec bot:on(event, fn). Le script déclare ses événements enregistrés au chargement, et seuls ces événements lui sont transmis. Un nom d'événement inconnu fait échouer le chargement, et un script peut enregistrer au maximum 8 gestionnaires par événement.

luaLua
bot:on("message_create", function(msg)
  if msg.content == "!ping" then
    msg:reply("pong")
  end
end)

Les événements de type message (message_*, automod_check) transmettent un objet message avec des méthodes, voir Objet message.

Les appels hôte rendent la main : tant qu'un gestionnaire attend bot:kv_get ou des appels similaires, aucun second événement ne s'exécute sur le même script. Les nouveaux événements attendent dans une file de 32, un dépassement supprime l'événement le plus ancien, et un flot prolongé met le script en quarantaine.

guild_id est toujours injecté dans la charge utile. Les événements ne sont transmis qu'aux gestionnaires enregistrés avec bot:on. Les noms d'événements inconnus font échouer le chargement avec une erreur de politique. Il n'existe pas d'événement voice_state.

Données des événements

Chaque charge utile contient guild_id. Les champs sont des tables en snake_case minuscule.

  • message_create

    id, guild_id, channel_id, author_id, author, content, attachments, referenced_id

    Se déclenche avant les tags/réponses automatiques. Pièces jointes : jusqu'à 8 entrées { id, filename, size }. referenced_id vaut 0 s'il n'y a pas de référence de réponse.

  • message_update

    same as message_create

  • message_delete

    channel_id, id

  • message_delete_bulk

    channel_id, ids

    ids est un tableau des identifiants de messages supprimés.

  • guild_member_add

    user_id

    Les comptes de bots sont ignorés.

  • guild_member_remove

    user_id

  • guild_member_update

    user_id

  • reaction_add

    channel_id, id, author_id, user_id, emoji, emoji_id

    Les réactions de bots sont ignorées. emoji est le nom, emoji_id vaut 0 pour les 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 déclenche avant le message de bienvenue natif. Uniquement pour les arrivées hors bots.

  • goodbye_before

    user_id

    Se déclenche avant le message de départ natif.

  • ticket_created

    ticket_id, user_id, channel_id

  • automod_check

    content, channel_id, author_id, id

    Se déclenche avant l'AutoMod natif. msg:escalate() supprime le message en cours ; le refus natif l'emporte toujours ; msg:allow() n'a aucun effet.

  • webhooks_update

    channel_id

  • emoji_update

    count, ids

    ids limité à 8.

  • tick

    unix

    Toutes les 60 secondes, transmis uniquement aux scripts qui ont enregistré bot:on("tick"). unix est l'heure Unix.

API hôte

Ces appels rendent la main au bot via IPC. Limites de débit par script : 30 appels hôte / 10 s, plus 5 envois Discord / 10 s (reply, channel_send, react, edit, delete, pin, escalate, http_get). Les lectures de cache comme member_get et guild_get n'atteignent jamais Discord directement.

  • bot:log(msg)

    Écrit dans le salon de journalisation des scripts et dans le journal d'erreurs. Compte dans la limite de 30 appels / 10 s.

  • bot:kv_get(key)

    Renvoie la valeur JSON stockée ou nil. Clés : 1 à 64 caractères [A-Za-z0-9_-].

  • bot:kv_set(key, value)

    Stocke jusqu'à 8 Kio de JSON. 256 clés par script, 1 Mio par script (8 Mio en premium), 8 Mio par serveur.

  • bot:kv_del(key)

    Supprime une clé.

  • bot:kv_keys()

    Renvoie jusqu'à 256 clés, triées.

  • bot:kv_incr(key, delta?)

    Incrémente un nombre JSON stocké, delta par défaut 1, delta limité à ±1 000 000 000.

  • bot:db_exec(sql, binds?)

    Exécute une instruction d'écriture ou de schéma (CREATE TABLE/INDEX, INSERT, UPDATE, DELETE, DROP) sur la base de données de scripts du serveur. Binds facultatifs : un tableau de nil, boolean, number ou string, 8 maximum. Renvoie { changes, last_insert_rowid }. Les problèmes de quota, d'occupation ou de limite renvoient { error, kind } au lieu de lever une erreur ; les instructions interdites lèvent une erreur de politique.

  • bot:db_query(sql, binds?)

    Exécute un SELECT avec des binds facultatifs et renvoie jusqu'à 50 lignes, chacune étant une table indexée par nom de colonne. Utilisez toujours LIMIT. En cas d'échec, renvoie { error, kind }, donc vérifiez rows.error.

  • bot:http_get(url)

    GET HTTPS uniquement. L'hôte doit figurer dans la liste d'autorisation du serveur. Renvoie { status, body, truncated } ; le corps est limité à 16 Kio. L'attente ne compte pas dans les 100 ms d'horloge murale Lua.

  • bot:channel_send(channel_id, text, embed?)

    Envoie dans n'importe quel salon du serveur où le bot peut écrire. Mêmes règles de contenu que msg:reply.

  • bot:channel_get(channel_id)

    Lecture du cache : { id, name, type, parent_id }. Serveur actuel uniquement.

  • bot:member_get(user_id)

    Lecture du cache : { id, nick, bot, roles (20 maximum) }.

  • bot:role_get(role_id)

    Lecture du cache : { id, name, position, color }.

  • bot:guild_get()

    Cache du serveur actuel : { id, name, owner_id, member_count }.

  • bot:self()

    L'utilisateur bot : { id, username, bot }.

  • bot:has_role(user_id, role_id)

    Lecture du cache, renvoie { has: true|false }.

  • bot:channels()

    Liste en cache des salons du serveur (50 max).

  • bot:roles()

    Liste en cache des rôles du serveur (50 max).

  • bot:now()

    Heure Unix en secondes.

  • bot:id()

    L'UUID de ce script. Ne rend pas la main et ne compte pas dans les limites de débit.

  • bot:message_get(channel_id, message_id)

    Lecture du cache de messages du bot (sans récupération réseau).

  • bot:can_send(channel_id)

    Renvoie { ok: true|false } selon que le bot peut envoyer dans ce salon.

Ce qu'accepte bot:http_get

  • HTTPS uniquement

    Les URL http:// sont refusées. Pas d'identifiants dans l'URL (user:pass@host).

  • Liste d'autorisation du serveur

    L'hôte doit figurer dans la liste d'autorisation HTTP du serveur (page Scripts, jusqu'à 32 hôtes). Vous la gérez vous-même.

  • Pas d'hôtes d'infrastructure

    Les adresses IP, localhost, *.local et *.internal sont toujours refusés.

  • Réponse

    { status, body, truncated }, corps limité à 16 Kio. Délai d'attente de 3 s. Partage le quota de 5 envois par 10 s de Discord.

Objet message

Les événements de message transmettent une table msg ainsi que les champs de la charge utile. Les méthodes agissent uniquement sur le message de l'événement en cours, vous ne pouvez pas récupérer et modifier des messages arbitraires.

Règles de contenu partout : texte de 1 à 2000 octets, au plus un embed { title, description, color }, les mentions ne sont résolues que pour l'auteur de l'événement, @everyone et @here sont supprimés. Pas de fichiers ni de stickers.

  • msg:reply(text, embed?)

    Répond dans le salon de l'événement. Les mentions ne sont résolues que pour l'auteur de l'événement ; @everyone / @here sont supprimés. Texte de 1 à 2000 octets, un seul embed maximum.

  • msg:react(emoji)

    Ajoute une réaction au message de l'événement.

  • msg:unreact(emoji)

    Retire la réaction du script lui-même.

  • msg:edit(text, embed?)

    Modifie le message en cours. Mêmes règles de contenu que pour reply.

  • msg:delete()

    Supprime le message en cours.

  • msg:pin() / msg:unpin()

    Épingle ou désépingle le message en cours.

  • msg:escalate()

    automod_check uniquement : supprime le message en cours (action supplémentaire, limitée en débit). Les décisions de l'AutoMod natif l'emportent toujours.

  • msg:allow()

    automod_check uniquement : marque le message comme autorisé. Cela ne peut pas annuler un refus natif — c'est en pratique un simple marqueur sans effet.

Limites et quotas

Toutes les limites se trouvent dans un fichier de configuration que le bot et le runner partagent. Elles sont appliquées à l'exécution, et pas seulement documentées ici. Les valeurs indiquées sont les valeurs par défaut actuelles.

  • Langage

    Lua 5.5, fragments de texte uniquement (le bytecode est refusé). Pas de io/os.file/require/load/debug/coroutine.

  • Calcul

    ~100 ms d'horloge murale par gestionnaire, 10 000 000 d'instructions (hook de comptage).

  • Mémoire

    2 Mo par script en gratuit, 8 Mo en premium.

  • Taille du code

    .lua UTF-8 de 64 Kio max, sans octets NUL, sans bytecode.

  • Appels hôte

    30 par 10 s par script (bot:log inclus).

  • Envois Discord

    5 par 10 s par script (reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 8 refus en 10 s → quarantaine.

  • Gestionnaires

    8 max par événement et par script. Les noms d'événements inconnus sont refusés au chargement.

  • File d'événements

    Profondeur de 32 par script, suppression du plus ancien en cas de dépassement. 8 pertes ou plus en 10 s → quarantaine.

  • HTTP

    HTTPS + liste d'autorisation du serveur, délai de 3 s, corps de 16 Kio, pas d'hôtes IP/localhost/internes, pas d'identifiants dans l'URL.

  • KV

    Clés de 1 à 64 [A-Za-z0-9_-], valeurs ≤ 8 Kio de JSON, 256 clés/script, 1 Mio/script (8 Mio en premium), 8 Mio/serveur.

  • Base de données

    Un fichier SQLite par serveur, partagé par tous ses scripts : 1 Mio (8 Mio en premium), une instruction par appel, SQL ≤ 2 Kio, ≤ 8 binds, ≤ 50 lignes, 20 ms par instruction, 64 tables et index. Pas d'ATTACH, PRAGMA, transactions, triggers, vues ni WITH. Chaque script du serveur peut lire et supprimer chaque table.

  • Scripts actifs

    2 par serveur en gratuit, 10 en premium. Les plus anciens scripts actifs en excès sont désactivés automatiquement.

  • Versions

    Nombre illimité par script ; chaque nouvelle version reste en attente jusqu'à son approbation par l'équipe.

  • Mentions

    Auteur de l'événement uniquement. @everyone / @here supprimés. Pas de fichiers ni de stickers.

  • Isolation

    3 fautes (timeout/memory/policy/runtime) → worker isolé dédié (4 max), sinon quarantaine + déchargement.

  • Journal d'erreurs

    Dernières 24 h visibles dans le tableau de bord, entrées purgées après 7 jours.

  • Interdit

    Pas de voice_state, pas d'API mod_* (ban/kick/timeout), pas d'accès inter-serveurs, pas de base de données/système de fichiers, pas de monétisation.

  • AutoMod

    L'AutoMod natif s'exécute en premier et l'emporte toujours sur les scripts.

Vérifications

Avant qu'une version soit enregistrée, le backend exécute des vérifications statiques. Le test à blanc fait la même chose sans enregistrer. Le bac à sable lui-même est la seconde ligne de défense : les globales interdites n'existent tout simplement pas à l'exécution.

  • Taille / encodage

    Non vide, ≤ 64 Kio, UTF-8 sans octets NUL.

  • Bytecode

    Tout ce qui commence par la signature du bytecode Lua est refusé.

  • API interdites

    L'analyse statique refuse : load(, loadfile, dofile, require(, os.execute, io.open, debug., string.dump, loadstring, getfenv, setfenv, package., coroutine., ffi., jit.

  • Bac à sable d'exécution

    Même si quelque chose passe à travers l'analyse statique, le bac à sable n'expose tout simplement pas ces globales — l'appel échoue avec une erreur de politique.

Fonctions intégrées disponibles : la bibliothèque Lua standard sans accès aux fichiers. string, table, math, tostring, tonumber, type, pairs, ipairs, os.time, os.clock, os.difftime et bot.

Erreurs et quarantaine

Chaque erreur de gestionnaire est enregistrée avec un type et affichée sur la page de détail du script (dernières 24 h). Les entrées sont purgées après 7 jours.

  • runtime

    Une erreur Lua dans votre gestionnaire (accès nil, arguments incorrects, …). Le message inclut le texte de l'erreur Lua.

  • timeout

    Le gestionnaire a dépassé les 100 ms d'horloge murale ou un appel IPC a expiré (1 s).

  • memory

    Le script a dépassé sa limite de mémoire.

  • policy

    Le script a tenté une action non autorisée : événement inconnu, trop de gestionnaires, API interdite, charge utile trop volumineuse.

Les abus répétés entraînent une escalade automatique : 3 fautes (timeout, memory, policy, runtime) déplacent le script vers son propre worker isolé. Si le comportement fautif continue, ou si aucun emplacement isolé n'est libre (4 max), le script est mis en quarantaine : déchargé et signalé dans le tableau de bord. Les surcharges de débit (8 envois Discord refusés ou 8 pertes de file en 10 s) entraînent une quarantaine immédiate. Vous êtes prévenu via le salon de journalisation des scripts ou en message privé.

Côté exploitation : le bot peut être mis en pause globalement via un interrupteur d'urgence sans redémarrage, et un signal scripts:updated recharge les versions modifiées en quelques secondes. Ces deux actions sont réservées à l'équipe.

Boutique

La boutique vous permet de partager des scripts publiquement et d'installer des scripts d'autres serveurs. La publication et l'installation créent toutes deux des copies. Installer une fiche crée un script indépendant dans votre serveur, il ne reste pas lié au code de l'auteur.

  • Publier

    Nécessite un script actif avec une version approuvée, une description (10 à 512 caractères) et 1 à 8 tags ([a-z0-9-], 1 à 24 caractères). Vous devez accepter une fois les conditions d'utilisation de la boutique avant la première publication.

  • Installer

    L'installation copie la fiche dans votre serveur comme un script à part entière (brouillon, version pré-approuvée, nom propre avec suffixe s'il est déjà pris). Le KV n'est pas copié. Le compteur d'installations de la fiche augmente.

  • Dépublier

    Retire la fiche de la boutique. Les copies existantes restent et continuent de fonctionner ; elles ne reçoivent simplement plus de mises à jour.

  • Pas d'argent

    Les ventes, accès payants, liens de dons/crypto et toute monétisation similaire dans la description ou le code sont refusés à la publication et peuvent entraîner le bannissement du script.

  • Signalements

    Toute personne ayant accès au serveur peut signaler une fiche en indiquant un motif. L'équipe voit une file de signalements ouverts et les clôture après examen.

  • Limites

    La liste de la boutique affiche jusqu'à 50 fiches par requête (recherche, tag, tri par installations ou nouveauté). Le quota normal de scripts du serveur s'applique aux installations.

Exemples

Sélectionnez un exemple pour voir son code.