Überblick
Skripte sind kleine Lua-Programme, die deinen Server erweitern. Sie laufen in einem isolierten Lua-5.5-Sandbox-Prozess, völlig getrennt vom Bot. Ein Skript greift nie auf die Datenbank, das Dateisystem oder andere Server zu. Jede Fähigkeit läuft über eine freigegebene Host-API, die per IPC an den Bot zurückgibt.
Ohne geladenes Skript existiert kein Prozess.
bot:*- und msg:*-Aufrufe prüft der Bot auf Berechtigung, nicht Lua.
AutoMod, Willkommensnachrichten und Tickets laufen immer. Skripte ergänzen, sie ersetzen nichts.
Ein abstürzendes Skript wird entladen oder unter Quarantäne gestellt, der Bot läuft weiter.
Es gibt keinen Editor im Browser. Schreibe .lua in einem richtigen Editor und lade die Datei auf der Skripte-Seite deines Servers hoch.
Erste Schritte
- 1
Eine .lua-Datei lokal schreiben
VS Code, Neovim oder ein beliebiger Editor. - 2
Lokal prüfen (optional)
script_runner --check file.luagibtcheck: okaus. - 3
Hochladen
Öffne die Skripte-Seite deines Servers und füge den Code ein oder lade die .lua-Datei hoch (bis 64 KiB). - 4
Testlauf
Prüft Größe, Kodierung, Bytecode und verbotene APIs, ohne etwas auszuführen. - 5
Aktivieren
Warte auf die Freigabe der Version durch das Team und klicke dann auf Aktivieren. Der Bot lädt sie innerhalb von Sekunden. - 6
Log-Kanal festlegen
Dort erscheinenbot:log-Ausgaben und Fehlermeldungen.
Lokale CLI
Der Runner ist dieselbe Datei, die auch der Bot nutzt.
- script_runner --check file.lua
Statische Syntax-/Verbotene-API-Prüfung ohne Ausführung. Gibt „check: ok“ aus.
- script_runner --eval 'code'
Führt ein Snippet lokal in der Sandbox aus. Fehler geben Art + Meldung aus.
- script_runner --self-test
Führt die Selbsttests für Sandbox, Protokoll und Socket aus.
Ablauf & Prüfung
Skripte sind versioniert. Eine Version wird beim Einreichen eingefroren und über ihren SHA-256 identifiziert. Nur freigegebene Versionen lassen sich aktivieren, und nur aktive Versionen lädt der Bot.
- 1
Schreiben
Erstelle die .lua-Datei lokal in VS Code, Neovim oder einem beliebigen Editor. Einen Editor im Browser gibt es bewusst nicht. - 2
Testlauf / Upload
Füge den Code ein oder lade die .lua-Datei auf der Skripte-Seite des Servers hoch (≤ 64 KiB, kein Binärformat). Der Testlauf prüft Größe, Kodierung, Bytecode und verbotene APIs, ohne etwas auszuführen. - 3
Prüfung
Jedes neue Skript und jede neue Version beginnt als ausstehend. Das Team gibt die genaue Code-Revision frei oder lehnt sie ab, bevor sie laufen kann. - 4
Aktivieren
Du aktivierst eine freigegebene Version. Das Aktivieren einer ausstehenden oder abgelehnten Version wird verweigert. Der Bot lädt nur aktive Versionen. - 5
Ausführen
Der Bot lädt die Version (Code + SHA-256, vom Runner verifiziert) in eine isolierte Lua-5.5-Sandbox. Events werden nur an Handler verteilt, die mit bot:on registriert sind. - 6
Update / Rollback
Lade eine neue Version hoch (wieder ausstehend) oder aktiviere eine ältere freigegebene Version für einen Rollback. Der KV-Speicher bleibt erhalten. - 7
Deaktivieren / Löschen
Deaktivieren stoppt das Laden sofort, „Alle deaktivieren“ trifft jedes aktive Skript des Servers. Löschen entfernt das Skript samt Versionen, KV und Fehlern.
Skript-Status
Wird bei jedem Skript als Badge angezeigt.
- draft
Erstellt, noch keine aktive Version.
- pending
Wartet auf etwas (z. B. die erste Freigabe), läuft nie als Entwurf.
- active
Eine freigegebene Version ist aktiviert und wird vom Bot geladen.
- rejected
Vom Team abgelehnt; das Skript wird nicht geladen.
- disabled
Manuell deaktiviert (du, das Team, „Alle deaktivieren“ oder Kontingent-Entfernung), entladen.
- quarantined
Automatische Abschaltung nach Limit-Missbrauch (Ratenüberflutung, Warteschlangen-Überflutung, wiederholte Fehler). Entladen und braucht die Aufmerksamkeit des Teams.
Versionsprüfung
Jede Code-Revision wird vom Team geprüft.
- pending
Das Team hat noch nicht entschieden. Kann nicht aktiviert werden.
- approved
Darf aktiviert werden. Neue Uploads installierter Store-Skripte überspringen die Warteschlange (die Version des Eintrags war bereits freigegeben).
- rejected
Das Team hat diese Code-Revision abgelehnt. Lade stattdessen eine neue Version hoch.
Kontingent: 2 aktive Skripte pro Server kostenlos, 10 mit Premium. Sinkt das Kontingent, werden die ältesten aktiven Skripte darüber automatisch deaktiviert und du wirst im Skript-Log-Kanal benachrichtigt.
Handler & Events
Registriere Handler mit bot:on(event, fn). Das Skript meldet seine registrierten Events beim Laden, und nur diese Events werden ihm zugestellt. Unbekannte Event-Namen lassen das Laden fehlschlagen, und ein Skript darf höchstens 8 Handler pro Event registrieren.
bot:on("message_create", function(msg)
if msg.content == "!ping" then
msg:reply("pong")
end
end)Nachrichtenähnliche Events (message_*, automod_check) übergeben ein Message-Objekt mit Methoden, siehe Message-Objekt.
Host-Aufrufe geben die Kontrolle ab: Während ein Handler auf bot:kv_get und ähnliche Aufrufe wartet, läuft auf demselben Skript kein zweites Event. Neue Events warten in einer Warteschlange mit 32 Plätzen, bei Überlauf fällt das älteste Event weg, und dauerhafte Überflutung stellt das Skript unter Quarantäne.
guild_id wird immer in die Payload eingefügt. Events werden nur an Handler zugestellt, die mit bot:on registriert sind. Unbekannte Event-Namen lassen das Laden mit einem Policy-Fehler fehlschlagen. Es gibt kein voice_state-Event.
Event-Payloads
Jede Payload enthält guild_id. Die Felder sind Tabellen in Kleinbuchstaben mit snake_case.
- message_create
id, guild_id, channel_id, author_id, author, content, attachments, referenced_id
Wird vor Tags und Auto-Respondern ausgelöst. Anhänge: bis zu 8 Einträge { id, filename, size }. referenced_id ist 0, wenn es keine Antwort-Referenz gibt.
- message_update
same as message_create
- message_delete
channel_id, id
- message_delete_bulk
channel_id, ids
ids ist ein Array der gelöschten Nachrichten-IDs.
- guild_member_add
user_id
Bot-Accounts werden übersprungen.
- guild_member_remove
user_id
- guild_member_update
user_id
- reaction_add
channel_id, id, author_id, user_id, emoji, emoji_id
Reaktionen von Bots werden übersprungen. emoji ist der Name, emoji_id ist 0 bei Unicode-Emojis.
- 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
Wird vor der nativen Willkommensnachricht ausgelöst. Nur Beitritte von Nicht-Bots.
- goodbye_before
user_id
Wird vor der nativen Abschiedsnachricht ausgelöst.
- ticket_created
ticket_id, user_id, channel_id
- automod_check
content, channel_id, author_id, id
Wird vor dem nativen AutoMod ausgelöst. msg:escalate() löscht die laufende Nachricht; ein natives Ablehnen gewinnt immer; msg:allow() hat keine Wirkung.
- webhooks_update
channel_id
- emoji_update
count, ids
ids auf 8 begrenzt.
- tick
unix
Alle 60 Sekunden, nur an Skripte zugestellt, die bot:on("tick") registriert haben. unix ist die Unix-Zeit.
Host-APIs
Diese Aufrufe geben per IPC an den Bot ab. Ratenlimits pro Skript: 30 Host-Aufrufe / 10 s sowie 5 Discord-Sendungen / 10 s (reply, channel_send, react, edit, delete, pin, escalate, http_get). Cache-Lesezugriffe wie member_get und guild_get erreichen Discord nie direkt.
- bot:log(msg)
Schreibt in den Skript-Log-Kanal und das Fehlerprotokoll. Zählt zum Limit von 30 Aufrufen / 10 s.
- bot:kv_get(key)
Gibt den gespeicherten JSON-Wert oder nil zurück. Keys: 1–64 Zeichen [A-Za-z0-9_-].
- bot:kv_set(key, value)
Speichert bis zu 8 KiB JSON. 256 Keys pro Skript, 1 MiB pro Skript (8 MiB Premium), 8 MiB pro Server.
- bot:kv_del(key)
Löscht einen Key.
- bot:kv_keys()
Gibt bis zu 256 Keys sortiert zurück.
- bot:kv_incr(key, delta?)
Erhöht eine gespeicherte JSON-Zahl, Standard-Delta 1, Delta auf ±1.000.000.000 begrenzt.
- bot:db_exec(sql, binds?)
Führt ein Schreib- oder Schema-Statement (CREATE TABLE/INDEX, INSERT, UPDATE, DELETE, DROP) auf der Skript-Datenbank des Servers aus. Optionale Binds: Array aus nil, boolean, number oder string, bis zu 8. Gibt { changes, last_insert_rowid } zurück. Quota-, Busy- oder Limit-Probleme liefern { error, kind } statt eines Fehlers; verbotene Statements lösen einen Policy-Fehler aus.
- bot:db_query(sql, binds?)
Führt ein SELECT mit optionalen Binds aus und liefert bis zu 50 Zeilen, jede als Tabelle mit Spaltennamen als Schlüssel. Immer LIMIT verwenden. Bei einem Fehler kommt { error, kind } zurück, also rows.error prüfen.
- bot:http_get(url)
Nur HTTPS-GET. Der Host muss auf der Allowlist des Servers stehen. Gibt { status, body, truncated } zurück; der Body ist auf 16 KiB begrenzt. Die Wartezeit zählt nicht zu den 100 ms Lua-Laufzeit.
- bot:channel_send(channel_id, text, embed?)
Sendet in jeden Kanal des eigenen Servers, in dem der Bot schreiben darf. Es gelten dieselben Inhaltsregeln wie bei msg:reply.
- bot:channel_get(channel_id)
Cache-Lesezugriff: { id, name, type, parent_id }. Nur der eigene Server.
- bot:member_get(user_id)
Cache-Lesezugriff: { id, nick, bot, roles (bis zu 20) }.
- bot:role_get(role_id)
Cache-Lesezugriff: { id, name, position, color }.
- bot:guild_get()
Cache des eigenen Servers: { id, name, owner_id, member_count }.
- bot:self()
Der Bot-Nutzer: { id, username, bot }.
- bot:has_role(user_id, role_id)
Cache-Lesezugriff, gibt { has: true|false } zurück.
- bot:channels()
Cache-Liste der Kanäle des Servers (max. 50).
- bot:roles()
Cache-Liste der Rollen des Servers (max. 50).
- bot:now()
Unix-Zeit in Sekunden.
- bot:id()
Die UUID dieses Skripts. Gibt die Kontrolle nicht ab und zählt nicht zu den Ratenlimits.
- bot:message_get(channel_id, message_id)
Cache-Lesezugriff auf den Nachrichten-Cache des Bots (kein Netzwerkabruf).
- bot:can_send(channel_id)
Gibt { ok: true|false } zurück, ob der Bot in diesem Kanal senden darf.
Was bot:http_get akzeptiert
- Nur HTTPS
http://-URLs werden abgelehnt. Keine Userinfo (user:pass@host).
- Allowlist des Servers
Der Host muss auf der HTTP-Allowlist des Servers stehen (Skripte-Seite, bis zu 32 Hosts). Du verwaltest sie selbst.
- Keine Infrastruktur-Hosts
IP-Adressen, localhost, *.local und *.internal werden immer abgelehnt.
- Antwort
{ status, body, truncated }, Body auf 16 KiB begrenzt. Timeout 3 s. Teilt sich das Discord-Kontingent von 5 pro 10 s.
Message-Objekt
Nachrichten-Events übergeben eine msg-Tabelle plus die Payload-Felder. Die Methoden wirken nur auf die Event-Nachricht selbst, beliebige Nachrichten kannst du nicht abrufen und bearbeiten.
Inhaltsregeln überall: Text 1 bis 2000 Bytes, höchstens ein Embed { title, description, color }, Erwähnungen gelten nur für den Autor des Events, @everyone und @here werden entfernt. Keine Dateien, keine Sticker.
- msg:reply(text, embed?)
Antwortet im Kanal des Events. Erwähnungen gelten nur für den Autor des Events; @everyone / @here werden entfernt. Text 1–2000 Bytes, höchstens ein Embed.
- msg:react(emoji)
Fügt der Event-Nachricht eine Reaktion hinzu.
- msg:unreact(emoji)
Entfernt die eigene Reaktion des Skripts.
- msg:edit(text, embed?)
Bearbeitet die laufende Nachricht. Es gelten dieselben Inhaltsregeln wie bei reply.
- msg:delete()
Löscht die laufende Nachricht.
- msg:pin() / msg:unpin()
Hängt die laufende Nachricht an oder löst sie.
- msg:escalate()
Nur bei automod_check: löscht die laufende Nachricht (Zusatzaktion mit Ratenlimit). Entscheidungen des nativen AutoMod gewinnen immer.
- msg:allow()
Nur bei automod_check: markiert die Nachricht als erlaubt. Ein natives Ablehnen lässt sich damit nicht aufheben, es ist praktisch eine wirkungslose Markierung.
Limits & Kontingente
Alle Limits stehen in einer Konfigurationsdatei, die Bot und Runner gemeinsam nutzen. Sie werden zur Laufzeit durchgesetzt und nicht nur hier dokumentiert. Die Zahlen sind die aktuellen Standardwerte.
- Sprache
Lua 5.5, nur Text-Chunks (Bytecode wird abgelehnt). Kein io/os.file/require/load/debug/coroutine.
- Rechenzeit
~100 ms Laufzeit pro Handler, 10.000.000 Instruktionen (Count-Hook).
- Speicher
2 MB pro Skript kostenlos, 8 MB Premium.
- Code-Größe
64 KiB UTF-8 .lua, keine NUL-Bytes, kein Bytecode.
- Host-Aufrufe
30 pro 10 s und Skript (bot:log eingeschlossen).
- Discord-Sendungen
5 pro 10 s und Skript (reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 8 Ablehnungen in 10 s → Quarantäne.
- Handler
Max. 8 pro Event und Skript. Unbekannte Event-Namen werden beim Laden abgelehnt.
- Event-Warteschlange
32 Plätze pro Skript, bei Überlauf fällt das älteste Event weg. 8+ verworfene Events in 10 s → Quarantäne.
- HTTP
HTTPS + Server-Allowlist, Timeout 3 s, Body 16 KiB, keine IP-/localhost-/internen Hosts, keine Userinfo.
- KV
Keys 1–64 [A-Za-z0-9_-], Werte ≤ 8 KiB JSON, 256 Keys/Skript, 1 MiB/Skript (8 MiB Premium), 8 MiB/Server.
- Datenbank
Eine SQLite-Datei pro Server, geteilt von allen Skripten: 1 MiB (8 MiB Premium), ein Statement pro Aufruf, SQL ≤ 2 KiB, ≤ 8 Binds, ≤ 50 Zeilen, 20 ms pro Statement, 64 Tabellen und Indizes. Kein ATTACH, PRAGMA, keine Transaktionen, Trigger, Views oder WITH. Jedes Skript des Servers kann jede Tabelle lesen und löschen.
- Aktive Skripte
2 pro Server kostenlos, 10 Premium. Überzählige, älteste aktive Skripte werden automatisch deaktiviert.
- Versionen
Unbegrenzt viele pro Skript; jede neue Version bleibt ausstehend, bis das Team sie freigibt.
- Erwähnungen
Nur der Autor des Events. @everyone / @here werden entfernt. Keine Dateien, keine Sticker.
- Isolation
3 Fehler (Timeout/Speicher/Policy/Runtime) → eigener isolierter Worker (max. 4), sonst Quarantäne + Entladen.
- Fehlerprotokoll
Die letzten 24 h sind im Dashboard sichtbar, Einträge werden nach 7 Tagen gelöscht.
- Verboten
Kein voice_state, keine mod_*-APIs (Bann/Kick/Timeout), kein serverübergreifender Zugriff, keine Datenbank/kein Dateisystem, keine Monetarisierung.
- AutoMod
Das native AutoMod läuft zuerst und gewinnt immer gegen Skripte.
Validierung
Bevor eine Version gespeichert wird, führt das Backend statische Prüfungen aus. Der Testlauf macht dasselbe, ohne zu speichern. Die Sandbox selbst ist die zweite Verteidigungslinie: Verbotene Globals existieren zur Laufzeit schlicht nicht.
- Größe / Kodierung
Nicht leer, ≤ 64 KiB, UTF-8 ohne NUL-Bytes.
- Bytecode
Alles, was mit der Lua-Bytecode-Signatur beginnt, wird abgelehnt.
- Verbotene APIs
Der statische Scan lehnt ab: load(, loadfile, dofile, require(, os.execute, io.open, debug., string.dump, loadstring, getfenv, setfenv, package., coroutine., ffi., jit.
- Laufzeit-Sandbox
Selbst wenn etwas den statischen Scan passiert, stellt die Sandbox diese Globals schlicht nicht bereit. Der Aufruf schlägt mit einem Policy-Fehler fehl.
Verfügbare Built-ins: die Lua-Standardbibliothek ohne Dateizugriff. string, table, math, tostring, tonumber, type, pairs, ipairs, os.time, os.clock, os.difftime und bot.
Fehler & Quarantäne
Jeder Handler-Fehler wird mit einer Art erfasst und auf der Skript-Detailseite angezeigt (letzte 24 h). Einträge werden nach 7 Tagen gelöscht.
- runtime
Ein Lua-Fehler in deinem Handler (nil-Zugriff, falsche Argumente, …). Die Meldung enthält den Lua-Fehlertext.
- timeout
Der Handler hat die 100 ms Laufzeit überschritten oder ein IPC-Aufruf lief in ein Timeout (1 s).
- memory
Das Skript hat sein Speicherlimit überschritten.
- policy
Das Skript hat etwas Unerlaubtes versucht: unbekanntes Event, zu viele Handler, nicht erlaubte API, zu große Payload.
Wiederholter Missbrauch eskaliert automatisch: 3 Fehler (Timeout, Speicher, Policy, Runtime) verschieben das Skript in einen eigenen isolierten Worker. Hält das Fehlverhalten an oder ist kein isolierter Platz frei (max. 4), wird das Skript unter Quarantäne gestellt: entladen und im Dashboard markiert. Ratenüberflutung (8 abgelehnte Discord-Sendungen oder 8 verworfene Events in 10 s) führt sofort zur Quarantäne. Du wirst im Skript-Log-Kanal oder per DM benachrichtigt.
Betriebsseite: Der Bot lässt sich über einen Kill-Switch ohne Neustart global pausieren, und ein scripts:updated-Signal lädt geänderte Versionen innerhalb von Sekunden neu. Beides sind Team-Aktionen.
Store
Im Store kannst du Skripte öffentlich teilen und Skripte anderer Server installieren. Veröffentlichen und Installieren erzeugen jeweils Kopien. Wer einen Eintrag installiert, erhält ein eigenständiges Skript auf seinem Server, das nicht mit dem Code des Autors verknüpft bleibt.
- Veröffentlichen
Erfordert ein aktives Skript mit freigegebener Version, eine Beschreibung (10–512 Zeichen) und 1–8 Tags ([a-z0-9-], 1–24 Zeichen). Vor der ersten Veröffentlichung musst du die Store-Nutzungsbedingungen einmal akzeptieren.
- Installieren
Beim Installieren wird der Eintrag als eigenes Skript in deinen Server kopiert (Entwurf, vorab freigegebene Version, eigener Name mit Suffix, falls vergeben). KV wird nicht kopiert. Der Installationszähler des Eintrags steigt.
- Zurückziehen
Entfernt den Eintrag aus dem Store. Bestehende Kopien bleiben erhalten und laufen weiter, erhalten aber keine Updates mehr.
- Kein Geld
Verkäufe, Paywalls, Spenden-/Krypto-Links und ähnliche Monetarisierung in Beschreibung oder Code werden beim Veröffentlichen abgelehnt und können zur Sperrung des Skripts führen.
- Meldungen
Jeder mit Serverzugriff kann einen Eintrag mit Begründung melden. Das Team sieht eine Warteschlange offener Meldungen und schließt sie nach der Prüfung.
- Limits
Die Store-Liste zeigt bis zu 50 Einträge pro Abfrage (Suche, Tag, Sortierung nach Installationen oder Neueste). Für Installationen gilt das normale Skript-Kontingent des Servers.
Beispiele
Wähle ein Beispiel, um den Code zu sehen.