Commit graph

59 commits

Author SHA1 Message Date
Daniel
df937f3e40 fix(soundboard): auto-prepend https:// for URL import
URLs pasted without protocol (e.g. instagram.com/reel/...) now work.
normalizeUrl() adds https:// if missing. Input type changed from
"url" to "text" to prevent browser validation blocking paste.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:45:41 +01:00
Daniel
200f03c1f8 feat(soundboard): extend URL download to support YouTube & Instagram
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>
2026-03-06 23:38:09 +01:00
Daniel
06326de465 feat(radio): add 2-second pre-buffer to reduce audio lag
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>
2026-03-06 23:28:58 +01:00
Daniel
7786d02f86 fix: globe clickable after tab switch — deferred init with ResizeObserver
Globe.gl needs non-zero container dimensions for initialization and click
handling. With the tab persistence fix (display:none for hidden tabs), the
globe container starts at 0×0 when radio isn't the first tab. Added a
separate ResizeObserver that detects when the container becomes visible
and triggers globe initialization via containerVisible state dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:17:48 +01:00
Daniel
056024d753 fix: preserve Radio tab play state across tab switches
All tab components are now always mounted and hidden via CSS
(display: none/contents) instead of conditionally rendered.
This prevents React state from being destroyed on tab switch,
so nowPlaying, SSE handlers, and stop buttons keep working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:04:49 +01:00
Daniel
09396dafce feat(lolstats): add Riot API hybrid — show ALL game modes (URF, Brawl, etc.)
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>
2026-03-06 21:54:53 +01:00
Daniel
87279933c3 refactor(lolstats): switch from MCP to op.gg REST API for fresh data
- 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>
2026-03-06 21:41:48 +01:00
Daniel
f4c8cce2f9 feat(lolstats): add data renewal — auto-refresh stale op.gg data
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>
2026-03-06 21:22:32 +01:00
Daniel
40c596fbfa feat: add LoL Stats plugin — op.gg-powered player lookup
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>
2026-03-06 21:04:45 +01:00
Daniel
24b4dadb0f fix: move hub-state.json to /data/sounds/ for persistence
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>
2026-03-06 20:28:03 +01:00
Daniel
55f311c612 fix: state persistence diagnostics + fallback write
- 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>
2026-03-06 20:22:07 +01:00
Daniel
fcf4ba86ce chore: globe texture q90 statt q85
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:13:53 +01:00
Daniel
5793a8f358 Optimize: Resize NASA Blue Marble from 28MB to 1.1MB
Original was 21600x10800 (full NASA resolution) - way too large
for a WebGL texture. Resized to 4096x2048 (max useful for globe.gl)
at JPEG quality 85 with progressive loading.

28 MB → 1.1 MB = 96% smaller, dramatically faster initial load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:10:02 +01:00
Daniel
dd71d763cd Feat: Show member count in soundboard channel dropdown
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>
2026-03-06 20:02:00 +01:00
Daniel
9aefc3d470 Harden state persistence: atomic writes + backup fallback
- 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>
2026-03-06 19:58:23 +01:00
Daniel
262cc9f213 Fix: Radio sidebar close + topbar layout consistency
1. FAB button (favorites star) now hidden when any panel is open.
   Previously the FAB (z-index:20) covered the panel close button
   (z-index:15), making it impossible to close the station sidebar.

2. Added margin-left:auto to radio-topbar-right so theme dots
   always stay right-aligned. Previously they sat left when no
   station was playing (missing flex:1 now-playing spacer).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:51:04 +01:00
Daniel
b4f3c7db4d Fix: Stop globe rotation while station panel is open
Auto-rotation now stops completely when a station list or
favorites sidebar is visible. Resumes only when all panels
are closed. Interaction-based pause (drag/scroll) also
respects open panels - won't resume after 5s timeout if
a sidebar is still showing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:40:35 +01:00
Daniel
1b2fbe27ed Fix: Size-based dot radius for radio stations
Restore d.size-dependent point radius so larger cities (more
stations) show bigger dots like Radio Garden. Formula:
radius = clamp(0.12, 0.45, 0.06 + size * 0.005)

Zoom scaling also respects per-station size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:37:34 +01:00
Daniel
a923463f83 Fix: Crisp radio station dots with zoom-scaling
Replace sprite-based markers (objectsData + SpriteMaterial with
soft 64x64 gradient texture → blurry at zoom) with optimized
point meshes:

