diff --git a/.env.example b/.env.example
index dc1b120..94b83ba 100644
--- a/.env.example
+++ b/.env.example
@@ -143,13 +143,6 @@ SPOTIFY_IMPORT_PLAYLISTS=[]
# Enable direct Spotify API access (default: false)
SPOTIFY_API_ENABLED=false
-# Spotify Client ID from https://developer.spotify.com/dashboard
-# Create an app in the Spotify Developer Dashboard to get this
-SPOTIFY_API_CLIENT_ID=
-
-# Spotify Client Secret (optional - only needed for certain OAuth flows)
-SPOTIFY_API_CLIENT_SECRET=
-
# Spotify session cookie (sp_dc) - REQUIRED for editorial playlists
# Editorial playlists (Release Radar, Discover Weekly, etc.) require authentication
# via session cookie because they're not accessible through the official API.
diff --git a/allstarr/Models/Settings/SpotifyApiSettings.cs b/allstarr/Models/Settings/SpotifyApiSettings.cs
index 88f23d4..fcf81b8 100644
--- a/allstarr/Models/Settings/SpotifyApiSettings.cs
+++ b/allstarr/Models/Settings/SpotifyApiSettings.cs
@@ -18,18 +18,6 @@ public class SpotifyApiSettings
///
public bool Enabled { get; set; }
- ///
- /// Spotify Client ID from https://developer.spotify.com/dashboard
- /// Used for OAuth token refresh and API access.
- ///
- public string ClientId { get; set; } = string.Empty;
-
- ///
- /// Spotify Client Secret from https://developer.spotify.com/dashboard
- /// Optional - only needed for certain OAuth flows.
- ///
- public string ClientSecret { get; set; } = string.Empty;
-
///
/// Spotify session cookie (sp_dc).
/// Required for accessing editorial/personalized playlists like Release Radar and Discover Weekly.
diff --git a/allstarr/Program.cs b/allstarr/Program.cs
index 4b0c407..15acbc3 100644
--- a/allstarr/Program.cs
+++ b/allstarr/Program.cs
@@ -473,7 +473,8 @@ else if (musicService == MusicService.SquidWTF)
sp.GetRequiredService>(),
sp.GetRequiredService>(),
sp.GetRequiredService(),
- squidWtfApiUrls));
+ squidWtfApiUrls,
+ sp.GetRequiredService()));
builder.Services.AddSingleton(sp =>
new SquidWTFDownloadService(
sp.GetRequiredService(),
@@ -537,18 +538,6 @@ builder.Services.Configure(options
options.Enabled = enabled.Equals("true", StringComparison.OrdinalIgnoreCase);
}
- var clientId = builder.Configuration.GetValue("SpotifyApi:ClientId");
- if (!string.IsNullOrEmpty(clientId))
- {
- options.ClientId = clientId;
- }
-
- var clientSecret = builder.Configuration.GetValue("SpotifyApi:ClientSecret");
- if (!string.IsNullOrEmpty(clientSecret))
- {
- options.ClientSecret = clientSecret;
- }
-
var sessionCookie = builder.Configuration.GetValue("SpotifyApi:SessionCookie");
if (!string.IsNullOrEmpty(sessionCookie))
{
@@ -576,7 +565,6 @@ builder.Services.Configure(options
// Log configuration (mask sensitive values)
Console.WriteLine($"SpotifyApi Configuration:");
Console.WriteLine($" Enabled: {options.Enabled}");
- Console.WriteLine($" ClientId: {(string.IsNullOrEmpty(options.ClientId) ? "(not set)" : options.ClientId[..8] + "...")}");
Console.WriteLine($" SessionCookie: {(string.IsNullOrEmpty(options.SessionCookie) ? "(not set)" : "***" + options.SessionCookie[^8..])}");
Console.WriteLine($" SessionCookieSetDate: {options.SessionCookieSetDate ?? "(not set)"}");
Console.WriteLine($" CacheDurationMinutes: {options.CacheDurationMinutes}");
diff --git a/allstarr/Services/Deezer/DeezerMetadataService.cs b/allstarr/Services/Deezer/DeezerMetadataService.cs
index 434b6b5..cf04c38 100644
--- a/allstarr/Services/Deezer/DeezerMetadataService.cs
+++ b/allstarr/Services/Deezer/DeezerMetadataService.cs
@@ -3,6 +3,7 @@ using allstarr.Models.Settings;
using allstarr.Models.Download;
using allstarr.Models.Search;
using allstarr.Models.Subsonic;
+using allstarr.Services.Common;
using System.Text.Json;
using Microsoft.Extensions.Options;
@@ -15,12 +16,17 @@ public class DeezerMetadataService : IMusicMetadataService
{
private readonly HttpClient _httpClient;
private readonly SubsonicSettings _settings;
+ private readonly GenreEnrichmentService _genreEnrichment;
private const string BaseUrl = "https://api.deezer.com";
- public DeezerMetadataService(IHttpClientFactory httpClientFactory, IOptions settings)
+ public DeezerMetadataService(
+ IHttpClientFactory httpClientFactory,
+ IOptions settings,
+ GenreEnrichmentService genreEnrichment)
{
_httpClient = httpClientFactory.CreateClient();
_settings = settings.Value;
+ _genreEnrichment = genreEnrichment;
}
public async Task> SearchSongsAsync(string query, int limit = 20)
@@ -203,6 +209,12 @@ public class DeezerMetadataService : IMusicMetadataService
}
}
+ // Enrich with MusicBrainz genres if missing
+ if (string.IsNullOrEmpty(song.Genre))
+ {
+ await _genreEnrichment.EnrichSongGenreAsync(song);
+ }
+
return song;
}
diff --git a/allstarr/Services/Lyrics/LyricsStartupValidator.cs b/allstarr/Services/Lyrics/LyricsStartupValidator.cs
index b45d764..057c77f 100644
--- a/allstarr/Services/Lyrics/LyricsStartupValidator.cs
+++ b/allstarr/Services/Lyrics/LyricsStartupValidator.cs
@@ -167,16 +167,14 @@ public class LyricsStartupValidator : BaseStartupValidator
{
try
{
- if (string.IsNullOrEmpty(_spotifySettings.ClientId))
+ if (!_spotifySettings.Enabled)
{
- WriteStatus("Spotify API", "NOT CONFIGURED", ConsoleColor.Yellow);
- WriteDetail("Set SpotifyApi__ClientId to enable");
+ WriteStatus("Spotify API", "DISABLED", ConsoleColor.Gray);
return true;
}
WriteStatus("Spotify API", "CONFIGURED", ConsoleColor.Green);
- WriteDetail($"Client ID: {_spotifySettings.ClientId.Substring(0, Math.Min(8, _spotifySettings.ClientId.Length))}...");
- WriteDetail("Note: Spotify API is used for track matching, not lyrics");
+ WriteDetail("Note: Spotify API is used for track matching and lyrics");
return true;
}
catch (Exception ex)
diff --git a/allstarr/Services/Qobuz/QobuzMetadataService.cs b/allstarr/Services/Qobuz/QobuzMetadataService.cs
index c718fe7..6cd39b3 100644
--- a/allstarr/Services/Qobuz/QobuzMetadataService.cs
+++ b/allstarr/Services/Qobuz/QobuzMetadataService.cs
@@ -3,6 +3,7 @@ using allstarr.Models.Settings;
using allstarr.Models.Download;
using allstarr.Models.Search;
using allstarr.Models.Subsonic;
+using allstarr.Services.Common;
using System.Text.Json;
using Microsoft.Extensions.Options;
@@ -18,6 +19,7 @@ public class QobuzMetadataService : IMusicMetadataService
private readonly SubsonicSettings _settings;
private readonly QobuzBundleService _bundleService;
private readonly ILogger _logger;
+ private readonly GenreEnrichmentService _genreEnrichment;
private readonly string? _userAuthToken;
private readonly string? _userId;
@@ -28,12 +30,14 @@ public class QobuzMetadataService : IMusicMetadataService
IOptions settings,
IOptions qobuzSettings,
QobuzBundleService bundleService,
- ILogger logger)
+ ILogger logger,
+ GenreEnrichmentService genreEnrichment)
{
_httpClient = httpClientFactory.CreateClient();
_settings = settings.Value;
_bundleService = bundleService;
_logger = logger;
+ _genreEnrichment = genreEnrichment;
var qobuzConfig = qobuzSettings.Value;
_userAuthToken = qobuzConfig.UserAuthToken;
@@ -177,7 +181,15 @@ public class QobuzMetadataService : IMusicMetadataService
if (track.TryGetProperty("error", out _)) return null;
- return ParseQobuzTrackFull(track);
+ var song = ParseQobuzTrackFull(track);
+
+ // Enrich with MusicBrainz genres if missing
+ if (song != null && string.IsNullOrEmpty(song.Genre))
+ {
+ await _genreEnrichment.EnrichSongGenreAsync(song);
+ }
+
+ return song;
}
catch (Exception ex)
{
diff --git a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs
index 6dae3f8..cd69966 100644
--- a/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs
+++ b/allstarr/Services/SquidWTF/SquidWTFMetadataService.cs
@@ -56,6 +56,7 @@ public class SquidWTFMetadataService : IMusicMetadataService
private readonly ILogger _logger;
private readonly RedisCacheService _cache;
private readonly RoundRobinFallbackHelper _fallbackHelper;
+ private readonly GenreEnrichmentService _genreEnrichment;
public SquidWTFMetadataService(
IHttpClientFactory httpClientFactory,
@@ -63,13 +64,15 @@ public class SquidWTFMetadataService : IMusicMetadataService
IOptions squidwtfSettings,
ILogger logger,
RedisCacheService cache,
- List apiUrls)
+ List apiUrls,
+ GenreEnrichmentService genreEnrichment)
{
_httpClient = httpClientFactory.CreateClient();
_settings = settings.Value;
_logger = logger;
_cache = cache;
_fallbackHelper = new RoundRobinFallbackHelper(apiUrls, logger, "SquidWTF");
+ _genreEnrichment = genreEnrichment;
// Set up default headers
_httpClient.DefaultRequestHeaders.Add("User-Agent",
@@ -286,6 +289,12 @@ public class SquidWTFMetadataService : IMusicMetadataService
var song = ParseTidalTrackFull(track);
+ // Enrich with MusicBrainz genres if missing (SquidWTF/Tidal doesn't provide genres)
+ if (string.IsNullOrEmpty(song.Genre))
+ {
+ await _genreEnrichment.EnrichSongGenreAsync(song);
+ }
+
// NOTE: Spotify ID conversion happens during download (in SquidWTFDownloadService)
// This avoids redundant conversions and ensures it's done in parallel with the download
diff --git a/allstarr/appsettings.json b/allstarr/appsettings.json
index f5e6fa6..d3934c8 100644
--- a/allstarr/appsettings.json
+++ b/allstarr/appsettings.json
@@ -61,8 +61,6 @@
},
"SpotifyApi": {
"Enabled": false,
- "ClientId": "",
- "ClientSecret": "",
"SessionCookie": "",
"CacheDurationMinutes": 60,
"RateLimitDelayMs": 100,
diff --git a/docker-compose.yml b/docker-compose.yml
index f4ed98c..6b28456 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -107,8 +107,6 @@ services:
# ===== SPOTIFY DIRECT API (for lyrics, ISRC matching, track ordering) =====
- SpotifyApi__Enabled=${SPOTIFY_API_ENABLED:-false}
- - SpotifyApi__ClientId=${SPOTIFY_API_CLIENT_ID:-}
- - SpotifyApi__ClientSecret=${SPOTIFY_API_CLIENT_SECRET:-}
- SpotifyApi__SessionCookie=${SPOTIFY_API_SESSION_COOKIE:-}
- SpotifyApi__SessionCookieSetDate=${SPOTIFY_API_SESSION_COOKIE_SET_DATE:-}
- SpotifyApi__CacheDurationMinutes=${SPOTIFY_API_CACHE_DURATION_MINUTES:-60}