All checks were successful
release-tag / release-image (push) Successful in 2m8s
373 lines
12 KiB
Markdown
373 lines
12 KiB
Markdown
# 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:<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:
|
||
|
||
- `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:<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:
|
||
|
||
```text
|
||
/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:
|
||
|
||
```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/<GuildID>/<ChannelID>
|
||
```
|
||
|
||
Der Bot benötigt im Invite-Post-Channel:
|
||
|
||
```text
|
||
View Channel
|
||
Send Messages
|
||
Embed Links
|
||
```
|