Commit Graph

509 Commits

Author SHA1 Message Date
f7a88791e8 Fix GetImage endpoint to proxy images instead of redirecting 2026-02-03 19:02:42 -05:00
9f8b3d65fb Fix HttpClient.SendAsync call for image proxy 2026-02-03 18:59:58 -05:00
1a1f9e136f Fix image proxy to handle binary data instead of JSON parsing 2026-02-03 18:59:03 -05:00
48f69b766d Add manual track mapping feature
- Add 'Map to Local' button for external tracks in playlist viewer
- Search Jellyfin library to find local tracks
- Save manual mappings (Spotify ID → Jellyfin ID) in cache
- Manual mappings take priority over fuzzy matching
- Clear playlist cache when mapping is saved to force refresh
- UI shows which tracks are manually mapped in logs
2026-02-03 18:57:19 -05:00
d619881b8e Fix: Correct matching logic - Jellyfin tracks first, then fill gaps
CRITICAL FIX: Changed matching strategy completely
- Step 1: Match ALL Jellyfin tracks to Spotify positions (fuzzy 70%)
- Step 2: Build playlist in Spotify order using matched Jellyfin tracks
- Step 3: Fill remaining gaps with external tracks (cached or on-demand)
- Step 4: Add any unmatched Jellyfin tracks at the end

