diff --git a/core/metadata/registry.py b/core/metadata/registry.py index 3299d55a..3aab4468 100644 --- a/core/metadata/registry.py +++ b/core/metadata/registry.py @@ -415,6 +415,14 @@ def get_primary_source_status( ) if source == "spotify": connected = bool(client and client.is_spotify_authenticated()) + # No-auth composite (fallback_source='spotify' + metadata.spotify_free): + # works without authentication, so treat the free path's availability + # as "connected" too. + if not connected and _get_config_value("metadata.spotify_free", False): + try: + connected = bool(client and client.is_spotify_metadata_available()) + except Exception: + connected = False elif source == "hydrabase": connected = bool(client and (client.is_connected() if hasattr(client, "is_connected") else client.is_authenticated())) elif client is not None and hasattr(client, "is_authenticated"): @@ -424,8 +432,17 @@ def get_primary_source_status( except Exception: connected = False + # Report the composite-aware source for DISPLAY: "Spotify (no auth)" is + # stored as fallback_source='spotify' + metadata.spotify_free=true. The raw + # 'spotify' is kept above for the connected/auth checks; consumers that + # label the source (sidebar, dashboard card) get 'spotify_free' so they + # stop mislabeling no-auth as plain Spotify. + display_source = source + if source == "spotify" and _get_config_value("metadata.spotify_free", False): + display_source = "spotify_free" + return { - "source": source, + "source": display_source, "connected": connected, "response_time": round((time.time() - started) * 1000, 1), } diff --git a/webui/static/settings.js b/webui/static/settings.js index 9cc9e341..99140053 100644 --- a/webui/static/settings.js +++ b/webui/static/settings.js @@ -3438,6 +3438,9 @@ async function revokeApiKey(keyId, label) { // Dashboard-specific test functions that create activity items async function testDashboardConnection(service) { + // 'spotify_free' is a display-only label for the no-auth composite; the real + // service to test is 'spotify'. + if (service === 'spotify_free') service = 'spotify'; try { showLoadingOverlay(`Testing ${service} service...`); diff --git a/webui/static/shared-helpers.js b/webui/static/shared-helpers.js index 5e1c1e62..f5a0b958 100644 --- a/webui/static/shared-helpers.js +++ b/webui/static/shared-helpers.js @@ -42,6 +42,11 @@ const SOURCE_LABELS = { logo: 'https://storage.googleapis.com/pr-newsroom-wp/1/2023/05/Spotify_Primary_Logo_RGB_Green.png', tabClass: 'enh-tab-spotify', badgeClass: 'enh-badge-spotify', }, + spotify_free: { + text: 'Spotify (no auth)', icon: '🎵', + logo: 'https://storage.googleapis.com/pr-newsroom-wp/1/2023/05/Spotify_Primary_Logo_RGB_Green.png', + tabClass: 'enh-tab-spotify', badgeClass: 'enh-badge-spotify', + }, itunes: { text: 'Apple Music', icon: '🍎', logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/ITunes_logo.svg/960px-ITunes_logo.svg.png', @@ -3633,6 +3638,7 @@ function getMetadataSourceLabel(source) { if (source === 'hydrabase') return 'Hydrabase'; if (source === 'itunes') return 'iTunes'; if (source === 'musicbrainz') return 'MusicBrainz'; + if (source === 'spotify_free') return 'Spotify (no auth)'; if (source === 'spotify') return 'Spotify'; return 'Unmapped'; } @@ -3641,9 +3647,12 @@ function getMetadataSourcePresentation(metadataStatus, spotifyStatus) { const source = metadataStatus?.source; const sourceLabel = getMetadataSourceLabel(source); const connected = metadataStatus?.connected === true; - const sessionActive = spotifyStatus?.authenticated === true || (source === 'spotify' && connected); - const rateLimited = !!(source === 'spotify' && spotifyStatus?.rate_limited && spotifyStatus?.rate_limit); - const cooldown = !!(source === 'spotify' && spotifyStatus?.post_ban_cooldown > 0); + // 'spotify_free' (the no-auth composite) is part of the Spotify family for + // session/rate-limit/cooldown display. + const spotifyFamily = (source === 'spotify' || source === 'spotify_free'); + const sessionActive = spotifyStatus?.authenticated === true || (spotifyFamily && connected); + const rateLimited = !!(spotifyFamily && spotifyStatus?.rate_limited && spotifyStatus?.rate_limit); + const cooldown = !!(spotifyFamily && spotifyStatus?.post_ban_cooldown > 0); if (rateLimited) { const remaining = spotifyStatus.rate_limit?.remaining_seconds || 0; @@ -3670,7 +3679,7 @@ function getMetadataSourcePresentation(metadataStatus, spotifyStatus) { if (source) { return { statusClass: connected ? 'connected' : 'disconnected', - statusText: connected ? (source === 'spotify' ? `Connected (${metadataStatus?.response_time}ms)` : sourceLabel) : 'Disconnected', + statusText: connected ? (spotifyFamily ? `Connected (${metadataStatus?.response_time}ms)` : sourceLabel) : 'Disconnected', dotClass: connected ? 'connected' : 'disconnected', dotTitle: connected ? sourceLabel : 'Disconnected', sessionActive