Fix manual match storing iTunes/Deezer IDs in Spotify ID columns (#213)
When Spotify falls back to iTunes/Deezer (auth failure, rate limit, API error), the manual match modals were storing numeric fallback IDs in spotify_*_id columns, breaking Spotify links and metadata fetching. Fix detects the actual provider by inspecting result IDs (alphanumeric = Spotify, numeric = fallback) rather than checking auth status, which can be misleading during rate limit bans. The frontend now passes the real provider to the storage endpoint so IDs land in the correct column.
This commit is contained in:
parent
3f866ebf5e
commit
213736e168
3 changed files with 41 additions and 10 deletions
|
|
@ -12304,6 +12304,15 @@ def library_search_service():
|
|||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
def _detect_provider(items, client):
|
||||
"""Detect actual provider from result IDs. Spotify IDs are alphanumeric;
|
||||
iTunes/Deezer IDs are purely numeric. If the results have numeric IDs,
|
||||
they came from the fallback source, not Spotify."""
|
||||
if items and str(items[0].id).isdigit():
|
||||
return client._fallback_source
|
||||
return 'spotify'
|
||||
|
||||
|
||||
def _search_service(service, entity_type, query):
|
||||
"""Search a service and return normalized results."""
|
||||
import requests as req_lib
|
||||
|
|
@ -12314,13 +12323,18 @@ def _search_service(service, entity_type, query):
|
|||
client = spotify_enrichment_worker.client
|
||||
if entity_type == 'artist':
|
||||
items = client.search_artists(query, limit=8)
|
||||
return [{'id': a.id, 'name': a.name, 'image': a.image_url, 'extra': ', '.join(a.genres[:3]) if a.genres else ''} for a in items]
|
||||
# Detect actual provider from result IDs — Spotify IDs are alphanumeric,
|
||||
# iTunes/Deezer IDs are purely numeric. Prevents storing wrong IDs.
|
||||
provider = _detect_provider(items, client)
|
||||
return [{'id': a.id, 'name': a.name, 'image': a.image_url, 'extra': ', '.join(a.genres[:3]) if a.genres else '', 'provider': provider} for a in items]
|
||||
elif entity_type == 'album':
|
||||
items = client.search_albums(query, limit=8)
|
||||
return [{'id': a.id, 'name': a.name, 'image': a.image_url, 'extra': f"{', '.join(a.artists)} · {a.release_date or ''}"} for a in items]
|
||||
provider = _detect_provider(items, client)
|
||||
return [{'id': a.id, 'name': a.name, 'image': a.image_url, 'extra': f"{', '.join(a.artists)} · {a.release_date or ''}", 'provider': provider} for a in items]
|
||||
elif entity_type == 'track':
|
||||
items = client.search_tracks(query, limit=8)
|
||||
return [{'id': t.id, 'name': t.name, 'image': t.image_url, 'extra': f"{', '.join(t.artists)} · {t.album or ''}"} for t in items]
|
||||
provider = _detect_provider(items, client)
|
||||
return [{'id': t.id, 'name': t.name, 'image': t.image_url, 'extra': f"{', '.join(t.artists)} · {t.album or ''}", 'provider': provider} for t in items]
|
||||
|
||||
elif service == 'itunes':
|
||||
if not itunes_enrichment_worker or not itunes_enrichment_worker.client:
|
||||
|
|
@ -13186,17 +13200,19 @@ def search_match():
|
|||
# Search for artists
|
||||
if use_hydrabase:
|
||||
artist_matches = hydrabase_client.search_artists(query, limit=8)
|
||||
provider = 'hydrabase'
|
||||
else:
|
||||
artist_matches = spotify_client.search_artists(query, limit=8)
|
||||
provider = _detect_provider(artist_matches, spotify_client)
|
||||
results = []
|
||||
|
||||
|
||||
for artist in artist_matches:
|
||||
# Calculate confidence based on search similarity
|
||||
confidence = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(query),
|
||||
matching_engine.normalize_string(artist.name)
|
||||
)
|
||||
|
||||
|
||||
results.append({
|
||||
"artist": {
|
||||
"id": artist.id,
|
||||
|
|
@ -13208,8 +13224,8 @@ def search_match():
|
|||
},
|
||||
"confidence": confidence
|
||||
})
|
||||
|
||||
return jsonify({"results": results})
|
||||
|
||||
return jsonify({"results": results, "provider": provider})
|
||||
|
||||
elif context == 'album':
|
||||
# Search for albums by specific artist
|
||||
|
|
@ -13218,11 +13234,13 @@ def search_match():
|
|||
if use_hydrabase:
|
||||
# Hydrabase: search albums by query directly
|
||||
album_matches = hydrabase_client.search_albums(query, limit=20)
|
||||
provider = 'hydrabase'
|
||||
else:
|
||||
if not artist_id:
|
||||
return jsonify({"error": "Artist ID required for album search"}), 400
|
||||
# Get artist's albums and filter by query
|
||||
album_matches = spotify_client.get_artist_albums(artist_id)
|
||||
provider = _detect_provider(album_matches, spotify_client)
|
||||
|
||||
results = []
|
||||
for album in album_matches:
|
||||
|
|
@ -13248,7 +13266,7 @@ def search_match():
|
|||
|
||||
# Sort by confidence
|
||||
results.sort(key=lambda x: x['confidence'], reverse=True)
|
||||
return jsonify({"results": results[:8]})
|
||||
return jsonify({"results": results[:8], "provider": provider})
|
||||
|
||||
else:
|
||||
return jsonify({"error": "Invalid context. Must be 'artist' or 'album'"}), 400
|
||||
|
|
@ -18981,6 +18999,16 @@ def get_version_info():
|
|||
"title": "What's New in SoulSync",
|
||||
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
|
||||
"sections": [
|
||||
{
|
||||
"title": "🔧 Fix Spotify Manual Match Storing Wrong IDs",
|
||||
"description": "Manual match modals no longer store iTunes/Deezer IDs in Spotify ID columns",
|
||||
"features": [
|
||||
"• Detects actual provider from result IDs — Spotify IDs are alphanumeric, iTunes/Deezer are numeric",
|
||||
"• Match button now stores IDs in the correct service column (itunes_artist_id vs spotify_artist_id)",
|
||||
"• Results show provider label when falling back (e.g. 'ID: 312095 (itunes)')",
|
||||
"• Fixes broken Spotify links on artist pages caused by stored iTunes IDs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "🛡️ Spotify Enrichment Daily Budget",
|
||||
"description": "The background enrichment worker now caps itself at 3,000 items per day to prevent rate limit bans",
|
||||
|
|
|
|||
|
|
@ -3403,6 +3403,7 @@ function closeHelperSearch() {
|
|||
const WHATS_NEW = {
|
||||
'2.1': [
|
||||
// Newest features first
|
||||
{ title: 'Fix Wrong Spotify IDs', desc: 'Manual match no longer stores iTunes IDs as Spotify IDs — detects actual provider from results' },
|
||||
{ title: 'Spotify Enrichment Budget', desc: 'Background enrichment worker caps at 3,000 items/day to prevent rate limit bans — resets at midnight' },
|
||||
{ title: 'Per-Artist Library Sync', desc: 'Validate files and clean stale entries per artist from the enhanced library view', page: 'library', selector: '.library-controls' },
|
||||
{ title: 'Collaborative Album Handling', desc: 'Smart folder naming for multi-artist albums — uses first listed artist', page: 'settings', selector: '#collab-artist-mode' },
|
||||
|
|
|
|||
|
|
@ -18530,6 +18530,7 @@ async function performArtistSearch(query) {
|
|||
|
||||
const data = await response.json();
|
||||
console.log('Manual search response:', data);
|
||||
if (data.provider) currentMatchingData.provider = data.provider;
|
||||
if (data.results) {
|
||||
console.log('Results array:', data.results);
|
||||
renderArtistSearchResults(data.results);
|
||||
|
|
@ -42772,14 +42773,15 @@ async function doManualMatchSearch(service, entityType, query, container, entity
|
|||
}
|
||||
const idLine = document.createElement('div');
|
||||
idLine.className = 'enhanced-match-result-id';
|
||||
idLine.textContent = `ID: ${result.id}`;
|
||||
const providerLabel = result.provider && result.provider !== service ? ` (${result.provider})` : '';
|
||||
idLine.textContent = `ID: ${result.id}${providerLabel}`;
|
||||
info.appendChild(idLine);
|
||||
row.appendChild(info);
|
||||
|
||||
const matchBtn = document.createElement('button');
|
||||
matchBtn.className = 'enhanced-meta-save-btn';
|
||||
matchBtn.textContent = 'Match';
|
||||
matchBtn.onclick = () => applyManualMatch(entityType, entityId, service, result.id, artistId);
|
||||
matchBtn.onclick = () => applyManualMatch(entityType, entityId, result.provider || service, result.id, artistId);
|
||||
row.appendChild(matchBtn);
|
||||
|
||||
container.appendChild(row);
|
||||
|
|
|
|||
Loading…
Reference in a new issue