This ensures Jellyfin tracks are ALWAYS used when they match, preventing
external tracks from being used when local versions exist.
2026-02-03 18:45:07 -05:00
dccdb7b744 Fix: Add on-demand external track search when cache is empty
- Search external providers in real-time if no cached match exists
- Use fuzzy matching (60% threshold) for external tracks
- Ensures external tracks are always available even without pre-matching
- Local tracks still prioritized first (70% threshold)
2026-02-03 18:39:33 -05:00
f240423822 Fix: Prioritize LOCAL tracks in Spotify playlist injection - match by name only
- Remove Spotify ID/ISRC matching (Jellyfin plugin doesn't add these)
- Use ONLY fuzzy name matching (title + artist, 70% threshold)
- LOCAL tracks ALWAYS used first before external providers
- Include ALL tracks from Jellyfin playlist (even if not in Spotify)
- Prevent duplicate track usage with HashSet tracking
- AdminController also updated to match by name for Local/External badges
- Better logging with emojis for debugging
2026-02-03 18:36:33 -05:00
1492778b14 UI fixes: Match per playlist, Match All button, local/external labels, preserve tab on reload 2026-02-03 18:27:29 -05:00
08af650d6c Add fuzzy name matching as fallback for local tracks + better error logging
- Add fuzzy matching by title+artist as fallback (like Jellyfin Spotify Import plugin)
- Add clear error messages when JELLYFIN_USER_ID is not configured
- Add emoji logging for easier debugging (🔍 📌  )
- Check HTTP status code when fetching playlist items
- This should fix the issue where all tracks show [S] even when they exist locally
2026-02-03 18:23:39 -05:00
c44be48eb9 Fix: Add UserId parameter for Jellyfin playlist operations + UI improvements
- CRITICAL FIX: Add UserId parameter to all Jellyfin playlist item fetches (fixes 400 BadRequest errors)
- Fix GetPlaylists to correctly count local/missing tracks
- Fix GetSpotifyPlaylistTracksOrderedAsync to find local tracks (was serving external tracks for everything)
- Fix SpotifyTrackMatchingService to skip tracks already in Jellyfin
- Add detailed debug logging for track matching (LOCAL by ISRC/Spotify ID, EXTERNAL match, NO MATCH)
- Add 'Match Tracks' button for individual playlists (not all playlists)
- Add 'Match All Tracks' button for matching all playlists at once
- Add JELLYFIN_USER_ID to web UI configuration tab for easy setup
- Add /api/admin/playlists/match-all endpoint

This fixes the issue where local tracks weren't being used - the system was downloading
from SquidWTF even when files existed locally in Jellyfin.
2026-02-03 18:14:13 -05:00
b16d16c9c9 Fix: Register SpotifyTrackMatchingService as singleton for DI
- Register as singleton first, then as hosted service
- Allows AdminController to inject and trigger matching manually
- Added better logging for Jellyfin playlist fetch failures
- Logs when JellyfinId is missing or API calls fail
- Helps debug '0 local / 0 missing' issue
2026-02-03 17:51:55 -05:00
e51d569d79 Show actual local/missing track counts in Active Playlists
- Fetches current Jellyfin playlist track count
- Compares with Spotify playlist total to calculate missing tracks
- Shows: '25 local / 5 missing' instead of just track count
- Local = tracks currently in Jellyfin playlist
- Missing = Spotify tracks not yet in Jellyfin (need to be matched)
- Helps users see which playlists need track matching
2026-02-03 17:48:49 -05:00
363c9e6f1b Add Match Tracks button and Local/External column to Active Playlists
- Added 'Match Tracks' button to trigger matching for specific playlist
- Added Local/External column (shows '-' for now, will populate after matching)
- New endpoint: POST /api/admin/playlists/{name}/match
- Injects SpotifyTrackMatchingService into AdminController
- UI shows: Name | Spotify ID | Total | Local/External | Cache Age | Actions
- Allows users to manually trigger matching without waiting 30 minutes
2026-02-03 17:47:44 -05:00
f813fe9eeb Fix: Match local Jellyfin tracks by ISRC instead of Spotify ID
- Local Jellyfin tracks don't have Spotify IDs (only plugin-added tracks do)
- Now matches by ISRC first (most reliable), then falls back to Spotify ID
- Builds dictionaries for fast lookup: existingBySpotifyId and existingByIsrc
- Prioritizes local tracks over external matches
- Logs: 'X tracks (Y with Spotify IDs, Z with ISRCs)'
- This fixes all tracks showing [S] - now uses local files when available
2026-02-03 17:45:53 -05:00
ef0ee65160 Optimize UI: Update playlist status without refetching all playlists
- After linking/unlinking, update UI state directly instead of refetching
- Avoids 7+ second delay from fetching track stats for all playlists
- Added data-playlist-id attribute to table rows for easy lookup
- Only refreshes Active Playlists tab (fast operation)
- Much better UX when linking multiple playlists
2026-02-03 17:40:47 -05:00
b3bfa16b93 Fix RemovePlaylist to use new 4-field config format
- Include JellyfinId when serializing playlists after removal
- Ensures config format stays consistent: [Name,SpotifyId,JellyfinId,position]
- Fixes issue where removed playlists would stay in the list
2026-02-03 17:35:54 -05:00
aa9b5c874d Skip matching tracks already in Jellyfin playlist
- Fetch existing tracks from Jellyfin playlist before matching
- Extract Spotify IDs from ProviderIds to identify already-matched tracks
- Only search for tracks not already in Jellyfin
- Logs: 'Matching X/Y tracks (skipping Z already in Jellyfin)'
- Significantly reduces matching time for playlists with local content
- Example: If 20/50 tracks exist locally, only searches for 30 tracks
2026-02-03 17:29:28 -05:00
e3546425eb Optimize track matching with parallel batch processing
- Process tracks in batches of 11 (matches SquidWTF provider count)
- Each batch runs 11 parallel searches (one per provider)
- Wait 150ms between batches (not between individual tracks)
- This is ~11x faster: 50 tracks now takes ~1 second instead of ~7.5 seconds
- Round-robin in SquidWTF ensures each parallel request hits a different provider
- Maintains rate limiting while maximizing throughput
2026-02-03 17:20:05 -05:00
5646aa07ea Fix playlist injection: Store Jellyfin playlist ID in config
- Added JellyfinId field to SpotifyPlaylistConfig model
- Updated config format: [[Name,SpotifyId,JellyfinId,position],...]
- LinkPlaylist now stores both Jellyfin and Spotify playlist IDs
- IsSpotifyPlaylist() now checks by Jellyfin playlist ID (not Spotify ID)
- GetJellyfinPlaylists shows linked status by checking JellyfinId
- Updated Program.cs to parse new 4-field format
- Backward compatible: JellyfinId defaults to empty string for old configs

This fixes the issue where playlists weren't being recognized as configured
because the code was checking Jellyfin playlist IDs against Spotify IDs.
2026-02-03 17:18:08 -05:00
7cdf7e3806 Switch to Spotify GraphQL API to match Jellyfin plugin approach
- Replace REST API calls with GraphQL API (api-partner.spotify.com/pathfinder/v1/query)
- Use same persisted query approach as Jellyfin Spotify Import plugin
- GraphQL API is more reliable and has better rate limits
- Fetch playlists with operationName: fetchPlaylist
- Parse GraphQL response structure (playlistV2, itemV2, etc)
- This should eliminate TooManyRequests errors
2026-02-03 17:10:38 -05:00
fe9c1e17be Add rate limiting delays to prevent Spotify 429 errors
- Increase delay between playlist fetches from 1s to 3s
- Only delay between playlists, not after the last one
- Add debug logging for rate limit delays
- Spotify is very aggressive with rate limiting on their API
2026-02-03 17:05:34 -05:00
63324def62 Fix Jellyfin BadRequest errors when fetching playlist items
- Add UserId parameter to playlist items API call (required by Jellyfin)
- Auto-fetch first user if no user ID configured
- Simplify Fields parameter to only request Path
- This fixes the 400 BadRequest errors when loading Jellyfin playlists tab
2026-02-03 17:03:04 -05:00
ff72ae2395 Fix playlist linking to save all playlists and add restart banner
- Read playlists from .env file instead of stale in-memory config
- This fixes issue where linking multiple playlists only saved the last one
- Add prominent restart banner at top when config changes
- Update UI immediately after linking/unlinking playlists
- Banner shows 'Restart Now' button and dismissible option
- All config changes now show the restart banner instead of toast messages
- GetJellyfinPlaylists now reads from .env for accurate linked status
2026-02-03 17:00:23 -05:00
1a3134083b Fix remaining pragma warnings in debug console output 2026-02-03 16:55:32 -05:00
bd64f437cd Fix web UI infinite loop and improve cookie age tracking
- Add cookieDateInitialized flag to prevent infinite init-cookie-date calls
- Auto-initialize cookie date when cookie exists but date not tracked
- Improve local vs external track detection in Jellyfin playlists
- Support multiple Spotify playlist ID formats (ID, URI, URL)
- Fix pragma warnings in legacy playlist parsing code
- Simplify GetPlaylistTrackStats to check Path property for local tracks
2026-02-03 16:54:40 -05:00
5606706dc8 Fix web UI config persistence and cookie age tracking
- Add SPOTIFY_API_SESSION_COOKIE_SET_DATE to docker-compose.yml env mapping
- Mount .env file in container for web UI to update
- Add SessionCookieSetDate loading in Program.cs
- Improve .env update logic with better error handling and logging
- Auto-initialize cookie date when cookie exists but date not set
- Simplify local vs external track detection in Jellyfin playlists
- Enhanced Spotify playlist ID parsing (supports ID, URI, and URL formats)
- Better UI clarity: renamed tabs to 'Link Playlists' and 'Active Playlists'
2026-02-03 16:50:19 -05:00
79a9e4063d Remove unnecessary /me API call - not needed for sp_dc auth 2026-02-03 16:27:19 -05:00
c33c85455f Fix JS syntax error: escape quotes in onclick handlers for playlist names 2026-02-03 16:19:13 -05:00
5af2bb1113 Remove hardcoded playlists from appsettings - use web UI instead 2026-02-03 16:14:28 -05:00
2c1297ebec Fix playlist config: dedupe entries, use Spotify playlist ID for lookup 2026-02-03 16:12:25 -05:00
df7f11e769 Add local/external track columns to Jellyfin playlists, remove libraries filter 2026-02-03 16:08:49 -05:00
75c7acb745 Fix Jellyfin playlists tab: linked Spotify ID, track count, and user/library filters 2026-02-03 15:13:29 -05:00
c7f6783fa2 CRITICAL: Stop hammering Spotify API on status checks
- GetStatus() no longer calls Spotify API at all
- Status is now determined purely from configuration
- Fixes rate limiting issue (429 errors every 30 seconds)
- Spotify API should only be called when actually fetching playlists
2026-02-03 15:08:17 -05:00
4c6406ef8f Add full-screen restart overlay for better UX
- Shows overlay with spinner during container restart
- Displays status messages: stopping, waiting, reloading
- Shows elapsed time while waiting for server
- Extended timeout to 60 seconds
- Hides overlay and shows toast if timeout exceeded
2026-02-03 15:06:38 -05:00
3ddf51924b Add container restart capability from admin UI
- Added POST /api/admin/restart endpoint using Docker socket
- Mounts Docker socket read-only in docker-compose.yml
- Admin UI now has working 'Restart Container' button
- Auto-reloads page after container comes back up
- Falls back to manual restart instructions if socket unavailable
2026-02-03 15:05:43 -05:00
3826f29019 Add Jellyfin playlist discovery and linking feature
- Added GET /api/admin/jellyfin/playlists to fetch all playlists from Jellyfin
- Added POST /api/admin/jellyfin/playlists/{id}/link to link playlist to Spotify
- Added DELETE /api/admin/jellyfin/playlists/{name}/unlink to remove link
- Added new 'Jellyfin Playlists' tab in admin UI showing all playlists
- Shows link status for each playlist (Linked/Not Linked)
- Link modal accepts Spotify playlist ID or full URL
- Renamed 'Playlists' tab to 'Configured Playlists' for clarity
2026-02-03 15:03:31 -05:00
4036c739a3 Remove LocalTracksPosition UI, auto-init cookie date tracking
- Removed LocalTracksPosition from playlist table (uses Spotify order now)
- Removed LocalTracksPosition from add playlist modal and JS
- Added /api/admin/config/init-cookie-date endpoint to auto-set date
- Cookie date auto-initializes when cookie exists but date is unknown
- Improved user display message when profile scope unavailable
- TOTP tokens work for playlists but don't have user-read-private scope
2026-02-03 15:00:12 -05:00
b7379e2fd4 Change external track labeling from ' - S' to ' [S]' format
- Updated album suffix from ' - S' to ' [S]'
- Updated artist suffix from ' - S' to ' [S]'
- Added song title suffix ' [S]' for external/streaming tracks
- Cleaner bracket format is more visible and consistent
2026-02-03 14:56:01 -05:00
c9895f6d1a Expand admin UI with full config editing and sp_dc cookie age tracking
- Fix auth status detection to use token validity instead of /me endpoint
- Add SessionCookieSetDate to SpotifyApiSettings for tracking cookie age
- Auto-set cookie date when updating sp_dc via admin UI
- Add edit buttons for all config settings (Spotify, Deezer, Qobuz, SquidWTF, Jellyfin)
- Show cookie age with color-coded expiration warnings (green/yellow/red)
- Display cookie age on both Dashboard and Config tabs
- Add generic edit setting modal supporting text/password/number/toggle/select inputs
- Remove SquidWTF base URL (not configurable)
- Add restart container button with manual restart instructions
2026-02-03 14:52:40 -05:00
71c4241a8a Fix AdminStaticFilesMiddleware to use simple file serving instead of internal ASP.NET classes 2026-02-03 14:43:24 -05:00
ffed9a67f3 Expose admin UI port 5275 in docker-compose (for direct/Tailscale access) 2026-02-03 14:39:18 -05:00
a8d04b225b Move admin UI to separate internal port (5275) for security
- Admin API and static files only accessible on port 5275
- Main proxy port (8080) no longer serves admin endpoints
- AdminPortFilter rejects admin requests on wrong port
- AdminStaticFilesMiddleware only serves static files on admin port
- Port 5275 NOT exposed in Dockerfile or docker-compose by default
- Access admin UI via SSH tunnel or by uncommenting port mapping
2026-02-03 14:39:07 -05:00
6abf0e0717 Add web dashboard UI for configuration and playlist management
- Add AdminController with API endpoints for status, playlists, config
- Add web UI dashboard at /admin with dark theme
- Disable SpotifyMissingTracksFetcher when SpotifyApi is enabled with cookie
- Support viewing playlists, tracks, and managing configuration
- Add .env file modification from UI (requires restart to apply)
- Dashboard auto-refreshes status every 30 seconds
2026-02-03 14:37:26 -05:00
8e7fc8b4ef Add info-level logging for Spotify auth debugging 2026-02-03 14:31:28 -05:00
b2c28d10f1 Fix TOTP secrets URL - use correct xyloflake/spot-secrets-go repo 2026-02-03 14:29:39 -05:00
a335997196 Implement TOTP-based Spotify authentication (like Jellyfin plugin)
- Use pre-scraped TOTP secrets from Viperinius/spotify-totp-secrets
- Generate TOTP code using server time and cipher transformation
- Add OTP.NET package for TOTP generation
- Match the authentication flow used by jellyfin-plugin-spotify-import
2026-02-03 14:27:18 -05:00
590f8f76cb Fix Spotify token endpoint - add required query params and fix cookie handling 2026-02-03 14:22:51 -05:00
1532d74a20 Add SpotifyApi environment variables to docker-compose.yml 2026-02-03 14:18:25 -05:00
f5ce355747 Fix logging levels - use appropriate debug/info/warn/error levels 2026-02-03 14:12:39 -05:00
494b4bbbc2 Reduce WebSocket/session logging noise, add playback progress logging
- Changed routine WebSocket messages (Sessions, KeepAlive) to debug level
- Reduced keep-alive spam - 401 on expired tokens is expected, not an error
- Added periodic progress logging for local tracks (every 10 seconds)
- Log warnings only when progress reports fail
2026-02-03 14:09:40 -05:00