mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Refactor Spotify playlist injection to use playlist IDs instead of names
This commit is contained in:
@@ -116,7 +116,7 @@ SPOTIFY_IMPORT_SYNC_START_MINUTE=15
|
|||||||
# Example: If plugin runs at 4:15 PM and window is 2 hours, checks from 4:00 PM to 6:00 PM
|
# Example: If plugin runs at 4:15 PM and window is 2 hours, checks from 4:00 PM to 6:00 PM
|
||||||
SPOTIFY_IMPORT_SYNC_WINDOW_HOURS=2
|
SPOTIFY_IMPORT_SYNC_WINDOW_HOURS=2
|
||||||
|
|
||||||
# Playlists to inject (comma-separated, optional)
|
# Playlist IDs to inject (comma-separated)
|
||||||
# Available: Release Radar, Discover Weekly
|
# Get IDs from Jellyfin playlist URLs: https://jellyfin.example.com/web/#/details?id=PLAYLIST_ID
|
||||||
# Leave empty to disable all, or specify which ones to enable
|
# Example: SPOTIFY_IMPORT_PLAYLIST_IDS=4383a46d8bcac3be2ef9385053ea18df,ba50e26c867ec9d57ab2f7bf24cfd6b0
|
||||||
SPOTIFY_IMPORT_PLAYLISTS=Release Radar,Discover Weekly
|
SPOTIFY_IMPORT_PLAYLIST_IDS=
|
||||||
|
|||||||
@@ -1281,36 +1281,23 @@ public class JellyfinController : ControllerBase
|
|||||||
return _responseBuilder.CreateItemsResponse(tracks);
|
return _responseBuilder.CreateItemsResponse(tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only check for Spotify playlists if the feature is enabled
|
// Check if this is a Spotify playlist (by ID)
|
||||||
_logger.LogInformation("Spotify Import Enabled: {Enabled}, Playlist starts with ext-: {IsExternal}",
|
_logger.LogInformation("Spotify Import Enabled: {Enabled}, Configured IDs: {Count}",
|
||||||
_spotifySettings.Enabled, playlistId.StartsWith("ext-"));
|
_spotifySettings.Enabled, _spotifySettings.PlaylistIds.Count);
|
||||||
|
|
||||||
if (_spotifySettings.Enabled && !playlistId.StartsWith("ext-"))
|
if (_spotifySettings.Enabled &&
|
||||||
|
_spotifySettings.PlaylistIds.Any(id => id.Equals(playlistId, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
// Get playlist info from Jellyfin to check the name
|
// Get playlist info from Jellyfin to get the name for matching missing tracks
|
||||||
_logger.LogInformation("Fetching playlist info from Jellyfin for ID: {PlaylistId}", playlistId);
|
_logger.LogInformation("Fetching playlist info from Jellyfin for ID: {PlaylistId}", playlistId);
|
||||||
var playlistInfo = await _proxyService.GetJsonAsync($"Items/{playlistId}", null, Request.Headers);
|
var playlistInfo = await _proxyService.GetJsonAsync($"Items/{playlistId}", null, Request.Headers);
|
||||||
|
|
||||||
if (playlistInfo != null && playlistInfo.RootElement.TryGetProperty("Name", out var nameElement))
|
if (playlistInfo != null && playlistInfo.RootElement.TryGetProperty("Name", out var nameElement))
|
||||||
{
|
{
|
||||||
var playlistName = nameElement.GetString() ?? "";
|
var playlistName = nameElement.GetString() ?? "";
|
||||||
_logger.LogInformation("Jellyfin playlist name: '{PlaylistName}'", playlistName);
|
_logger.LogInformation("✓ MATCHED! Intercepting Spotify playlist: {PlaylistName} (ID: {PlaylistId})",
|
||||||
|
playlistName, playlistId);
|
||||||
// Check if this matches any configured Spotify playlists
|
return await GetSpotifyPlaylistTracksAsync(playlistName);
|
||||||
var matchingConfig = _spotifySettings.Playlists
|
|
||||||
.FirstOrDefault(p => p.Enabled && p.Name.Equals(playlistName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (matchingConfig != null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("✓ MATCHED! Intercepting Spotify playlist: {PlaylistName}", playlistName);
|
|
||||||
return await GetSpotifyPlaylistTracksAsync(matchingConfig.SpotifyName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("✗ No match found for playlist '{PlaylistName}'", playlistName);
|
|
||||||
_logger.LogInformation("Configured playlists: {Playlists}",
|
|
||||||
string.Join(", ", _spotifySettings.Playlists.Select(p => $"'{p.Name}' (enabled={p.Enabled})")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1705,48 +1692,34 @@ public class JellyfinController : ControllerBase
|
|||||||
// DEBUG: Log EVERY request to see what's happening
|
// DEBUG: Log EVERY request to see what's happening
|
||||||
_logger.LogWarning("ProxyRequest called with path: {Path}", path);
|
_logger.LogWarning("ProxyRequest called with path: {Path}", path);
|
||||||
|
|
||||||
// DEBUG: Log Spotify settings for playlist requests
|
// Intercept Spotify playlist requests by ID
|
||||||
if (path.Contains("playlist", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("=== PLAYLIST REQUEST DEBUG ===");
|
|
||||||
_logger.LogWarning("Path: {Path}", path);
|
|
||||||
_logger.LogWarning("Spotify Enabled: {Enabled}", _spotifySettings.Enabled);
|
|
||||||
_logger.LogWarning("Starts with 'playlists/': {StartsWith}", path.StartsWith("playlists/", StringComparison.OrdinalIgnoreCase));
|
|
||||||
_logger.LogWarning("Contains '/items': {Contains}", path.Contains("/items", StringComparison.OrdinalIgnoreCase));
|
|
||||||
_logger.LogWarning("Playlists count: {Count}", _spotifySettings.Playlists.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intercept Spotify playlist requests FIRST
|
|
||||||
if (_spotifySettings.Enabled &&
|
if (_spotifySettings.Enabled &&
|
||||||
path.StartsWith("playlists/", StringComparison.OrdinalIgnoreCase) &&
|
path.StartsWith("playlists/", StringComparison.OrdinalIgnoreCase) &&
|
||||||
path.Contains("/items", StringComparison.OrdinalIgnoreCase))
|
path.Contains("/items", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("========================================");
|
|
||||||
_logger.LogInformation("=== SPOTIFY PLAYLIST REQUEST INTERCEPTED ===");
|
|
||||||
_logger.LogInformation("Path: {Path}", path);
|
|
||||||
_logger.LogInformation("Spotify Import Enabled: {Enabled}", _spotifySettings.Enabled);
|
|
||||||
_logger.LogInformation("Configured Playlists: {Count}", _spotifySettings.Playlists.Count);
|
|
||||||
foreach (var p in _spotifySettings.Playlists)
|
|
||||||
{
|
|
||||||
_logger.LogInformation(" - {Name} (SpotifyName: {SpotifyName}, Enabled: {Enabled})",
|
|
||||||
p.Name, p.SpotifyName, p.Enabled);
|
|
||||||
}
|
|
||||||
_logger.LogInformation("========================================");
|
|
||||||
|
|
||||||
// Extract playlist ID from path: playlists/{id}/items
|
// Extract playlist ID from path: playlists/{id}/items
|
||||||
var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length >= 2 && parts[0].Equals("playlists", StringComparison.OrdinalIgnoreCase))
|
if (parts.Length >= 2 && parts[0].Equals("playlists", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var playlistId = parts[1];
|
var playlistId = parts[1];
|
||||||
_logger.LogInformation("Extracted playlist ID: {PlaylistId}", playlistId);
|
|
||||||
|
_logger.LogWarning("=== PLAYLIST REQUEST ===");
|
||||||
|
_logger.LogWarning("Playlist ID: {PlaylistId}", playlistId);
|
||||||
|
_logger.LogWarning("Spotify Enabled: {Enabled}", _spotifySettings.Enabled);
|
||||||
|
_logger.LogWarning("Configured IDs: {Ids}", string.Join(", ", _spotifySettings.PlaylistIds));
|
||||||
|
_logger.LogWarning("Is configured: {IsConfigured}", _spotifySettings.PlaylistIds.Contains(playlistId, StringComparer.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
// Check if this playlist ID is configured for Spotify injection
|
||||||
|
if (_spotifySettings.PlaylistIds.Any(id => id.Equals(playlistId, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("========================================");
|
||||||
|
_logger.LogInformation("=== INTERCEPTING SPOTIFY PLAYLIST ===");
|
||||||
|
_logger.LogInformation("Playlist ID: {PlaylistId}", playlistId);
|
||||||
|
_logger.LogInformation("========================================");
|
||||||
return await GetPlaylistTracks(playlistId);
|
return await GetPlaylistTracks(playlistId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Intercept Spotify playlist requests (alternative path format)
|
|
||||||
if (_spotifySettings.Enabled &&
|
|
||||||
path.StartsWith("playlists/", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
path.EndsWith("/items", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
// Extract playlist ID from path: playlists/{id}/items
|
// Extract playlist ID from path: playlists/{id}/items
|
||||||
var parts = path.Split('/');
|
var parts = path.Split('/');
|
||||||
@@ -2009,36 +1982,27 @@ public class JellyfinController : ControllerBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var matchingPlaylist = _spotifySettings.Playlists
|
var cacheKey = $"spotify:matched:{spotifyPlaylistName}";
|
||||||
.FirstOrDefault(p => p.SpotifyName.Equals(spotifyPlaylistName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (matchingPlaylist == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Spotify playlist not found in config: {PlaylistName}", spotifyPlaylistName);
|
|
||||||
return _responseBuilder.CreateItemsResponse(new List<Song>());
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheKey = $"spotify:matched:{matchingPlaylist.SpotifyName}";
|
|
||||||
var cachedTracks = await _cache.GetAsync<List<Song>>(cacheKey);
|
var cachedTracks = await _cache.GetAsync<List<Song>>(cacheKey);
|
||||||
|
|
||||||
if (cachedTracks != null)
|
if (cachedTracks != null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Returning {Count} cached matched tracks for {Playlist}",
|
_logger.LogDebug("Returning {Count} cached matched tracks for {Playlist}",
|
||||||
cachedTracks.Count, matchingPlaylist.Name);
|
cachedTracks.Count, spotifyPlaylistName);
|
||||||
return _responseBuilder.CreateItemsResponse(cachedTracks);
|
return _responseBuilder.CreateItemsResponse(cachedTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
var missingTracksKey = $"spotify:missing:{matchingPlaylist.SpotifyName}";
|
var missingTracksKey = $"spotify:missing:{spotifyPlaylistName}";
|
||||||
var missingTracks = await _cache.GetAsync<List<allstarr.Models.Spotify.MissingTrack>>(missingTracksKey);
|
var missingTracks = await _cache.GetAsync<List<allstarr.Models.Spotify.MissingTrack>>(missingTracksKey);
|
||||||
|
|
||||||
if (missingTracks == null || missingTracks.Count == 0)
|
if (missingTracks == null || missingTracks.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("No missing tracks found for {Playlist}", matchingPlaylist.Name);
|
_logger.LogInformation("No missing tracks found for {Playlist}", spotifyPlaylistName);
|
||||||
return _responseBuilder.CreateItemsResponse(new List<Song>());
|
return _responseBuilder.CreateItemsResponse(new List<Song>());
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Matching {Count} tracks for {Playlist}",
|
_logger.LogInformation("Matching {Count} tracks for {Playlist}",
|
||||||
missingTracks.Count, matchingPlaylist.Name);
|
missingTracks.Count, spotifyPlaylistName);
|
||||||
|
|
||||||
var matchTasks = missingTracks.Select(async track =>
|
var matchTasks = missingTracks.Select(async track =>
|
||||||
{
|
{
|
||||||
@@ -2064,7 +2028,7 @@ public class JellyfinController : ControllerBase
|
|||||||
await _cache.SetAsync(cacheKey, matchedTracks, TimeSpan.FromHours(1));
|
await _cache.SetAsync(cacheKey, matchedTracks, TimeSpan.FromHours(1));
|
||||||
|
|
||||||
_logger.LogInformation("Matched {Matched}/{Total} tracks for {Playlist}",
|
_logger.LogInformation("Matched {Matched}/{Total} tracks for {Playlist}",
|
||||||
matchedTracks.Count, missingTracks.Count, matchingPlaylist.Name);
|
matchedTracks.Count, missingTracks.Count, spotifyPlaylistName);
|
||||||
|
|
||||||
return _responseBuilder.CreateItemsResponse(matchedTracks);
|
return _responseBuilder.CreateItemsResponse(matchedTracks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,29 +31,9 @@ public class SpotifyImportSettings
|
|||||||
public int SyncWindowHours { get; set; } = 2;
|
public int SyncWindowHours { get; set; } = 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of playlists to inject
|
/// Comma-separated list of Jellyfin playlist IDs to inject
|
||||||
|
/// Example: "4383a46d8bcac3be2ef9385053ea18df,ba50e26c867ec9d57ab2f7bf24cfd6b0"
|
||||||
|
/// Get IDs from Jellyfin playlist URLs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SpotifyPlaylistConfig> Playlists { get; set; } = new();
|
public List<string> PlaylistIds { get; set; } = new();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configuration for a single Spotify playlist
|
|
||||||
/// </summary>
|
|
||||||
public class SpotifyPlaylistConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Display name in Jellyfin (e.g., "Release Radar")
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Playlist name in Spotify Import plugin missing tracks file
|
|
||||||
/// Must match exactly (e.g., "Release Radar")
|
|
||||||
/// </summary>
|
|
||||||
public string SpotifyName { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enable this playlist
|
|
||||||
/// </summary>
|
|
||||||
public bool Enabled { get; set; } = true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,29 +113,23 @@ builder.Services.Configure<SpotifyImportSettings>(options =>
|
|||||||
{
|
{
|
||||||
builder.Configuration.GetSection("SpotifyImport").Bind(options);
|
builder.Configuration.GetSection("SpotifyImport").Bind(options);
|
||||||
|
|
||||||
// Parse SPOTIFY_IMPORT_PLAYLISTS env var (comma-separated) into Playlists array
|
// Parse SPOTIFY_IMPORT_PLAYLIST_IDS env var (comma-separated) into PlaylistIds array
|
||||||
var playlistsEnv = builder.Configuration.GetValue<string>("SpotifyImport:Playlists");
|
var playlistIdsEnv = builder.Configuration.GetValue<string>("SpotifyImport:PlaylistIds");
|
||||||
if (!string.IsNullOrWhiteSpace(playlistsEnv) && options.Playlists.Count == 0)
|
if (!string.IsNullOrWhiteSpace(playlistIdsEnv) && options.PlaylistIds.Count == 0)
|
||||||
{
|
{
|
||||||
options.Playlists = playlistsEnv
|
options.PlaylistIds = playlistIdsEnv
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(name => name.Trim())
|
.Select(id => id.Trim())
|
||||||
.Where(name => !string.IsNullOrEmpty(name))
|
.Where(id => !string.IsNullOrEmpty(id))
|
||||||
.Select(name => new SpotifyPlaylistConfig
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
SpotifyName = name,
|
|
||||||
Enabled = true
|
|
||||||
})
|
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log configuration at startup
|
// Log configuration at startup
|
||||||
Console.WriteLine($"Spotify Import: Enabled={options.Enabled}, SyncHour={options.SyncStartHour}:{options.SyncStartMinute:D2}, WindowHours={options.SyncWindowHours}");
|
Console.WriteLine($"Spotify Import: Enabled={options.Enabled}, SyncHour={options.SyncStartHour}:{options.SyncStartMinute:D2}, WindowHours={options.SyncWindowHours}");
|
||||||
Console.WriteLine($"Spotify Import Playlists: {options.Playlists.Count} configured");
|
Console.WriteLine($"Spotify Import Playlist IDs: {options.PlaylistIds.Count} configured");
|
||||||
foreach (var p in options.Playlists)
|
foreach (var id in options.PlaylistIds)
|
||||||
{
|
{
|
||||||
Console.WriteLine($" - {p.Name} (SpotifyName: {p.SpotifyName}, Enabled: {p.Enabled})");
|
Console.WriteLine($" - {id}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user