diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index bbb096c..b64655b 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -2860,20 +2860,26 @@ public class JellyfinController : ControllerBase var userId = _settings.UserId; if (string.IsNullOrEmpty(userId)) { - _logger.LogWarning("No UserId configured - attempting to fetch existing playlist tracks may fail"); + _logger.LogError("❌ JELLYFIN_USER_ID is NOT configured! Cannot fetch playlist tracks. Set it in .env or admin UI."); + return null; // Fall back to legacy mode } - var playlistItemsUrl = $"Playlists/{playlistId}/Items"; - if (!string.IsNullOrEmpty(userId)) - { - playlistItemsUrl += $"?UserId={userId}"; - } + var playlistItemsUrl = $"Playlists/{playlistId}/Items?UserId={userId}"; - var (existingTracksResponse, _) = await _proxyService.GetJsonAsync( + _logger.LogInformation("🔍 Fetching existing tracks from Jellyfin playlist {PlaylistId} with UserId {UserId}", + playlistId, userId); + + var (existingTracksResponse, statusCode) = await _proxyService.GetJsonAsync( playlistItemsUrl, null, Request.Headers); + if (statusCode != 200) + { + _logger.LogError("❌ Failed to fetch Jellyfin playlist items: HTTP {StatusCode}. Check JELLYFIN_USER_ID is correct.", statusCode); + return null; + } + var existingTracks = new List(); var existingBySpotifyId = new Dictionary(); // SpotifyId -> Song var existingByIsrc = new Dictionary(); // ISRC -> Song @@ -2894,7 +2900,7 @@ public class JellyfinController : ControllerBase if (!string.IsNullOrEmpty(spotifyId)) { existingBySpotifyId[spotifyId] = song; - _logger.LogDebug("Indexed local track by Spotify ID: {SpotifyId} -> {Title}", spotifyId, song.Title); + _logger.LogDebug(" 📌 Indexed local track by Spotify ID: {SpotifyId} -> {Title}", spotifyId, song.Title); } } @@ -2902,15 +2908,16 @@ public class JellyfinController : ControllerBase if (!string.IsNullOrEmpty(song.Isrc)) { existingByIsrc[song.Isrc] = song; - _logger.LogDebug("Indexed local track by ISRC: {Isrc} -> {Title}", song.Isrc, song.Title); + _logger.LogDebug(" 📌 Indexed local track by ISRC: {Isrc} -> {Title}", song.Isrc, song.Title); } } - _logger.LogInformation("Found {Count} existing tracks in Jellyfin playlist ({SpotifyIds} with Spotify IDs, {Isrcs} with ISRCs)", + _logger.LogInformation("✅ Found {Count} existing tracks in Jellyfin playlist ({SpotifyIds} with Spotify IDs, {Isrcs} with ISRCs)", existingTracks.Count, existingBySpotifyId.Count, existingByIsrc.Count); } else { - _logger.LogWarning("No existing tracks found in Jellyfin playlist {PlaylistId} - may need UserId parameter", playlistId); + _logger.LogError("❌ No existing tracks found in Jellyfin playlist {PlaylistId} - Jellyfin Spotify Import plugin may not have run yet", playlistId); + return null; } // Get the full playlist from Spotify to know the correct order @@ -2930,7 +2937,7 @@ public class JellyfinController : ControllerBase { Song? localTrack = null; - // Try to find local track by Spotify ID first (fastest) + // Try to find local track by Spotify ID first (fastest and most reliable) if (existingBySpotifyId.TryGetValue(spotifyTrack.SpotifyId, out var trackBySpotifyId)) { localTrack = trackBySpotifyId; @@ -2945,6 +2952,34 @@ public class JellyfinController : ControllerBase _logger.LogDebug("#{Pos} {Title} - Found LOCAL by ISRC: {Isrc}", spotifyTrack.Position, spotifyTrack.Title, spotifyTrack.Isrc); } + // Fallback: Match by title + artist name (like Jellyfin Spotify Import plugin does) + else + { + var bestMatch = existingTracks + .Select(song => new + { + Song = song, + TitleScore = FuzzyMatcher.CalculateSimilarity(spotifyTrack.Title, song.Title), + ArtistScore = FuzzyMatcher.CalculateSimilarity(spotifyTrack.PrimaryArtist, song.Artist) + }) + .Select(x => new + { + x.Song, + x.TitleScore, + x.ArtistScore, + TotalScore = (x.TitleScore * 0.7) + (x.ArtistScore * 0.3) // Weight title more + }) + .OrderByDescending(x => x.TotalScore) + .FirstOrDefault(); + + // Only use if match is good enough (>75% combined score) + if (bestMatch != null && bestMatch.TotalScore >= 75) + { + localTrack = bestMatch.Song; + _logger.LogDebug("#{Pos} {Title} - Found LOCAL by fuzzy match: {MatchTitle} (score: {Score:F1})", + spotifyTrack.Position, spotifyTrack.Title, bestMatch.Song.Title, bestMatch.TotalScore); + } + } // If we found a local track, use it if (localTrack != null)