DMN/Software

ProjekteDiscord-Bot

Discord-Bot

Ein Bot in TypeScript, der Discord-Server aus JSON-Vorlagen aufbaut, moderiert und Tickets bis zum Transkript führt. Vorlagen aus einer Datei und Entwürfe per KI laufen durch dieselbe Schemaprüfung mit zod.

Läuft auf dem eigenen Server, nicht öffentlich erreichbar

Nachricht des Bots mit der Überschrift Hier wird nicht geschrieben: Wer in diesem Kanal schreibt, wird sieben Tage stummgeschaltet und die Nachricht gelöscht. Übernommene Konten fallen so auf, bevor sie den Rest des Servers erreichen. Team und Support sind ausgenommen
Die Nachricht im Honigtopf-Kanal. Wer dort schreibt, wird ohne Verwarnung stummgeschaltet.

Eckdaten

Stand

Umfang
rund 7.000 Zeilen in 60 TypeScript-Dateien, 13 Dateien Doku
Tests
43 mit node --test
Datenbank
10 Migrationen
Betrieb
Docker-Container ohne offenen Port

Ein Server aus einer Datei

Rollen, Kategorien, Kanäle und ihre Rechte stehen in einer JSON-Vorlage. /serveraufbau wendet sie in einem von drei Modi an: ergänzen legt nur an, was fehlt, ersetzen gleicht auch Vorhandenes an die Vorlage an, leeren räumt den Server ab und baut ihn neu auf.

Vor jedem Lauf zeigt der Bot die geplanten Schritte, was gelöscht würde, steht dort mit Namen. Erst ein Knopfdruck startet den Lauf.

Jeder Lauf schreibt ein Journal. Darüber nimmt der Bot Angelegtes wieder weg und setzt geänderte Rechte zurück. Gelöschtes bringt er nicht zurück, ein Lauf mit leeren lässt sich gar nicht zurücknehmen.

Der Bot läuft als Docker-Container ohne offenen Port und unabhängig vom Spielserver, gebaut für den Discord-Server zu den FiveM-Ressourcen.

  1. Vorlage als Datei oder als Entwurf aus /serveraufbau erzeugen
  2. Prüfung mit zod, für Datei und Entwurf dieselbe
  3. Vorschau mit allen Schritten, Löschungen mit Namen
  4. Lauf als ergänzen, ersetzen oder leeren
  5. Journal nimmt Angelegtes und geänderte Rechte zurück, nicht nach leeren

Entwurf aus einer Beschreibung

/serveraufbau erzeugen macht aus einer Beschreibung in Freitext eine Vorlage, über Anthropic oder Gemini. Die Antwort des Modells läuft durch dieselbe Schemaprüfung wie eine Datei, einen eigenen Prüfweg für das Modell gibt es nicht.

Hält der Entwurf nicht, meldet der Bot die ersten zehn Befunde mit ihrem Pfad in der Vorlage. Antwortet Gemini mit 404, 429 oder 503, versucht es der Bot mit einem Ersatzmodell.

Das Modell entwirft nur Vorlagen. Moderation und Tickets laufen ohne KI.

Tickets

Ein Panel mit Knöpfen öffnet Tickets der Arten Support, Kauf, Beschwerde und Tebex. Jedes Ticket bekommt einen eigenen Kanal, den nur der Ersteller, das Team und wen das Team dazuholt sehen.

Beim Schließen legt der Bot ein Transkript im Archiv der jeweiligen Art ab. Danach lässt sich das Ticket mit einem bis fünf Sternen und einem Kommentar bewerten.

/ticket statistik zeigt je Teammitglied die bearbeiteten Tickets, die mittlere Bewertung und die mittlere Dauer.

Die Karte Ticket öffnen, die die Arten Support, Kauf, Beschwerde und Tebex erklärt, darunter die vier Knöpfe
Das Ticket-Panel im Kanal ticket-erstellen.

Moderation

Der Werbefilter findet Einladungen auch in Schreibweisen wie „disc0rd . gg / abc“ oder mit unsichtbaren Zeichen zwischen den Buchstaben. Spam wird verwarnt, ein Raid-Verdacht landet im Mod-Log. Dazu kommt ein Wortfilter, dessen Muster der Bot vor dem Speichern gegen ReDoS prüft.

