From 3319c9b21b9948662de83fbc63a16c4fb7a757ee Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Thu, 5 Feb 2026 11:23:01 -0500 Subject: [PATCH] Fix external mapping: add Map to External button for external tracks, fetch metadata from provider, set searchQuery for missing tracks --- allstarr/Controllers/AdminController.cs | 94 +++++++++++++++++-------- allstarr/wwwroot/index.html | 11 ++- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index 073db61..87861e0 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -640,7 +640,7 @@ public class AdminController : ControllerBase albumArtUrl = track.AlbumArtUrl, isLocal = isLocal, externalProvider = externalProvider, - searchQuery = isLocal == false ? $"{track.Title} {track.PrimaryArtist}" : null, + searchQuery = isLocal != true ? $"{track.Title} {track.PrimaryArtist}" : null, // Set for both external and missing isManualMapping = isManualMapping, manualMappingType = manualMappingType, manualMappingId = manualMappingId @@ -740,7 +740,7 @@ public class AdminController : ControllerBase albumArtUrl = track.AlbumArtUrl, isLocal = isLocal, externalProvider = externalProvider, - searchQuery = isLocal == false ? $"{track.Title} {track.PrimaryArtist}" : null + searchQuery = isLocal != true ? $"{track.Title} {track.PrimaryArtist}" : null // Set for both external and missing }); } @@ -1031,45 +1031,76 @@ public class AdminController : ControllerBase _logger.LogInformation("Cleared playlist caches for {Playlist} to force rebuild", decodedName); - // Fetch the mapped Jellyfin track details to return to the UI + // Fetch the mapped track details to return to the UI string? trackTitle = null; string? trackArtist = null; string? trackAlbum = null; + bool isLocalMapping = hasJellyfinMapping; - try + if (hasJellyfinMapping) { - var userId = _jellyfinSettings.UserId; - var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}"; - if (!string.IsNullOrEmpty(userId)) + // Fetch Jellyfin track details + try { - trackUrl += $"?UserId={userId}"; - } - - var trackRequest = new HttpRequestMessage(HttpMethod.Get, trackUrl); - trackRequest.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader()); - - var response = await _jellyfinHttpClient.SendAsync(trackRequest); - - if (response.IsSuccessStatusCode) - { - var trackData = await response.Content.ReadAsStringAsync(); - using var doc = JsonDocument.Parse(trackData); - var track = doc.RootElement; + var userId = _jellyfinSettings.UserId; + var trackUrl = $"{_jellyfinSettings.Url}/Items/{request.JellyfinId}"; + if (!string.IsNullOrEmpty(userId)) + { + trackUrl += $"?UserId={userId}"; + } - trackTitle = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null; - trackArtist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() : - (track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0 - ? artistsEl[0].GetString() : null); - trackAlbum = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : null; + var trackRequest = new HttpRequestMessage(HttpMethod.Get, trackUrl); + trackRequest.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader()); + + var response = await _jellyfinHttpClient.SendAsync(trackRequest); + + if (response.IsSuccessStatusCode) + { + var trackData = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(trackData); + var track = doc.RootElement; + + trackTitle = track.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : null; + trackArtist = track.TryGetProperty("AlbumArtist", out var artistEl) ? artistEl.GetString() : + (track.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0 + ? artistsEl[0].GetString() : null); + trackAlbum = track.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : null; + } + else + { + _logger.LogWarning("Failed to fetch Jellyfin track {Id}: {StatusCode}", request.JellyfinId, response.StatusCode); + } } - else + catch (Exception ex) { - _logger.LogWarning("Failed to fetch Jellyfin track {Id}: {StatusCode}", request.JellyfinId, response.StatusCode); + _logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved"); } } - catch (Exception ex) + else { - _logger.LogWarning(ex, "Failed to fetch mapped track details, but mapping was saved"); + // Fetch external provider track details + try + { + var metadataService = HttpContext.RequestServices.GetRequiredService(); + var externalSong = await metadataService.GetSongAsync(request.ExternalProvider!, request.ExternalId!); + + if (externalSong != null) + { + trackTitle = externalSong.Title; + trackArtist = externalSong.Artist; + trackAlbum = externalSong.Album; + _logger.LogInformation("✓ Fetched external track metadata: {Title} by {Artist}", trackTitle, trackArtist); + } + else + { + _logger.LogWarning("Failed to fetch external track metadata for {Provider} ID {Id}", + request.ExternalProvider, request.ExternalId); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to fetch external track metadata, but mapping was saved"); + } } // Trigger immediate playlist rebuild with the new mapping @@ -1104,11 +1135,12 @@ public class AdminController : ControllerBase // Return success with track details if available var mappedTrack = new { - id = request.JellyfinId, + id = hasJellyfinMapping ? request.JellyfinId : request.ExternalId, title = trackTitle ?? "Unknown", artist = trackArtist ?? "Unknown", album = trackAlbum ?? "Unknown", - isLocal = true + isLocal = isLocalMapping, + externalProvider = hasExternalMapping ? request.ExternalProvider : null }; return Ok(new diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index bc882b2..005a76a 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -1799,7 +1799,7 @@ if (t.isManualMapping && t.manualMappingType === 'external') { statusBadge += 'Manual'; } - // Add manual map button for external tracks using data attributes + // Add both mapping buttons for external tracks using data attributes const firstArtist = (t.artists && t.artists.length > 0) ? t.artists[0] : ''; mapButton = ``; + style="margin-left:8px;font-size:0.75rem;padding:4px 8px;">Map to Local + `; } else { // isLocal is null/undefined - track is missing (not found locally or externally) statusBadge = 'Missing';