From 3c211eaac8d717fb4b0c95236eda473bda870cf0 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Mon, 6 Apr 2026 11:27:49 -0700 Subject: [PATCH] Make Spotify a selectable metadata source instead of auto-override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spotify was automatically the primary metadata source whenever authenticated, ignoring the user's fallback dropdown selection. Now: - Dropdown renamed "Fallback Source" → "Primary Source" with Spotify added as an option alongside iTunes, Deezer, Discogs - User's selection drives search results, discovery page, status display - Enhanced search uses selected primary source first, others as tabs - Discovery page filters by selected source's artist IDs - Spotify auth still works for playlists, followed artists, enrichment - Default is 'deezer' — users who want Spotify select it explicitly --- web_server.py | 70 ++++++++++++++++++++++++++++-------------------- webui/index.html | 5 ++-- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/web_server.py b/web_server.py index 597a3a3a..5f9e8adf 100644 --- a/web_server.py +++ b/web_server.py @@ -4424,13 +4424,19 @@ def get_status(): if is_rate_limited or cooldown_remaining > 0: # During rate limit or post-ban cooldown, skip the auth probe entirely. # Probing Spotify here would reset the rate limit timer. - music_source = _get_metadata_fallback_source() # App uses fallback during ban + music_source = _get_metadata_fallback_source() + if music_source == 'spotify': + music_source = 'deezer' # Spotify rate limited — can't use it spotify_response_time = 0 else: spotify_start = time.time() spotify_connected = spotify_client.is_spotify_authenticated() if spotify_client else False spotify_response_time = (time.time() - spotify_start) * 1000 - music_source = 'spotify' if spotify_connected else _get_metadata_fallback_source() + # Use whatever the user configured as primary metadata source + music_source = _get_metadata_fallback_source() + # If user selected spotify but it's not authed, fall back + if music_source == 'spotify' and not spotify_connected: + music_source = 'deezer' _status_cache['spotify'] = { 'connected': True, # Always true — iTunes fallback is always available @@ -5981,7 +5987,7 @@ def test_connection_endpoint(): current_time = time.time() if service == 'spotify': _status_cache['spotify']['connected'] = True - _status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else _get_metadata_fallback_source() + _status_cache['spotify']['source'] = _get_metadata_fallback_source() _status_cache_timestamps['spotify'] = current_time print("✅ Updated Spotify status cache after successful test") elif service in ['plex', 'jellyfin', 'navidrome']: @@ -6031,7 +6037,7 @@ def test_dashboard_connection_endpoint(): current_time = time.time() if service == 'spotify': _status_cache['spotify']['connected'] = True - _status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else _get_metadata_fallback_source() + _status_cache['spotify']['source'] = _get_metadata_fallback_source() _status_cache_timestamps['spotify'] = current_time print("✅ Updated Spotify status cache after successful dashboard test") elif service in ['plex', 'jellyfin', 'navidrome']: @@ -7698,23 +7704,22 @@ def enhanced_search(): hydrabase_worker.enqueue(query, 'albums') hydrabase_worker.enqueue(query, 'artists') - # Search primary source synchronously — use is_spotify_authenticated() - # (is_authenticated() always returns True because fallback is always available) - if spotify_client and spotify_client.is_spotify_authenticated(): - try: - primary_results = _enhanced_search_source(query, spotify_client) - primary_source = "spotify" - except Exception as e: - logger.debug(f"Spotify search failed: {e}") + # Search using the user's configured primary metadata source + fb_source = _get_metadata_fallback_source() + try: + primary_results = _enhanced_search_source(query, _get_metadata_fallback_client()) + primary_source = fb_source + except Exception as e: + logger.debug(f"Primary source ({fb_source}) search failed: {e}") - # If Spotify unavailable, use configured fallback as primary - if primary_results is empty_source: - fb_source = _get_metadata_fallback_source() - try: - primary_results = _enhanced_search_source(query, _get_metadata_fallback_client()) - primary_source = fb_source - except Exception as e: - logger.debug(f"Fallback ({fb_source}) search failed: {e}") + # If primary source failed and it wasn't Spotify, try Spotify as fallback + if primary_results is empty_source and fb_source != 'spotify': + if spotify_client and spotify_client.is_spotify_authenticated(): + try: + primary_results = _enhanced_search_source(query, spotify_client) + primary_source = "spotify" + except Exception as e: + logger.debug(f"Spotify fallback search failed: {e}") # Determine which alternate sources are available (for frontend to fetch async) spotify_available = bool(spotify_client and spotify_client.is_spotify_authenticated()) @@ -32748,16 +32753,22 @@ def _get_deezer_client(): return _deezer_client_instance def _get_metadata_fallback_source(): - """Get the configured metadata fallback source ('itunes', 'deezer', 'discogs', or 'hydrabase').""" + """Get the configured primary metadata source. + Returns 'spotify', 'itunes', 'deezer', 'discogs', or 'hydrabase'.""" try: return config_manager.get('metadata.fallback_source', 'deezer') or 'deezer' except Exception: - return 'itunes' + return 'deezer' def _get_metadata_fallback_client(): - """Get the active metadata fallback client based on settings. - Returns an iTunesClient, DeezerClient, DiscogsClient, or HydrabaseClient instance with identical interfaces.""" + """Get the active metadata client based on settings. + Returns a SpotifyClient, iTunesClient, DeezerClient, DiscogsClient, or HydrabaseClient instance.""" source = _get_metadata_fallback_source() + if source == 'spotify': + if spotify_client and spotify_client.is_spotify_authenticated(): + return spotify_client + # Spotify selected but not authed — fall back to deezer + return _get_deezer_client() if source == 'deezer': return _get_deezer_client() if source == 'discogs': @@ -32765,7 +32776,6 @@ def _get_metadata_fallback_client(): if token: from core.discogs_client import DiscogsClient return DiscogsClient(token=token) - # No token — fall back to iTunes from core.itunes_client import iTunesClient return iTunesClient() if source == 'hydrabase': @@ -39499,11 +39509,13 @@ metadata_update_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix= def _get_active_discovery_source(): """ Determine which music source is active for discovery. - Returns 'spotify' if Spotify is authenticated, otherwise the configured fallback. + Returns the user's configured primary metadata source. + If the selected source requires auth and isn't available, falls back. """ - if spotify_client and spotify_client.is_spotify_authenticated(): - return 'spotify' - return _get_metadata_fallback_source() + source = _get_metadata_fallback_source() + if source == 'spotify' and not (spotify_client and spotify_client.is_spotify_authenticated()): + return 'deezer' + return source @app.route('/api/discover/hero', methods=['GET']) diff --git a/webui/index.html b/webui/index.html index 1bd1e518..90dcba25 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4235,15 +4235,16 @@