- Add Steam OpenID 2.0 authentication routes (login + callback)
- Enable Steam button in LoginModal (was placeholder)
- Unified user ID system: getUserId() supports Discord, Steam, Admin
- Update soundboard user-sound endpoints for Steam users
- UserSettings now works for both Discord and Steam providers
- Steam hover uses brand color #66c0f4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: writeState() → writeStateDebounced() im Volume-Endpoint
(kein synchroner Disk-Write bei jedem Slider-Tick mehr)
- Frontend: Debounce von 120ms auf 50ms reduziert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bei PCM-Memory-Cache wurde inlineVolume nur aktiviert wenn vol != 1,
dadurch fehlte das Volume-Objekt auf der AudioResource und
apiSetVolumeLive konnte die Lautstärke nicht live ändern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Das Multi-Platform Profile System hatte den Common-Games-Endpoint
kaputt gemacht: Client sendete profileIds (UUIDs), Server erwartete
steamIds. Endpoint loest jetzt profileIds korrekt auf.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Neue op.gg Champion API (lol-api-champion.op.gg) fuer Tier-Daten.
30-Min In-Memory Cache pro Mode+Region. Neuer Bereich unterhalb
der Match History mit Mode-Tabs, Champion-Grid, Tier-Badges und
Filter. Arena-Mode zeigt Avg Placement statt KDA.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README komplett ueberarbeitet mit allen 7 Plugins, Electron Desktop App,
Streaming Qualitaets-Presets, aktualisierte Env-Variablen und CI/CD Doku.
Version auf 1.8.0 in VERSION, server, web und electron package.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- remoteStreamRef speichert MediaStream aus ontrack, damit er nicht
verloren geht wenn das <video>-Element noch nicht gemountet ist
- useEffect verbindet Stream mit Video sobald beides bereit ist
- Explizites play() mit Mute-Fallback bei Autoplay-Blockierung
(neuer Tab ohne User-Interaktion, z.B. Discord-Link)
- Debug-Logging aus Notification-Config entfernt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Neues Notification-Plugin mit eigenem Discord-Bot
- Admin-Modal im Streaming-Tab für Channel-Konfiguration
- Automatische Benachrichtigungen bei Stream-Start/Ende
- Stream-Links mit Passwort-Hinweis in Discord-Embeds
- Konfigurierbare Events pro Channel (stream_start, stream_end)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- MP3/WAV per Drag & Drop ablegen öffnet jetzt ein Rename-Modal
- Jede Datei einzeln benennen vor dem Upload (sequentiell bei mehreren)
- Server-Upload-Endpoint unterstützt optionalen customName Parameter
- Reuse der bestehenden dl-modal CSS-Klassen für konsistentes Design
- Überspringen-Option bei mehreren Dateien
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Das bisherige client_secret war fehlerhaft (falsche Endbytes).
Korrigiert anhand der offiziellen GOG API-Dokumentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Electron: Fängt GOG Redirect automatisch ab (will-redirect Interceptor)
- Code-Exchange passiert im Hintergrund, User sieht nur Erfolgs-Popup
- GOG Auth-URLs (auth.gog.com, login.gog.com, embed.gog.com) in Popup erlaubt
- Server: GET /gog/login Redirect + POST /gog/exchange Endpoint
- Browser-Fallback: Code-Paste Dialog falls nicht in Electron
- gog.ts: Feste redirect_uri (embed.gog.com/on_login_success)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server enriched bestehende User beim Plugin-Start automatisch (fire-and-forget)
- Frontend triggert IGDB-Enrichment automatisch beim Öffnen einer User-Bibliothek
- Reduzierte Log-Ausgabe (kein Spam pro Cache-Hit mehr)
- IGDB-Button zeigt Lade-Animation während Enrichment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Electron: setDisplayMediaRequestHandler zeigt jetzt immer einen modalen Picker
mit Thumbnails statt automatisch die letzte Quelle zu verwenden
- Passwort bei Streams ist jetzt optional (Server + Frontend)
- Streams ohne Passwort: direkter Beitritt ohne Modal
- hasPassword wird korrekt im stream_available Event übertragen
- Windows Toast-Notifications via Electron Notification API für
"Stream gestartet" und "Neuer Stream" Events
- Browser-Variante nutzt weiterhin Web Notification API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: report_time ignoriert Host-Updates für 2s nach einem Seek
- Frontend: seekingRef Guard auf 3s erhöht um Sync-Pulse-Rücksetzung zu verhindern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Host-Only-Beschränkung für play/pause/resume/seek/skip entfernt — alle Raum-Mitglieder können jetzt die Wiedergabe steuern
- Dailymotion-Videos können jetzt abgespielt werden (postMessage API, iframe-basiert)
- YouTube-Videoqualität einstellbar (Standard: 1080p, wird in localStorage gespeichert)
- Queue-Items sind für alle User klickbar
- "Gesehene entfernen"-Button für alle sichtbar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Neues Plugin: game-library mit Steam OpenID 2.0 Login
- Steam GetOwnedGames API zum Abrufen der Spielebibliothek
- Gemeinsame Spiele finden (Schnittmenge mehrerer Bibliotheken)
- Spielesuche ueber alle verbundenen User
- User-Profil mit Spielzeit-Sortierung
- JSON-basierte Persistenz in /data/game-library.json
- Steam API Key als CI/CD Variable konfiguriert
- Frontend: User Cards, Common Games Finder, Suchfunktion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Raumliste zeigt jetzt Teilnehmernamen
- Join per Link (?wt=roomId) für einfaches Teilen
- Sync-Indikator (grün/gelb/rot) zeigt Synchronstatus
- Pause/Skip-Voting für Nicht-Host-Teilnehmer
- In-Room Chat mit Nachrichtenverlauf
- "Gesehene entfernen" Button für Host in der Queue
- REST endpoint GET /api/watch-together/room/:id
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Statt Videos nach dem Abspielen aus der Warteschlange zu entfernen,
bleiben sie drin und werden mit einem gruenen Haken markiert.
Separate History-Section entfernt — die Queue IST die History.
Videos bleiben klickbar zum erneuten Abspielen.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server:
- Video-History Tracking (max 50 Einträge pro Raum)
- History wird bei Skip und Play-Video gespeichert
- Server-seitiger Titel-Fetch via noembed.com als Fallback
Client:
- Aufklappbare History-Sektion im Queue-Panel
- "Weiter" Button mit Text-Label statt nur Icon
- YouTube-Thumbnails in der Warteschlange
- History in RoomState integriert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Downloads-Pfad von relativem App-Pfad auf DATA_DIR/downloads geändert
- Electron package.json: author Feld ergänzt (Squirrel-Requirement)
- forge.config.js: fehlende Icon-Referenzen entfernt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Version auf 1.5.0 in allen Packages, CI, Dockerfile, Electron
- Download-Button im gleichen Design wie Version-Badge (bg-secondary, radius, hover accent)
- CI Registry auf adriahub (192.168.1.100:9080) umgestellt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root Cause: ws-Library killt WS-Verbindungen mit HTTP 400 wenn
mehrere WebSocketServer mit { server, path } registriert werden.
Der erste WSS (streaming) hat abortHandshake() fuer watch-together
Verbindungen aufgerufen.
- Beide WSS auf noServer-Modus umgestellt
- Zentrales upgrade-Routing in index.ts nach Pathname
- Frontend: connectWs prueft jetzt auch CONNECTING-State
- Frontend: onclose clobbert wsRef nur noch wenn gleicher Socket
- Frontend: waitForWs hat jetzt 10s Timeout statt Endlosschleife
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Neuer Tab: Watch Together - gemeinsam Videos schauen (w2g.tv-Style)
- Raum-System mit optionalem Passwort und Host-Kontrolle
- Video-Queue mit Hinzufuegen/Entfernen/Umordnen
- YouTube (IFrame API) + direkte Video-URLs (.mp4, .webm)
- Synchronisierte Wiedergabe via WebSocket (/ws/watch-together)
- Server-autoritative Playback-State mit Drift-Korrektur (2.5s Sync-Pulse)
- Host-Transfer bei Disconnect, Room-Cleanup nach 30s
Electron Desktop App (electron/):
- Wrapper fuer Gaming Hub mit integriertem Ad-Blocker
- uBlock-Style Request-Filtering via session.webRequest
- 100+ Ad-Domains + YouTube-spezifische Filter
- Download-Button im Web-Header (nur sichtbar wenn nicht in Electron)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server:
- Dual-Role: Client kann gleichzeitig broadcasten UND zuschauen
(broadcastStreamId + viewingStreamId statt single role)
- POST /api/streaming/disconnect Beacon-Endpoint fuer
zuverlaessigen Cleanup bei Page-Unload
- Heartbeat auf 5s reduziert (schnellere Erkennung)
Frontend:
- pagehide + sendBeacon: Streams werden sofort aufgeraeumt wenn
Browser geschlossen/neugeladen wird
- ICE Routing: Broadcaster-Map wird zuerst geprueft, dann Viewer-PC
→ Broadcast + View im selben Tab moeglich
- 3-Punkt-Menü mit Stream-Details, "In neuem Fenster oeffnen" und
"Link teilen" (Clipboard)
- Auto-Join via ?viewStream=... Query-Parameter (fuer geteilte Links)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Disconnect:
- Server-side heartbeat ping/pong every 10s with 25s timeout
- Detects and cleans up dead connections (browser closed, network lost)
- ws.terminate() on heartbeat timeout triggers handleDisconnect
Password:
- Stream password is mandatory (server rejects start_broadcast without)
- Password stored server-side, never sent to clients
- Viewers must enter password via modal before joining
- Lock icon on tiles, WRONG_PASSWORD error shown in modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New plugin: browser-based screen sharing via Chrome Screen Capture API.
Multi-stream grid layout (Rustdesk-style tiles) with live previews.
- Server: WebSocket signaling at /ws/streaming (SDP/ICE relay)
- Server: http.createServer for WebSocket attachment
- Frontend: StreamingTab with broadcaster/viewer modes
- Frontend: tile grid, fullscreen viewer, LIVE badges
- Supports multiple concurrent streams
- Peer-to-peer video via WebRTC (no video through server)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every step now logged with timestamps, exit codes, stdout/stderr
streaming in real-time. yt-dlp runs with --verbose flag. Routes log
URL type detection, download progress, normalization, play status.
Error messages include HTTP status codes and specific yt-dlp errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yt-dlp extracts audio as MP3 from YouTube and Instagram links.
Direct MP3 links continue to work as before. URL input field now shows
a type indicator (YT/IG/MP3) and validates all three formats.
Backend: downloadWithYtDlp() spawns yt-dlp with --extract-audio,
saves to SOUNDS_DIR, normalizes if enabled. New /download-url route
for save-only without auto-play. play-url route extended for all types.
Frontend: isSupportedUrl() validates YouTube/Instagram/MP3, dynamic
icon changes per URL type, disabled state when URL is unsupported.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Buffer 384 KB of PCM data (~2 seconds at 48kHz stereo) via PassThrough
stream before starting Discord playback. Falls back to immediate start
after 3s timeout for slow streams. Cleanup integrated into stopAudio.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
op.gg REST API doesn't track featured game modes (URF, ARAM Mayhem/Brawl).
Now uses Riot API for match history when RIOT_API_KEY env var is set,
falling back to op.gg REST for profile/ranked stats (no key needed).
- Add Riot API match fetcher with region routing (europe/americas/asia/sea)
- Add DDragon champion ID→name mapping for Riot API matches
- Add queue ID→name mapping (420=Ranked, 450=ARAM, 900=URF, etc.)
- Transform Riot match data to existing MatchEntry interface
- Batch match detail requests (5 at a time) for rate limit safety
- Keep op.gg REST as fallback when no API key is configured
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Profile now fetched via REST API (summoner lookup + summary endpoint)
- Match history via REST API games endpoint (proper JSON, no parser)
- All 10 players per game returned directly (no separate detail fetch)
- DDragon champion ID→name mapping loaded at startup
- Fixed summoner_id lookup to use # separator (was using - which failed)
- MCP kept as fallback for match detail and edge cases
- Frontend: find "me" by summoner name instead of assuming index 0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses op.gg REST API to trigger summoner data renewal before fetching
stats via MCP. Adds Update button in profile header for manual refresh.
Flow: lookup summoner_id → POST renewal → poll until finish → re-fetch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New plugin for League of Legends stats tracking, similar to op.gg:
- Search summoners by Riot ID (Name#Tag) + region
- Profile overview: rank, tier, LP, win rate, ladder position
- Top champions with KDA and win rates
- Match history with KDA, CS, items, game duration
- Expandable match details showing all 10 players
- Recent searches persisted across restarts
Uses op.gg MCP server (no API key needed, no 24h expiration).
Backend: server/src/plugins/lolstats/ (3 files)
Frontend: web/src/plugins/lolstats/ (2 files)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: only /data/sounds/ survives container recreation (it's the
volume-mounted directory). /data/hub-state.json was written to the
container's ephemeral layer and lost on every redeploy.
- State file now saved to /data/sounds/hub-state.json
- Auto-migrates from legacy /data/hub-state.json if found
- Favorites and radio volumes will now persist across deploys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- loadState: logs DATA_DIR path, writability check, lists files, shows
radio_favorites count on load
- saveState: read-back verification after atomic write, fallback to
direct write if rename fails
- /api/health: shows state diagnostics (file exists, file size, keys,
favorites count in memory vs disk, lastSaveOk)
- Helps diagnose why favorites are not persisting across deploys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add voice channel member count (non-bot users) to soundboard
channel API response and display it in the dropdown, matching
the radio plugin's existing behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Atomic save: write to .tmp file then rename (prevents corruption
if container is killed mid-write)
- Backup: .bak copy created on successful load, used as fallback
if main file is corrupted
- Startup log shows loaded keys (verifies favorites survived)
Ensures radio_favorites and radio_volumes survive container
updates, crashes, and forced restarts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of destroying and recreating the voice connection on every
station change, now checks if the bot is already in the target channel.
If same channel: only stops ffmpeg/player, spawns new stream, reuses
the existing connection (no reconnect flicker).
If different channel: full disconnect + reconnect as before.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Globe was black because Radio Garden CDN (rg-tiles.b-cdn.net) returns
403 without Referer: radio.garden header. Added server-side tile proxy
/api/radio/tile/:z/:x/:y with in-memory cache (max 500 tiles).
- Added radio_voicestats SSE broadcast (every 5s) with voice ping,
gateway ping, status, channel name, and connected-since timestamp.
- Added clickable "Verbunden" connection indicator in RadioTab bottom
bar with live ping display and connection details modal (matching
soundboard's existing modal pattern).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>