Files
sftrading/README.md
jbergner 25b42c8335
All checks were successful
release-tag / release-image (push) Successful in 2m8s
Update mit Presence-Einstellungen
2026-05-31 08:34:07 +02:00

373 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```