Mit der dritten Verwarnung folgt eine Stunde Stummschaltung, mit der fünften zwölf Stunden, mit der siebten ein Kick und mit der neunten ein Bann. Jede Maßnahme, automatisch oder von Hand, steht im Mod-Log.

Ausgenommen ist der Honigtopf-Kanal aus dem ersten Bild, dort schaltet der Bot sofort für sieben Tage stumm.

Im Code

Das Schema der Vorlage ist ein strictObject, unbekannte Felder fallen durch. Die Prüfung darüber meldet zwei Namen, die nach dem Normalisieren gleich sind, weil der Bot über den Namen zuordnet und nicht über den Schlüssel.

Die Stufen der Moderation vergleichen auf Gleichheit statt auf „ab“. Jede Verwarnung erhöht den Zähler um eins, so greift eine Stufe beim Erreichen und nicht bei jeder weiteren Verwarnung erneut.

Die Datenbank migriert über user_version, jede Migration in einer eigenen Transaktion. Ist die Datenbank neuer als der Build, bricht der Bot beim Start ab, statt sie mit altem Code zu beschreiben.

Auszüge aus dem echten Code, gekürzt.

Echter Code aus dem Discord-Bot

src/templates/schema.ts

export const VorlageBasis = z.strictObject({    version: z.literal(1),    schluessel,    name: z.string().min(1).max(100),    })export const Vorlage = VorlageBasis.superRefine((vorlage, ctx) => {            const gleich = normalisiere(name)        const erster = namen.get(gleich)                ctx.addIssue({            code: 'custom',            path: [...pfad, 'name'],    

src/commands/serveraufbau.ts

const geprueft = Vorlage.safeParse(roh)if (!geprueft.success) {    const zeilen = geprueft.error.issues        .slice(0, 10)        .map(i => `\`${i.path.length > 0 ? i.path.join('.') : '(Wurzel)'}\` — ${i.message}`)    await interaction.editReply({        embeds: [Embed.fehler(            'Die erzeugte Vorlage ist nicht gültig',                    )],    })    return}

src/moderation/escalation.ts

const stufen: Stufe[] = [    { ab: 3, aktion: 'stumm', dauerMs: 3_600_000 },    { ab: 5, aktion: 'stumm', dauerMs: 43_200_000 },    { ab: 7, aktion: 'kick' },    { ab: 9, aktion: 'bann' },]export function stufeFuer(anzahl: number): Stufe | null {    return stufen.find(s => s.ab === anzahl) ?? null}

src/core/db.ts

function migriere(verbindung: DatabaseSync): void {    const zeile = verbindung.prepare('PRAGMA user_version').get() as {  }    const stand = zeile.user_version    if (stand > migrationen.length) {        throw new Error(`database is at schema version ${stand}, `)    }    for (let i = stand; i < migrationen.length; i++) {        verbindung.exec('BEGIN')        try {            verbindung.exec(migrationen[i]!)            verbindung.exec(`PRAGMA user_version = ${i + 1}`)            verbindung.exec('COMMIT')        } catch (fehler) {            verbindung.exec('ROLLBACK')    

Weitere Aufgaben

Die Nachricht des Bots mit sechs Regeln, darunter der Abschnitt Regeln annehmen und der grüne Knopf Regeln akzeptieren
Der Kanal regeln. Der Knopf unter der Nachricht vergibt die Rolle, mit der der Rest des Servers sichtbar wird.

Technik

Laufzeit
Node 24, TypeScript ohne Build-Schritt, Node führt die .ts-Dateien direkt aus
Discord
discord.js 14
Prüfung
zod 4
Daten
node:sqlite, Volumes für die Datenbank und die erzeugten Vorlagen, Sicherung auf Zuruf
KI
@anthropic-ai/sdk und @google/genai, nur für Entwürfe von Vorlagen
CI
GitHub Actions mit Typecheck und Tests, Dependabot wöchentlich
Betrieb
Docker Compose auf dem eigenen Server, Dateisystem schreibgeschützt, alle Capabilities entzogen, no-new-privileges, Image per Digest gepinnt, läuft als Nutzer node
Netz
kein offener Port, alle Verbindungen gehen nach außen: zu Discord, zur Headless-API von Tebex und für Entwürfe zu Anthropic oder Gemini