Starauftrag Discord Bot (Go + SQLite)
Produktionsnaher Discord-Bot für Lieferaufträge mit SQLite-Persistenz, Slash Commands, internen Team-Buttons, Audit-Log und privater Lagerverwaltung.
Kernidee
- Endnutzer erstellen mit
/auftrageinen Lieferauftrag. - Der öffentliche Auftragspost enthält nur auftragsbezogene Basisdaten.
- Das Team erhält in einem privaten internen Channel denselben Auftrag plus Lagerprüfung.
- Annahme/Ablehnung erfolgt ausschließlich im internen Channel per Button.
- Lagerdaten werden nie an den Einreicher oder in öffentliche Posts ausgegeben.
- Alle relevanten Änderungen werden in
order_eventsauditiert.
Discord Buttons sind Message Components mit custom_id; beim Klick erhält der Bot eine Component Interaction und ordnet sie damit wieder dem Auftrag zu.
Setup
- Discord Developer Portal: Bot erstellen, Token kopieren, Bot in deinen Server einladen.
- Bot-Berechtigungen:
Send MessagesUse Slash CommandsRead Message HistoryEmbed LinksCreate Public ThreadsoderCreate Private Threadsje nach Channel-EinstellungSend Messages in Threads
.env.examplenach.envkopieren oder Umgebungsvariablen setzen.- Starten:
go mod tidy
go run .
Für schnelle Command-Updates in Entwicklung DISCORD_GUILD_ID setzen. Ohne Guild-ID werden Slash Commands global registriert, was länger dauern kann.
Wichtige Umgebungsvariablen
DISCORD_TOKEN=...
DISCORD_APP_ID=...
DISCORD_GUILD_ID=...
DISCORD_PUBLIC_ORDER_CHANNEL_ID=...
DISCORD_INTERNAL_ORDER_CHANNEL_ID=...
DISCORD_AUDIT_CHANNEL_ID=...
DISCORD_ADMIN_ROLE_IDS=roleId1,roleId2
DISCORD_TEAM_ROLE_IDS=roleId3,roleId4
DATABASE_PATH=orders.db
THREAD_DELETE_AFTER_HOURS=72
DISCORD_INTERNAL_ORDER_CHANNEL_ID sollte ein privater Team-Channel sein. Nur dort erscheinen Lagerprüfung und Annahme-/Ablehnen-Buttons.
User Commands
/auftrag
Erstellt einen Auftrag mit:
warequalitaet0-1000mengeals ZahleinheitSCU,cSCUoderStückfristim FormatYYYY-MM-DDlieferortbudgetin aUEC
/auftrag_status id:<id>
Zeigt den Auftrag. Team/Admin sieht die interne Ansicht inklusive Lagerprüfung, normale Nutzer nur eigene Aufträge ohne Lagerdaten.
/auftrag_liste status:<optional> limit:<optional>
Listet nur aktive Aufträge. Standardmäßig werden offen, angenommen, in Lieferung und geliefert angezeigt. Abgeschlossene, abgelehnte und abgebrochene Aufträge erscheinen hier nicht mehr.
/auftrag_archiv status:<optional> limit:<optional>
Listet archivierte Aufträge mit den deutschen Statuswerten abgeschlossen, abgelehnt und abgebrochen. Intern werden die stabilen Werte completed, declined und cancelled gespeichert.
/auftrag_abschliessen id:<id>
Einreicher, Annehmer oder Team/Admin können abschließen.
/auftrag_abbrechen id:<id> grund:<optional>
Einreicher oder Team/Admin können abbrechen.
/auftrag_nachricht id:<id> text:<text>
Leitet eine Nachricht per DM an Einreicher und Annehmer weiter und schreibt sie zusätzlich in den internen Auftragsthread.
Team/Admin Commands
/auftrag_status_setzen
Team/Admin kann den Status manuell setzen:
offenangenommenin Lieferunggeliefertabgeschlossenabgelehntabgebrochen
Lager Commands
Nur Admins können Lagerbestände verändern. Team/Admin kann Lager prüfen und anzeigen.
Lagerorte werden separat verwaltet und bei den ort-Feldern per Autocomplete vorgeschlagen. Dadurch entstehen keine Tippfehler wie Orison, orison und Orison als getrennte Lagerorte.
/lagerort_add
Team/Admin kann interne Lagerorte anlegen. Diese Orte sind danach bei /lager_add, /lager_remove und /lagerort_remove als Auswahl verfügbar.
/lagerort_add name:Baijini Point
/lagerort_add name:Orison
/lagerort_remove
Team/Admin kann Lagerorte löschen, aber nur wenn dort kein aktiver Bestand mehr liegt.
/lagerort_remove ort:Baijini Point
Wenn noch Bestand vorhanden ist, lehnt der Bot das Löschen ab.
/lager_add
Fügt Bestand hinzu oder setzt ihn absolut.
/lager_add ware:Gold qualitaet:900 menge:64 einheit:SCU ort:Orison modus:addieren
/lager_add ware:Gold qualitaet:900 menge:100 einheit:SCU ort:Orison modus:setzen
ort ist ein Pflichtfeld und muss vorher mit /lagerort_add angelegt worden sein. Discord schlägt vorhandene Lagerorte beim Tippen automatisch vor. Der Ort wird nur in Team-/Admin-Ausgaben, der internen Lagerprüfung und im Auditlog angezeigt. Öffentliche Auftragsposts und Endnutzer-DMs enthalten keine Lagerorte.
Das Auditlog zeigt Bestandsänderungen mit altem und neuem Wert, z. B.:
Lager geändert durch b1tk1ll3r: P4-AR Q725 Stück @ Baijini Point: 0 → 2
/lager_remove
Reduziert Bestand. Der Bot verhindert negative Bestände.
/lager_remove ware:Gold qualitaet:900 menge:10 einheit:SCU ort:Orison
Auch hier ist ort ein Pflichtfeld und wird aus den angelegten Lagerorten vorgeschlagen. Dadurch werden Bestände an verschiedenen Orten sauber getrennt geführt.
/lager_liste
Zeigt interne Lagerbestände ephemeral an.
/lager_liste suche:Gold limit:20
/lager_check
Prüft intern, ob eine Menge verfügbar ist.
/lager_check ware:Gold qualitaet:900 menge:64 einheit:SCU
/lager_check ware:P4-AR qualitaet:725 menge:2 einheit:cSCU
Statusmodell
offen -> angenommen -> in Lieferung -> geliefert -> abgeschlossen
\-> abgebrochen
offen -> abgelehnt
Intern speichert der Bot weiterhin stabile technische Statuswerte wie `open`, `accepted`, `completed` usw. In Discord werden sie deutsch angezeigt.
Datenbanktabellen
orders: Aufträge und Discord-Message-Referenzeninventory: interne Lagerpositionenstorage_locations: verwaltete Lagerorte für Autocomplete und Validierungorder_events: Audit-Log für Aufträge und Lageränderungen
SQLite wird mit WAL-Modus und busy_timeout betrieben, damit kleine produktive Deployments robuster laufen.
Produktionshinweise
- Setze
DISCORD_INTERNAL_ORDER_CHANNEL_IDauf einen strikt privaten Channel. - Gib Lager-Commands nur einer kleinen Admin-Rolle.
- Sichere
orders.dbregelmäßig. - Führe den Bot als systemd-Service oder Container aus.
- Aktiviere Monitoring für Logs und Neustarts.
- Teste DMs: Manche Nutzer blockieren DMs von Servermitgliedern; der Bot loggt solche Fehler, kann sie aber nicht erzwingen.
Lagerbestand bei 0
Wenn ein Lagerbestand durch /lager_remove oder /lager_add mit modus:setzen auf 0 fällt, wird der Lagerdatensatz automatisch gelöscht. Das Auditlog zeigt weiterhin die Änderung als alter Wert → neuer Wert, z. B. 2 → 0.
Zusätzlich verhindert die Lagerlogik negative Bestände. Wenn mehr entfernt wird als vorhanden ist, antwortet der Bot mit einem Fehler und ändert den Bestand nicht.
/lager_liste und die interne Lagerprüfung berücksichtigen nur Bestände größer als 0, sodass alte Nullbestände nicht mehr angezeigt werden.
Automatische Thread-Bereinigung
Interne Auftragsthreads werden nach einer konfigurierbaren Zeit automatisch gelöscht, sobald der Auftrag archiviert ist. Archiviert bedeutet:
abgeschlossenabgelehntabgebrochen
Die Zeit steuerst du über:
THREAD_DELETE_AFTER_HOURS=72
72 bedeutet: Der Thread wird frühestens 72 Stunden nach der letzten Statusänderung gelöscht. 0 deaktiviert die automatische Thread-Bereinigung.
Für das Löschen benötigt der Bot im internen Channel zusätzlich Manage Threads. Wenn diese Berechtigung fehlt, bleibt der Thread bestehen und der Fehler erscheint im Bot-Log.
Deutsche Statusbegriffe
Discord zeigt Statuswerte jetzt deutsch an:
| Anzeige | interner Wert |
|---|---|
| offen | open |
| angenommen | accepted |
| in Lieferung | in_delivery |
| geliefert | delivered |
| abgeschlossen | completed |
| abgelehnt | declined |
| abgebrochen | cancelled |
Die internen Werte bleiben absichtlich englisch, damit bestehende Datenbanken und Integrationen stabil bleiben.
Einheiten
Bei Aufträgen und Lagerbeständen sind jetzt diese Einheiten auswählbar:
SCUcSCUStück
Trading-Modul
Das Trading-Modul ersetzt eine Excel-Erfassung für monatliche Trading-/Hauling-Runs.
Neue Konfiguration
DISCORD_TRADING_ROLE_IDS=456789012345678901
DISCORD_TRADING_REPORT_CHANNEL_ID=TradingReportChannelIDOptional
TRADING_SHIPS=Caterpillar,C2 Hercules,Freelancer MAX,Hull A,Hull C,Taurus
TRADING_COMMODITIES=Gold,Beryl,Diamond,Laranite,Agricium,Quantanium,Processed Food,Medical Supplies
TRADING_SHARE_TRADE=10
TRADING_SHARE_HAULING=5
TRADING_SHARE_EVENT=0
TRADING_SHARE_INTERN=0
Die Prozentwerte können als 10 oder 0.10 angegeben werden. Beide Varianten bedeuten 10%.
Neue Commands
/trading_add
/trading_liste
/trading_delete
/trading_report
/trading_abrechnung
/trading_add darf von Mitgliedern mit DISCORD_TRADING_ROLE_IDS sowie Team/Admin genutzt werden. Schiff und Ware werden aus den Umgebungsvariablen per Autocomplete vorgeschlagen.
Gewinnberechnung:
Gewinn = (Verkaufswert - Einkaufswert - Kosten) × Wiederholungen
Orga-Anteil = max(0, Gewinn × Prozentwert des Zwecks)
Transportierte SCU = SCU × Wiederholungen
Kosten gesamt = Kosten × Wiederholungen
/trading_report monat:<1-12> jahr:<YYYY> darf nur von Admins genutzt werden und postet eine hübsche Embed-Auswertung in DISCORD_TRADING_REPORT_CHANNEL_ID.
/trading_delete id:<id> ist ein Admin-Kommando. Einträge werden soft-deleted, damit sie nicht mehr in Listen/Reports auftauchen, aber nachvollziehbar bleiben.
Trading-Abrechnung per DM
Admins können am Monatsende jedem Mitglied mit positivem Orga-Anteil automatisch eine private Abrechnung senden:
/trading_abrechnung monat:<1-12> jahr:<YYYY>
Der Bot fasst pro Nutzer zusammen:
- Anzahl Runs
- Gewinn
- transportierte SCU
- zu überweisender Orga-Anteil
- Empfänger aus
TRADING_PAYOUT_RECIPIENT
Neue Konfiguration:
TRADING_PAYOUT_RECIPIENT=Jan / Orga-Kasse
Mitglieder ohne Orga-Anteil werden übersprungen. Falls ein Mitglied keine DMs zulässt, meldet der Bot diese fehlgeschlagene Zustellung in der Command-Antwort und schreibt Details ins Log.
Internes Web-Dashboard
Optional kann ein kleines Web-Dashboard gestartet werden. Es zeigt:
- Trading-Monatswerte
- Abrechnung pro Nutzer
- aktive Aufträge
- archivierte Aufträge
- Lagerbestände
Aktivierung in .env:
DASHBOARD_ADDR=127.0.0.1:8080
DASHBOARD_TOKEN=ChangeMePlease
Aufruf:
http://127.0.0.1:8080/?token=ChangeMePlease
Für Docker sollte DASHBOARD_ADDR=0.0.0.0:8080 gesetzt werden. Das Dashboard ist als internes Admin-/Team-Werkzeug gedacht. Stelle es nicht ungeschützt öffentlich ins Internet. Nutze mindestens DASHBOARD_TOKEN; für produktiven Betrieb idealerweise zusätzlich Reverse Proxy mit HTTPS und Zugriffsbeschränkung.
Webinterface: Rich Presence & Session-Einladung
Im Dashboard gibt es jetzt den Bereich Rich Presence & Session-Einladung. Dort können Admins über das Webinterface konfigurieren:
- Bot-Status: online, idle, dnd, invisible
- Activity-Typ: spielt, schaut, hört, tritt an, custom
- Name, State, Details
- Start- und Endzeit
- Large-/Small-Image-Asset-Keys und Texte
- Discord-Zielchannel-ID
- Post-Channel-ID für einen Session-Invite
- Button-Label, Post-Titel und Post-Text
Wichtig: Discord erlaubt Bot-Activities offiziell nur eingeschränkt. Die Discord-Dokumentation sagt, dass Bot-User nur name, state, type und url setzen können. Deshalb speichert das Dashboard zwar Details, Assets und Timestamps und sendet sie mit, aber Discord-Clients können diese Felder ignorieren. Für den eigentlichen Einladungszweck erzeugt der Button Session-Invite posten zuverlässig einen Discord-Post mit Link-Button zum konfigurierten Channel.
Der Channel-Link wird aus DISCORD_GUILD_ID und der im Webinterface eingetragenen Zielchannel-ID erzeugt:
https://discord.com/channels/<GuildID>/<ChannelID>
Der Bot benötigt im Invite-Post-Channel:
View Channel
Send Messages
Embed Links