diff --git a/allstarr/Controllers/AdminController.cs b/allstarr/Controllers/AdminController.cs index f13416b..4883cfd 100644 --- a/allstarr/Controllers/AdminController.cs +++ b/allstarr/Controllers/AdminController.cs @@ -24,6 +24,9 @@ public class AdminController : ControllerBase private readonly SpotifyApiSettings _spotifyApiSettings; private readonly SpotifyImportSettings _spotifyImportSettings; private readonly JellyfinSettings _jellyfinSettings; + private readonly DeezerSettings _deezerSettings; + private readonly QobuzSettings _qobuzSettings; + private readonly SquidWTFSettings _squidWtfSettings; private readonly SpotifyApiClient _spotifyClient; private readonly SpotifyPlaylistFetcher _playlistFetcher; private readonly RedisCacheService _cache; @@ -36,6 +39,9 @@ public class AdminController : ControllerBase IOptions spotifyApiSettings, IOptions spotifyImportSettings, IOptions jellyfinSettings, + IOptions deezerSettings, + IOptions qobuzSettings, + IOptions squidWtfSettings, SpotifyApiClient spotifyClient, SpotifyPlaylistFetcher playlistFetcher, RedisCacheService cache) @@ -45,6 +51,9 @@ public class AdminController : ControllerBase _spotifyApiSettings = spotifyApiSettings.Value; _spotifyImportSettings = spotifyImportSettings.Value; _jellyfinSettings = jellyfinSettings.Value; + _deezerSettings = deezerSettings.Value; + _qobuzSettings = qobuzSettings.Value; + _squidWtfSettings = squidWtfSettings.Value; _spotifyClient = spotifyClient; _playlistFetcher = playlistFetcher; _cache = cache; @@ -63,11 +72,24 @@ public class AdminController : ControllerBase { try { - var (success, userId, displayName) = await _spotifyClient.GetCurrentUserAsync(); - if (success) + // First check if we can get a valid (non-anonymous) token + var token = await _spotifyClient.GetWebAccessTokenAsync(); + if (!string.IsNullOrEmpty(token)) { - spotifyAuthStatus = "authenticated"; - spotifyUser = displayName ?? userId; + // Try to get user info (may fail even with valid token) + var (success, userId, displayName) = await _spotifyClient.GetCurrentUserAsync(); + if (success) + { + spotifyAuthStatus = "authenticated"; + spotifyUser = displayName ?? userId; + } + else + { + // Token is valid but /me endpoint failed - still consider it authenticated + // (the token being non-anonymous is the real indicator) + spotifyAuthStatus = "authenticated"; + spotifyUser = "(profile not accessible)"; + } } else { @@ -95,6 +117,7 @@ public class AdminController : ControllerBase authStatus = spotifyAuthStatus, user = spotifyUser, hasCookie = !string.IsNullOrEmpty(_spotifyApiSettings.SessionCookie), + cookieSetDate = _spotifyApiSettings.SessionCookieSetDate, cacheDurationMinutes = _spotifyApiSettings.CacheDurationMinutes, preferIsrcMatching = _spotifyApiSettings.PreferIsrcMatching }, @@ -104,6 +127,20 @@ public class AdminController : ControllerBase syncTime = $"{_spotifyImportSettings.SyncStartHour:D2}:{_spotifyImportSettings.SyncStartMinute:D2}", syncWindowHours = _spotifyImportSettings.SyncWindowHours, playlistCount = _spotifyImportSettings.Playlists.Count + }, + deezer = new + { + hasArl = !string.IsNullOrEmpty(_deezerSettings.Arl), + quality = _deezerSettings.Quality ?? "FLAC" + }, + qobuz = new + { + hasToken = !string.IsNullOrEmpty(_qobuzSettings.UserAuthToken), + quality = _qobuzSettings.Quality ?? "FLAC" + }, + squidWtf = new + { + quality = _squidWtfSettings.Quality ?? "LOSSLESS" } }); } @@ -214,8 +251,8 @@ public class AdminController : ControllerBase spotifyApi = new { enabled = _spotifyApiSettings.Enabled, - clientId = MaskValue(_spotifyApiSettings.ClientId), sessionCookie = MaskValue(_spotifyApiSettings.SessionCookie, showLast: 8), + sessionCookieSetDate = _spotifyApiSettings.SessionCookieSetDate, cacheDurationMinutes = _spotifyApiSettings.CacheDurationMinutes, rateLimitDelayMs = _spotifyApiSettings.RateLimitDelayMs, preferIsrcMatching = _spotifyApiSettings.PreferIsrcMatching @@ -238,6 +275,22 @@ public class AdminController : ControllerBase url = _jellyfinSettings.Url, apiKey = MaskValue(_jellyfinSettings.ApiKey), libraryId = _jellyfinSettings.LibraryId + }, + deezer = new + { + arl = MaskValue(_deezerSettings.Arl, showLast: 8), + arlFallback = MaskValue(_deezerSettings.ArlFallback, showLast: 8), + quality = _deezerSettings.Quality ?? "FLAC" + }, + qobuz = new + { + userAuthToken = MaskValue(_qobuzSettings.UserAuthToken, showLast: 8), + userId = _qobuzSettings.UserId, + quality = _qobuzSettings.Quality ?? "FLAC" + }, + squidWtf = new + { + quality = _squidWtfSettings.Quality ?? "LOSSLESS" } }); } @@ -291,6 +344,16 @@ public class AdminController : ControllerBase envContent[key] = value; appliedUpdates.Add(key); _logger.LogInformation(" Setting {Key}", key); + + // Auto-set cookie date when Spotify session cookie is updated + if (key == "SPOTIFY_API_SESSION_COOKIE" && !string.IsNullOrEmpty(value)) + { + var dateKey = "SPOTIFY_API_SESSION_COOKIE_SET_DATE"; + var dateValue = DateTime.UtcNow.ToString("o"); // ISO 8601 format + envContent[dateKey] = dateValue; + appliedUpdates.Add(dateKey); + _logger.LogInformation(" Auto-setting {Key} to {Value}", dateKey, dateValue); + } } // Write back to .env file diff --git a/allstarr/Models/Settings/SpotifyApiSettings.cs b/allstarr/Models/Settings/SpotifyApiSettings.cs index 6e99d66..2dce551 100644 --- a/allstarr/Models/Settings/SpotifyApiSettings.cs +++ b/allstarr/Models/Settings/SpotifyApiSettings.cs @@ -63,4 +63,10 @@ public class SpotifyApiSettings /// Default: true /// public bool PreferIsrcMatching { get; set; } = true; + + /// + /// ISO date string of when the session cookie was last set/updated. + /// Used to track cookie age and warn when it's approaching expiration (~1 year). + /// + public string? SessionCookieSetDate { get; set; } } diff --git a/allstarr/Services/Spotify/SpotifyApiClient.cs b/allstarr/Services/Spotify/SpotifyApiClient.cs index dff2f8a..7158cef 100644 --- a/allstarr/Services/Spotify/SpotifyApiClient.cs +++ b/allstarr/Services/Spotify/SpotifyApiClient.cs @@ -622,6 +622,8 @@ public class SpotifyApiClient : IDisposable if (!response.IsSuccessStatusCode) { + var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); + _logger.LogWarning("Spotify /me endpoint returned {StatusCode}: {Body}", response.StatusCode, errorBody); return (false, null, null); } diff --git a/allstarr/Services/Spotify/SpotifyPlaylistFetcher.cs b/allstarr/Services/Spotify/SpotifyPlaylistFetcher.cs index 5f6ef34..109d4d7 100644 --- a/allstarr/Services/Spotify/SpotifyPlaylistFetcher.cs +++ b/allstarr/Services/Spotify/SpotifyPlaylistFetcher.cs @@ -210,18 +210,28 @@ public class SpotifyPlaylistFetcher : BackgroundService return; } - // Verify authentication + // Verify we can get an access token (the most reliable auth check) _logger.LogInformation("Attempting Spotify authentication..."); - var (success, userId, displayName) = await _spotifyClient.GetCurrentUserAsync(stoppingToken); - if (!success) + var token = await _spotifyClient.GetWebAccessTokenAsync(stoppingToken); + if (string.IsNullOrEmpty(token)) { - _logger.LogError("Failed to authenticate with Spotify - check session cookie"); + _logger.LogError("Failed to get Spotify access token - check session cookie"); _logger.LogInformation("========================================"); return; } + // Try to get user info (may fail even with valid token due to scope limitations) + var (gotUser, userId, displayName) = await _spotifyClient.GetCurrentUserAsync(stoppingToken); + _logger.LogInformation("Spotify API ENABLED"); - _logger.LogInformation("Authenticated as: {DisplayName} ({UserId})", displayName, userId); + if (gotUser) + { + _logger.LogInformation("Authenticated as: {DisplayName} ({UserId})", displayName, userId); + } + else + { + _logger.LogInformation("Authenticated (user profile not accessible, but token is valid)"); + } _logger.LogInformation("Cache duration: {Minutes} minutes", _spotifyApiSettings.CacheDurationMinutes); _logger.LogInformation("ISRC matching: {Enabled}", _spotifyApiSettings.PreferIsrcMatching ? "enabled" : "disabled"); _logger.LogInformation("Configured Playlists: {Count}", _spotifyImportSettings.Playlists.Count); diff --git a/allstarr/wwwroot/index.html b/allstarr/wwwroot/index.html index 985dc55..8723f0b 100644 --- a/allstarr/wwwroot/index.html +++ b/allstarr/wwwroot/index.html @@ -473,6 +473,10 @@ User - +
+ Cookie Age + - +
Cache Duration - @@ -552,23 +556,69 @@
API Enabled - +
- Client ID - - -
-
- Session Cookie + Session Cookie (sp_dc) - - + +
+
+ Cookie Age + -
Cache Duration - +
ISRC Matching - + +
+
+ + +
+

Deezer Settings

+
+
+ ARL Token + - + +
+
+ Quality + - + +
+
+
+ +
+

SquidWTF / Tidal Settings

+
+
+ Quality + - + +
+
+
+ +
+

Qobuz Settings

+
+
+ User Auth Token + - + +
+
+ Quality + - +
@@ -579,14 +629,33 @@
URL - +
API Key - +
Library ID - + +
+ + + +
+

Sync Schedule

+
+
+ Sync Start Time + - + +
+
+ Sync Window + - +
@@ -596,7 +665,10 @@

These actions can affect your data. Use with caution.

- +
+ + +
@@ -627,20 +699,20 @@ - -