- pointResolution(24): smooth 24-sided circles (no hexagons)
- pointAltitude(0.001): nearly flat on surface (no cylinder effect)
- sqrt-based zoom scaling: dots shrink when zooming in, grow when
  zooming out → visually consistent at all zoom levels
- Removed three.js Sprite/SpriteMaterial/CanvasTexture imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:30:42 +01:00
Daniel
99421f4577 Fix radio marker object placement 2026-03-06 16:15:44 +01:00
Daniel
3d59eda3da Fix radio marker sprites and size scaling 2026-03-06 14:08:09 +01:00
Daniel
cf113f65ca Update README: NASA Blue Marble texture + sprite particles docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:06:40 +01:00
Daniel
d55aaf71b1 Render radio stations as sprite particles 2026-03-06 13:58:50 +01:00
Daniel
693f719abc Switch radio globe imagery to NASA Blue Marble 2026-03-06 12:10:19 +01:00
Daniel
54a53a98b7 Improve radio globe tile sharpness 2026-03-06 12:01:41 +01:00
Daniel
b8268b4999 Fix radio globe texture rendering 2026-03-06 11:55:26 +01:00
Daniel
fef207e9df Add: Vollstaendige README mit Architektur, Env-Vars, Docker, API-Docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:51:57 +01:00
Daniel
f18638ff57 Refactor: Radio topbar layout + fix soundboard connection modal
- Radio: Move controls from bottom bar to topbar above globe
  (guild/channel selectors, now playing, volume, connection, stop)
- Radio: Theme selector moved inline into topbar-right
- Radio: Globe now fills remaining space below topbar
- Soundboard: Fix connection modal not opening when voiceStats is null
  (modal now renders with '---' values when no voice data available)
- Both: Modal condition changed from `showConnModal && voiceStats` to
  just `showConnModal`, handling null voiceStats gracefully

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:36:01 +01:00
Daniel
b821ad9615 Fix: Reuse voice connection when switching radio stations
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>
2026-03-06 11:25:42 +01:00
Daniel
63afc55836 Fix: Tile proxy for black globe + radio voicestats modal
- 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>
2026-03-06 11:19:19 +01:00
Daniel
b9a9347356 Fix: Separate voice groups so radio + soundboard play in parallel
Each plugin now uses its own @discordjs/voice group:
- Radio: group='radio'
- Soundboard: group='soundboard'

This prevents joinVoiceChannel from one bot overwriting the
other bot's connection. Both bots can now play simultaneously
in the same voice channel. Removed claimVoice system (not needed
with separate bots).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:07:31 +01:00
Daniel
3cd9f6f169 Feat: Multi-bot support - separate Discord bot per plugin
Each plugin gets its own Discord client and token:
- DISCORD_TOKEN_JUKEBOX (fallback: DISCORD_TOKEN) → Soundboard
- DISCORD_TOKEN_RADIO → Radio

discord.ts: factory createClient() instead of singleton
plugin.ts: per-plugin context storage via registerPlugin(p, ctx)
index.ts: creates/logins/shutdowns multiple bots independently

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:56:22 +01:00
Daniel
fd0a95be8e Fix: Add @types/three for TypeScript build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:44:19 +01:00
Daniel
ed7e07a9ba Feat: Dynamic tile-based globe texture (like Radio Garden)
Replace static earth texture with tile-based rendering from CDN.
Tiles load progressively (zoom 1→2→3 base), then dynamically
load zoom 4-6 as user zooms in. Mercator→equirectangular
reprojection via strip-based canvas compositing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:43:49 +01:00
Daniel
1e4ccfb1f1 Fix: Voice claim system - radio stops when soundboard plays
Plugins now claim voice per guild via claimVoice(). When soundboard
plays a sound, radio's cleanup runs automatically (kills ffmpeg,
broadcasts SSE stop event). Fixes stale "now playing" UI on tab switch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:21:11 +01:00
Daniel
e0635b30ef Fix: Radio search results empty + texture quality
- Search API: read title/subtitle/url from _source.page (nested)
- Channel click: extract correct ID from URL (last segment)
- Replace earth texture with higher-res 4096x2048 original

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:00:44 +01:00
Daniel
a1a49f0ec3 fix: host NASA Earth texture locally to avoid CORS
NASA's image server blocks cross-origin requests. Downloaded the
3600x1800 Black Marble texture and serve it from web/public/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:50:29 +01:00
Daniel
a9f81d66fd fix: sharp globe rendering - HiDPI pixel ratio + NASA 4K texture
- Set renderer pixel ratio to window.devicePixelRatio for crisp
  rendering on Retina/HiDPI displays (was defaulting to 1)
