#798: Spotify Free UI — enable toggle + source availability
Surfaces the opt-in Spotify Free source so it's usable end-to-end: - Settings: 'Enable Spotify Free (no credentials)' toggle that saves metadata.spotify_free (load + save wired). Clear best-effort/limitations note. - config-status: adds spotify.metadata_available (configured OR free-available), keeping the configured flag = has-credentials so the Connections indicator stays honest. Search source picker shows Spotify when metadata_available. - status payload: adds spotify.metadata_available; the Settings primary-source selector now allows picking Spotify when authed OR free-available. Verified gate composition: OFF by default (no surprise scraping); ON + no auth + installed -> available & serving; AUTHED -> official always wins (free never runs); missing package -> gracefully unavailable. JS + integrity + 111 tests green.
This commit is contained in:
parent
0eff9e3708
commit
2667eaec87
4 changed files with 45 additions and 6 deletions
|
|
@ -2318,9 +2318,19 @@ def get_status():
|
|||
if t.get('status') in ('downloading', 'searching', 'post_processing', 'queued', 'pending'):
|
||||
active_dl_count += 1
|
||||
|
||||
# Spotify Free: tell the UI whether Spotify metadata is available even
|
||||
# without auth (so the Settings source selector can offer it).
|
||||
spotify_status = dict(metadata_status['spotify'])
|
||||
try:
|
||||
spotify_status['metadata_available'] = bool(
|
||||
spotify_client and spotify_client.is_spotify_metadata_available()
|
||||
)
|
||||
except Exception:
|
||||
spotify_status['metadata_available'] = bool(spotify_status.get('authenticated'))
|
||||
|
||||
status_data = {
|
||||
'metadata_source': metadata_status['metadata_source'],
|
||||
'spotify': metadata_status['spotify'],
|
||||
'spotify': spotify_status,
|
||||
'media_server': _status_cache['media_server'],
|
||||
'soulseek': soulseek_data,
|
||||
'active_media_server': active_server,
|
||||
|
|
@ -3665,10 +3675,23 @@ def settings_config_status_endpoint():
|
|||
Drives the green/yellow header gradient. No API calls — just config reads.
|
||||
"""
|
||||
try:
|
||||
return jsonify({
|
||||
result = {
|
||||
service: {'configured': _is_service_configured(service)}
|
||||
for service in SERVICE_CONFIG_REGISTRY
|
||||
})
|
||||
}
|
||||
# Spotify Free: Spotify metadata can be available without credentials
|
||||
# (opt-in no-creds source). Surface that separately so the search source
|
||||
# picker offers Spotify, while `configured` (the Connections indicator)
|
||||
# keeps meaning "has client credentials".
|
||||
if 'spotify' in result:
|
||||
try:
|
||||
meta_avail = bool(spotify_client and spotify_client.is_spotify_metadata_available())
|
||||
except Exception:
|
||||
meta_avail = False
|
||||
result['spotify']['metadata_available'] = (
|
||||
result['spotify']['configured'] or meta_avail
|
||||
)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
logger.error(f"config-status error: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
|
|
|||
|
|
@ -3844,8 +3844,15 @@
|
|||
<option value="musicbrainz">MusicBrainz</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="metadata-spotify-free">
|
||||
<span>Enable Spotify Free (no credentials)</span>
|
||||
</label>
|
||||
<div class="callback-help">Fetch Spotify metadata without API credentials, for when you can't or don't want to connect Spotify. Unofficial & best-effort (it can break if Spotify changes), and it can't search albums by name or access your library — only used when Spotify isn't authenticated.</div>
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Choose the primary source for artist, album, and track metadata. Spotify can only be selected while an active Spotify session exists. Discogs requires a personal token. MusicBrainz is always available but rate-limited to 1 req/sec.</div>
|
||||
<div class="callback-help">Choose the primary source for artist, album, and track metadata. Spotify needs an active session — or enable Spotify Free above. Discogs requires a personal token. MusicBrainz is always available but rate-limited to 1 req/sec.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ function syncMetadataSourceSelection(source) {
|
|||
|
||||
function _isMetadataSourceSelectable(source) {
|
||||
if (source === 'spotify') {
|
||||
return _lastStatusPayload?.spotify?.authenticated === true;
|
||||
// Selectable with real auth OR when Spotify Free (no-creds) is available.
|
||||
return _lastStatusPayload?.spotify?.authenticated === true
|
||||
|| _lastStatusPayload?.spotify?.metadata_available === true;
|
||||
}
|
||||
if (source === 'discogs') {
|
||||
const token = document.getElementById('discogs-token');
|
||||
|
|
@ -1045,6 +1047,8 @@ async function loadSettingsData() {
|
|||
|
||||
// Populate Metadata source setting
|
||||
document.getElementById('metadata-fallback-source').value = settings.metadata?.fallback_source || 'deezer';
|
||||
const spotifyFreeToggle = document.getElementById('metadata-spotify-free');
|
||||
if (spotifyFreeToggle) spotifyFreeToggle.checked = settings.metadata?.spotify_free === true;
|
||||
|
||||
// Populate Hydrabase settings
|
||||
const hbConfig = settings.hydrabase || {};
|
||||
|
|
@ -2842,7 +2846,8 @@ async function saveSettings(quiet = false) {
|
|||
token: document.getElementById('discogs-token').value,
|
||||
},
|
||||
metadata: {
|
||||
fallback_source: metadataSource
|
||||
fallback_source: metadataSource,
|
||||
spotify_free: document.getElementById('metadata-spotify-free')?.checked === true
|
||||
},
|
||||
hydrabase: {
|
||||
url: document.getElementById('hydrabase-url').value,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ async function fetchSourceConfiguredMap() {
|
|||
for (const src of SOURCE_ORDER) {
|
||||
if (_ALWAYS_CONFIGURED_SOURCES.has(src)) {
|
||||
map[src] = true;
|
||||
} else if (src === 'spotify') {
|
||||
// Spotify Free: available without credentials when the
|
||||
// opt-in no-creds source is on (metadata_available).
|
||||
map[src] = !!(data[src] && (data[src].configured || data[src].metadata_available));
|
||||
} else {
|
||||
map[src] = !!(data[src] && data[src].configured);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue