diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index f4344c6..998be69 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -787,9 +787,9 @@ public class AdminController : ControllerBase else if (item.TryGetProperty("RecursiveItemCount", out var ric) && ric.ValueKind == JsonValueKind.Number) childCount = ric.GetInt32(); - // Check if this playlist is configured in allstarr and get linked Spotify ID + // Check if this playlist is configured in allstarr by Jellyfin ID var configuredPlaylist = configuredPlaylists - .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(p => p.JellyfinId.Equals(id, StringComparison.OrdinalIgnoreCase)); var isConfigured = configuredPlaylist != null; var linkedSpotifyId = configuredPlaylist?.Id; @@ -945,13 +945,22 @@ public class AdminController : ControllerBase // Read current playlists from .env file (not in-memory config which is stale) var currentPlaylists = await ReadPlaylistsFromEnvFile(); - // Check if already configured - var existingPlaylist = currentPlaylists + // Check if already configured by Jellyfin ID + var existingByJellyfinId = currentPlaylists + .FirstOrDefault(p => p.JellyfinId.Equals(jellyfinPlaylistId, StringComparison.OrdinalIgnoreCase)); + + if (existingByJellyfinId != null) + { + return BadRequest(new { error = $"This Jellyfin playlist is already linked to '{existingByJellyfinId.Name}'" }); + } + + // Check if already configured by name + var existingByName = currentPlaylists .FirstOrDefault(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); - if (existingPlaylist != null) + if (existingByName != null) { - return BadRequest(new { error = $"Playlist '{request.Name}' is already configured" }); + return BadRequest(new { error = $"Playlist name '{request.Name}' is already configured" }); } // Add the playlist to configuration @@ -959,12 +968,13 @@ public class AdminController : ControllerBase { Name = request.Name, Id = request.SpotifyPlaylistId, + JellyfinId = jellyfinPlaylistId, LocalTracksPosition = LocalTracksPosition.First // Use Spotify order }); - // Convert to JSON format for env var + // Convert to JSON format for env var: [["Name","SpotifyId","JellyfinId","first|last"],...] var playlistsJson = JsonSerializer.Serialize( - currentPlaylists.Select(p => new[] { p.Name, p.Id, p.LocalTracksPosition.ToString().ToLower() }).ToArray() + currentPlaylists.Select(p => new[] { p.Name, p.Id, p.JellyfinId, p.LocalTracksPosition.ToString().ToLower() }).ToArray() ); // Update .env file @@ -1020,7 +1030,7 @@ public class AdminController : ControllerBase return playlists; } - // Parse JSON array format: [["Name","Id","first|last"],...] + // Parse JSON array format: [["Name","SpotifyId","JellyfinId","first|last"],...] var playlistArrays = JsonSerializer.Deserialize(value); if (playlistArrays != null) { @@ -1032,8 +1042,9 @@ public class AdminController : ControllerBase { Name = arr[0].Trim(), Id = arr[1].Trim(), - LocalTracksPosition = arr.Length >= 3 && - arr[2].Trim().Equals("last", StringComparison.OrdinalIgnoreCase) + JellyfinId = arr.Length >= 3 ? arr[2].Trim() : "", + LocalTracksPosition = arr.Length >= 4 && + arr[3].Trim().Equals("last", StringComparison.OrdinalIgnoreCase) ? LocalTracksPosition.Last : LocalTracksPosition.First }); diff --git a/allstarr/Models/Settings/SpotifyImportSettings.cs b/allstarr/Models/Settings/SpotifyImportSettings.cs index ce8a55e..44d2768 100644 --- a/allstarr/Models/Settings/SpotifyImportSettings.cs +++ b/allstarr/Models/Settings/SpotifyImportSettings.cs @@ -34,6 +34,13 @@ public class SpotifyPlaylistConfig /// public string Id { get; set; } = string.Empty; + /// + /// Jellyfin playlist ID (internal Jellyfin GUID) + /// Example: "4383a46d8bcac3be2ef9385053ea18df" + /// This is the ID Jellyfin uses when requesting playlist tracks + /// + public string JellyfinId { get; set; } = string.Empty; + /// /// Where to position local tracks: "first" or "last" /// @@ -108,6 +115,12 @@ public class SpotifyImportSettings public SpotifyPlaylistConfig? GetPlaylistById(string playlistId) => Playlists.FirstOrDefault(p => p.Id.Equals(playlistId, StringComparison.OrdinalIgnoreCase)); + /// + /// Gets the playlist configuration by Jellyfin playlist ID. + /// + public SpotifyPlaylistConfig? GetPlaylistByJellyfinId(string jellyfinPlaylistId) => + Playlists.FirstOrDefault(p => p.JellyfinId.Equals(jellyfinPlaylistId, StringComparison.OrdinalIgnoreCase)); + /// /// Gets the playlist configuration by name. /// @@ -115,8 +128,8 @@ public class SpotifyImportSettings Playlists.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); /// - /// Checks if a playlist ID is configured for Spotify import. + /// Checks if a Jellyfin playlist ID is configured for Spotify import. /// - public bool IsSpotifyPlaylist(string playlistId) => - Playlists.Any(p => p.Id.Equals(playlistId, StringComparison.OrdinalIgnoreCase)); + public bool IsSpotifyPlaylist(string jellyfinPlaylistId) => + Playlists.Any(p => p.JellyfinId.Equals(jellyfinPlaylistId, StringComparison.OrdinalIgnoreCase)); } diff --git a/allstarr/Program.cs b/allstarr/Program.cs index 359f82e..cdba55f 100644 --- a/allstarr/Program.cs +++ b/allstarr/Program.cs @@ -135,7 +135,7 @@ builder.Services.Configure(options => #pragma warning restore CS0618 // Parse SPOTIFY_IMPORT_PLAYLISTS env var (JSON array format) - // Format: [["Name","Id","first|last"],["Name2","Id2","first|last"]] + // Format: [["Name","SpotifyId","JellyfinId","first|last"],["Name2","SpotifyId2","JellyfinId2","first|last"]] var playlistsEnv = builder.Configuration.GetValue("SpotifyImport:Playlists"); if (!string.IsNullOrWhiteSpace(playlistsEnv)) { @@ -158,13 +158,14 @@ builder.Services.Configure(options => { Name = arr[0].Trim(), Id = arr[1].Trim(), - LocalTracksPosition = arr.Length >= 3 && - arr[2].Trim().Equals("last", StringComparison.OrdinalIgnoreCase) + JellyfinId = arr.Length >= 3 ? arr[2].Trim() : "", + LocalTracksPosition = arr.Length >= 4 && + arr[3].Trim().Equals("last", StringComparison.OrdinalIgnoreCase) ? LocalTracksPosition.Last : LocalTracksPosition.First }; options.Playlists.Add(config); - Console.WriteLine($" Added: {config.Name} (ID: {config.Id}, Position: {config.LocalTracksPosition})"); + Console.WriteLine($" Added: {config.Name} (Spotify: {config.Id}, Jellyfin: {config.JellyfinId}, Position: {config.LocalTracksPosition})"); } } } @@ -176,7 +177,7 @@ builder.Services.Configure(options => catch (System.Text.Json.JsonException ex) { Console.WriteLine($"Warning: Failed to parse SPOTIFY_IMPORT_PLAYLISTS: {ex.Message}"); - Console.WriteLine("Expected format: [[\"Name\",\"Id\",\"first|last\"],[\"Name2\",\"Id2\",\"first|last\"]]"); + Console.WriteLine("Expected format: [[\"Name\",\"SpotifyId\",\"JellyfinId\",\"first|last\"],[\"Name2\",\"SpotifyId2\",\"JellyfinId2\",\"first|last\"]]"); Console.WriteLine("Will try legacy format instead"); } }