# 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 `/auftrag` einen 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_events` auditiert. 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 1. Discord Developer Portal: Bot erstellen, Token kopieren, Bot in deinen Server einladen. 2. Bot-Berechtigungen: - `Send Messages` - `Use Slash Commands` - `Read Message History` - `Embed Links` - `Create Public Threads` oder `Create Private Threads` je nach Channel-Einstellung - `Send Messages in Threads` 3. `.env.example` nach `.env` kopieren oder Umgebungsvariablen setzen. 4. Starten: ```bash 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 ```env 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: - `ware` - `qualitaet` 0-1000 - `menge` als Zahl - `einheit` `SCU`, `cSCU` oder `Stück` - `frist` im Format `YYYY-MM-DD` - `lieferort` - `budget` in aUEC ### `/auftrag_status id:` Zeigt den Auftrag. Team/Admin sieht die interne Ansicht inklusive Lagerprüfung, normale Nutzer nur eigene Aufträge ohne Lagerdaten. ### `/auftrag_liste status: limit:` 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: limit:` 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:` Einreicher, Annehmer oder Team/Admin können abschließen. ### `/auftrag_abbrechen id: grund:` Einreicher oder Team/Admin können abbrechen. ### `/auftrag_nachricht id: 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: - `offen` - `angenommen` - `in Lieferung` - `geliefert` - `abgeschlossen` - `abgelehnt` - `abgebrochen` ## 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. ```text /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. ```text /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. ```text /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.: ```text Lager geändert durch b1tk1ll3r: P4-AR Q725 Stück @ Baijini Point: 0 → 2 ``` ### `/lager_remove` Reduziert Bestand. Der Bot verhindert negative Bestände. ```text /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. ```text /lager_liste suche:Gold limit:20 ``` ### `/lager_check` Prüft intern, ob eine Menge verfügbar ist. ```text /lager_check ware:Gold qualitaet:900 menge:64 einheit:SCU /lager_check ware:P4-AR qualitaet:725 menge:2 einheit:cSCU ``` ## Statusmodell ```text 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-Referenzen - `inventory`: interne Lagerpositionen - `storage_locations`: verwaltete Lagerorte für Autocomplete und Validierung - `order_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_ID` auf einen strikt privaten Channel. - Gib Lager-Commands nur einer kleinen Admin-Rolle. - Sichere `orders.db` regelmäß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: - `abgeschlossen` - `abgelehnt` - `abgebrochen` Die Zeit steuerst du über: ```env 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: - `SCU` - `cSCU` - `Stück` ## Trading-Modul Das Trading-Modul ersetzt eine Excel-Erfassung für monatliche Trading-/Hauling-Runs. ### Neue Konfiguration ```env 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 ```text /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: ```text 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:` darf nur von Admins genutzt werden und postet eine hübsche Embed-Auswertung in `DISCORD_TRADING_REPORT_CHANNEL_ID`. `/trading_delete 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: ```text /trading_abrechnung monat:<1-12> jahr: ``` Der Bot fasst pro Nutzer zusammen: - Anzahl Runs - Gewinn - transportierte SCU - zu überweisender Orga-Anteil - Empfänger aus `TRADING_PAYOUT_RECIPIENT` Neue Konfiguration: ```env 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`: ```env DASHBOARD_ADDR=127.0.0.1:8080 DASHBOARD_TOKEN=ChangeMePlease ``` Aufruf: ```text 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: ```text https://discord.com/channels// ``` Der Bot benötigt im Invite-Post-Channel: ```text View Channel Send Messages Embed Links ```