- Upgraded Earth texture from low-res three-globe example (~2K)
  to NASA Black Marble 3600x1800 for sharp detail when zooming
- Added pointResolution(6) for cleaner point geometry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:46:18 +01:00
Daniel
8c2d75380d fix: smooth globe rotation - only rescale points on zoom change
pointRadius was recalculated on every controls change event (including
rotation frames), causing 50k+ points to re-render each frame.
Now only triggers when altitude changes >5%, keeping rotation smooth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:44:24 +01:00
Daniel
2c0cb7a67a fix: volume slider disappearing + theme overlapping favorites
- SSE handler now distinguishes per-guild events (single NowPlaying)
  from snapshots (Record<string, NowPlaying>) to prevent state corruption
- Theme selector moved left (right: 72px) to not overlap favorites button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:39:27 +01:00
Daniel
bac2e3b17f fix: scale radio globe points with zoom level
Points now shrink proportionally when zooming in, preventing
overlap in dense areas and making individual stations clickable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:35:48 +01:00
Daniel
90ef17932c feat: Radio themes + globe rotation pause on interaction
- 5 themes: Sunset (default), Midnight, Forest, Ocean, Cherry
- Theme selector dots in top-right corner, persisted to localStorage
- Globe accent colors (points, atmosphere) update with theme
- Globe pauses auto-rotation for 5s on any interaction (click, drag, scroll)
- All radio CSS vars scoped to .radio-container[data-theme]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 02:15:06 +01:00
Daniel
d1ae2db00b feat: Radio volume control - server-wide slider synced via SSE
- Server: inlineVolume on AudioResource, POST /api/radio/volume endpoint
- Volume persisted per guild, broadcast via SSE to all clients
- Frontend: volume slider in bottom bar with debounced API calls
- Volume icon changes based on level (muted/low/normal)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 02:10:02 +01:00
Daniel
971fd2c3fc fix: add missing @snazzah/davey (DAVE) and ws dependencies
Jukebox has @snazzah/davey (Discord Audio Video Encryption) and ws
(WebSocket) as dependencies which were missing from the Gaming Hub.
Without davey, voice connections get stuck at 'signalling' because
Discord's voice servers require DAVE negotiation in @discordjs/voice 0.19.
Also removed unused prism-media (covered by @discordjs/opus).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:57:56 +01:00
Daniel
20ea13d71f debug: add connection stateChange + error listeners from creation
Listen for state transitions and errors immediately after joinVoiceChannel,
not just after ensureConnectionReady succeeds. This will show if the
connection transitions at all internally or stays stuck at signalling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:49:17 +01:00
Daniel
a13765d5b6 fix: correct voice adapter debug wrapper method direction
Previous wrapper intercepted wrong methods (library→adapter vs adapter→library).
Now correctly wraps:
- sendPayload (adapter→gateway): logs op code and return value
- onVoiceServerUpdate/onVoiceStateUpdate (gateway→library): logs incoming events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:41:34 +01:00
Daniel
cc5e21fe1c debug: add voice adapter wrapper to trace gateway communication
Logs sendPayload calls (op code, result), onVoiceServerUpdate
and onVoiceStateUpdate to identify why connection stays at signalling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:27:50 +01:00
Daniel
5faf5139ef feat: add comprehensive logging to soundboard voice/play pipeline
- Log voice state transitions (Signalling → Connecting → Ready etc.)
- Log play requests with sound name, guild, channel, file path
- Log connection creation, rejoin attempts, and failures
- Log AudioPlayer state changes and errors
- All prefixed with [Soundboard] for easy filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:22:28 +01:00
Daniel
c41138f62a fix: stop globe auto-rotation on point click
Rotation continued while browsing stations, making selection impossible.
Now stops immediately when clicking any radio point.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:18:35 +01:00
Daniel
9ac1034e5e fix: initialize voice encryption libs before first connection
- await sodium.ready + nacl preload (same as original jukebox)
- Add generateDependencyReport() for debugging
- Add type declarations for libsodium-wrappers and tweetnacl

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:13:46 +01:00