Files
GSM/docs/discord-bot.md
Alexander Zielonka 20ba93b26f Add multi-guild Discord OAuth support
- Users can now login via Bacanaks OR Piccadilly Discord server
- Highest role from all servers is used (superadmin > moderator > user)
- Lazy initialization fixes env loading timing issue
- Updated documentation with implementation details and troubleshooting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 00:30:15 +01:00

339 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# Discord Bot
Der GSM Discord Bot bietet Live-Status-Updates für alle Gameserver direkt in Discord. Der Bot kann auf mehreren Discord-Servern gleichzeitig laufen.
## Features
- **Live-Status**: Automatisch aktualisiertes Embed mit Server-Status, Spielerzahlen und Metriken
- **Alerts**: Benachrichtigungen wenn Server online/offline gehen oder Spieler joinen/leaven
- **Multi-Guild**: Kann auf beliebig vielen Discord-Servern eingesetzt werden
- **Auto-Setup**: Erstellt automatisch alle nötigen Channels beim Beitreten
## Bot einladen
### Invite-Link
```
https://discord.com/oauth2/authorize?client_id=1458251194806833306&permissions=34359831568&integration_type=0&scope=bot+applications.commands
```
Der Link ist auch im GSM Dashboard unter den Server-Cards verfügbar.
### Benötigte Permissions
| Permission | Verwendung |
|------------|------------|
| View Channels | Channels sehen |
| Manage Channels | Channels erstellen |
| Send Messages | Nachrichten senden |
| Manage Messages | Eigene Nachrichten bearbeiten |
| Embed Links | Rich Embeds für Status |
| Read Message History | Alte Nachrichten lesen |
| Create Public Threads | Forum-Threads erstellen |
## Automatisch erstellte Channel-Struktur
Wenn der Bot einem Server beitritt, erstellt er automatisch folgende Struktur:
```
🎮 Gameserver (Kategorie)
├── │info - Informationen zum GSM System
├── 📊│status - Live-Status aller Gameserver (Auto-Update)
├── 📢│alerts - Server-Events und Spieler-Benachrichtigungen
├── 📰│updates - Ankündigungen zu neuen Gameservern
├── 💬│diskussion - Diskussions-Channel (User können schreiben)
└── 💡│requests - Forum für Gameserver-Vorschläge
```
### Channel-Permissions
| Channel | @everyone | Bot |
|---------|-----------|-----|
| Kategorie | Lesen | ViewChannel + Schreiben |
| info | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| status | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| alerts | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| updates | Lesen (kein Schreiben) | ViewChannel + Schreiben |
| diskussion | Lesen + Schreiben | ViewChannel + Schreiben |
| requests | Lesen + Threads erstellen | ViewChannel + Schreiben |
**Wichtig**: Der Bot braucht explizit `ViewChannel` Permission für jeden Channel, auch wenn @everyone den Channel sehen kann. Ohne `ViewChannel` kann der Bot nicht in den Channel schreiben ("Missing Access" Fehler).
## Datenbank
### guild_settings Tabelle
Speichert die Channel-IDs für jeden Discord-Server:
```sql
CREATE TABLE guild_settings (
guild_id TEXT PRIMARY KEY,
category_id TEXT,
info_channel_id TEXT,
status_channel_id TEXT,
status_message_id TEXT,
alerts_channel_id TEXT,
updates_channel_id TEXT,
discussion_channel_id TEXT,
requests_channel_id TEXT,
requests_info_thread_id TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
Die Datenbank liegt in `backend/db/users.sqlite`.
### DB-Funktionen
In `backend/db/init.js`:
```javascript
initGuildSettings() // Tabelle erstellen
getGuildSettings(guildId) // Settings für einen Server
getAllGuildSettings() // Alle Server-Settings
setGuildSettings(guildId, {}) // Settings speichern
deleteGuildSettings(guildId) // Settings löschen (bei Bot-Kick)
```
## Bot-Events
### guildCreate
Wird ausgelöst wenn der Bot einem neuen Server beitritt:
1. Erstellt Kategorie und alle Channels
2. Postet Info-Nachricht im `│info` Channel
3. Erstellt Info-Thread im `💡│requests` Forum
4. Speichert alle Channel-IDs in der Datenbank
5. Sendet erste Status-Nachricht
### guildDelete
Wird ausgelöst wenn der Bot von einem Server entfernt wird:
1. Löscht alle Settings aus der Datenbank
## Status-Updates
Der Bot aktualisiert die Status-Nachricht in allen registrierten Guilds alle 30 Sekunden:
```javascript
async function updateAllGuildStatus() {
const guilds = getAllGuildSettings();
for (const guild of guilds) {
await updateGuildStatus(guild);
}
}
```
Jeder Server bekommt ein eigenes Embed mit:
- Server-Name und Status (Online/Offline)
- Aktuelle Spielerzahl
- CPU und RAM Auslastung
- Verbindungsadresse
## Konfiguration
### Umgebungsvariablen (.env)
```env
DISCORD_CLIENT_ID=1458251194806833306
DISCORD_CLIENT_SECRET=xxx
DISCORD_BOT_TOKEN=xxx
# Multi-Server OAuth Login
# User muss nur in EINEM der Server Mitglied sein
# Rollen werden pro Server geprüft, höchste Berechtigung zählt
# Server 1: Bacanaks
DISCORD_GUILD_ID_1=729865854329815051
DISCORD_ADMIN_ROLE_ID_1=1024693717434650736
DISCORD_MOD_ROLE_ID_1=1024693170958766141
# Server 2: Piccadilly
DISCORD_GUILD_ID_2=730907665802330224
DISCORD_ADMIN_ROLE_ID_2=1458595551514988584
DISCORD_MOD_ROLE_ID_2=1458591909210488914
```
**Hinweis**: Die Guild-IDs werden nur für den Discord OAuth Login verwendet, nicht für den Bot selbst.
### OAuth Login-Logik
```
1. User loggt sich via Discord OAuth ein
2. Für jeden konfigurierten Server:
- Ist User Mitglied? → Rollen prüfen
- Admin-Rolle → superadmin
- Mod-Rolle → moderator
- Nur Mitglied → user
3. Höchste Berechtigung aus allen Servern wird verwendet
4. Nicht in mindestens einem Server → Login verweigert
```
| User ist in... | Bacanaks Rolle | Piccadilly Rolle | GSM Rolle |
|----------------|----------------|------------------|-----------|
| Nur Bacanaks | Admin | - | superadmin |
| Nur Piccadilly | - | Mod | moderator |
| Beide | Mitglied | Admin | superadmin |
| Keinem | - | - | ❌ Kein Login |
### Developer Portal Einstellungen
1. Gehe zu https://discord.com/developers/applications
2. Wähle die Bot-Application
3. **Bot** Tab:
- "Public Bot" aktivieren (damit andere einladen können)
- Privileged Gateway Intents:
- Server Members Intent: Optional
- Message Content Intent: Nicht benötigt
## Dateien
| Datei | Beschreibung |
|-------|--------------|
| `backend/services/discord.js` | OAuth-Logik, Multi-Guild Membership-Prüfung |
| `backend/services/discordBot.js` | Bot-Logik und Event-Handler |
| `backend/routes/auth.js` | Auth-Endpoints (Login, Callback, Refresh) |
| `backend/db/init.js` | Guild-Settings DB-Funktionen |
| `frontend/src/pages/Dashboard.jsx` | Invite-Button im Dashboard |
## Implementierungsdetails
### Multi-Guild OAuth
Die OAuth-Implementierung in `discord.js` verwendet **lazy initialization** für die Guild-Konfigurationen:
```javascript
let _guildConfigs = null;
function getGuildConfigs() {
if (_guildConfigs === null) {
_guildConfigs = [
{ name: 'Bacanaks', guildId: process.env.DISCORD_GUILD_ID_1, ... },
{ name: 'Piccadilly', guildId: process.env.DISCORD_GUILD_ID_2, ... }
].filter(config => config.guildId);
}
return _guildConfigs;
}
```
**Wichtig**: Die Konfiguration darf NICHT beim Modul-Import initialisiert werden, da zu diesem Zeitpunkt dotenv die `.env` noch nicht geladen hat. Die lazy initialization stellt sicher, dass die Umgebungsvariablen verfügbar sind.
### Funktionen
| Funktion | Beschreibung |
|----------|--------------|
| `getGuildMemberships(userId)` | Prüft alle konfigurierten Server, gibt Array von Memberships zurück |
| `getUserRoleFromMemberships(memberships)` | Bestimmt höchste Rolle aus allen Memberships |
| `getGuildMember(userId)` | Legacy-Funktion, gibt ersten Match zurück |
| `getUserRole(memberRoles)` | Legacy-Funktion für einzelne Rollen-Liste |
### Rollen-Priorität
```javascript
const ROLE_PRIORITY = { superadmin: 3, moderator: 2, user: 1 };
```
Bei mehreren Memberships wird immer die höchste Rolle verwendet.
### Voraussetzungen
- Der Bot muss auf **allen** konfigurierten Discord-Servern sein
- Der Bot braucht Zugriff auf die Guild Members API
## Troubleshooting
### Login schlägt fehl mit "nicht Mitglied"
1. **Bot auf allen Servern?** Der Bot muss auf Bacanaks UND Piccadilly eingeladen sein
2. **Env-Variablen prüfen**:
```bash
grep GUILD /opt/gameserver-monitor/backend/.env
```
3. **Mit --update-env neustarten**:
```bash
pm2 restart gameserver-backend --update-env
```
4. **Logs prüfen**:
```bash
pm2 logs gameserver-backend --lines 30 | grep -i discord
```
### Rolle wird nicht erkannt
1. **Rollen-IDs prüfen** - Discord Developer Mode aktivieren, Rechtsklick auf Rolle → ID kopieren
2. **User-Rollen abfragen**:
```bash
curl -s -H "Authorization: Bot BOT_TOKEN" \
"https://discord.com/api/v10/guilds/GUILD_ID/members/USER_ID" | jq '.roles'
```
3. **Konfigurierte IDs vergleichen** mit den tatsächlichen Rollen des Users
### Bot erstellt keine Channels
- Prüfen ob Bot "Manage Channels" Permission hat
- Prüfen ob Bot-Rolle hoch genug in der Rollen-Hierarchie ist
### Status-Nachricht wird nicht aktualisiert
```bash
pm2 logs gameserver-backend --lines 50
```
Suche nach `[DiscordBot]` Log-Einträgen.
### Bot aus Datenbank entfernen
```bash
cd /opt/gameserver-monitor/backend
node -e "
import Database from 'better-sqlite3';
const db = new Database('./db/users.sqlite');
db.prepare('DELETE FROM guild_settings WHERE guild_id = ?').run('GUILD_ID_HIER');
console.log('Deleted');
"
```
Alternativ mit sqlite3 (falls installiert):
```bash
sqlite3 /opt/gameserver-monitor/backend/db/users.sqlite
DELETE FROM guild_settings WHERE guild_id = 'xxx';
```
### Missing Access Fehler
Wenn der Bot "Missing Access" meldet obwohl er eingeladen wurde:
1. **ViewChannel Permission prüfen**: Der Bot braucht explizit `ViewChannel` für jeden Channel
2. Im Discord: Rechtsklick auf Channel → Bearbeiten → Berechtigungen → Bot auswählen → "Kanal ansehen" aktivieren
3. Logs prüfen: `pm2 logs gameserver-backend --lines 50 | grep -i "missing\|access"`
### Status-Nachricht wurde gelöscht
Wenn die Status-Nachricht manuell gelöscht wurde, erscheint "Unknown Message" in den Logs. Fix:
```bash
cd /opt/gameserver-monitor/backend
node -e "
import Database from 'better-sqlite3';
const db = new Database('./db/users.sqlite');
// Status Message ID auf NULL setzen, Bot erstellt neue beim nächsten Update
db.prepare('UPDATE guild_settings SET status_message_id = NULL WHERE guild_id = ?').run('GUILD_ID_HIER');
console.log('Reset status_message_id');
"
```
## Login vs. Bot
| Feature | Login (OAuth) | Bot |
|---------|--------------|-----|
| Erfordert Mitgliedschaft | Bacanaks oder Piccadilly | Nein |
| Server-Steuerung | Ja (je nach Rolle) | Nein |
| Status sehen | Ja | Ja |
| Alerts erhalten | Nein | Ja |
| Verfügbar für | Mitglieder beider Discord-Server | Alle mit Bot |
Der Login zur Webapp erfordert Mitgliedschaft in mindestens einem der konfigurierten Discord-Server (Bacanaks oder Piccadilly). Die höchste Rolle aus beiden Servern bestimmt die GSM-Berechtigung. Der Bot ist davon unabhängig und zeigt nur passive Status-Updates.