Fix all watchlist Discogs gaps

- Add discogs_artist_id to ALL watchlist WHERE clauses + bind params
- Fix artistPrimaryId to include discogs_artist_id
- Fix View Discography — add Discogs source branch
- Fix openWatchlistArtistDetailView — destructure discogs_artist_id
  from response (was causing ReferenceError)
- Discogs source/provider badges + CSS + image fetch + ID resolution
This commit is contained in:
Broque Thomas 2026-04-02 21:33:41 -07:00
parent f51a8a9ee9
commit 55f7e174d8
3 changed files with 75 additions and 28 deletions

View file

@ -6514,7 +6514,7 @@ class MusicDatabase:
return False return False
def remove_artist_from_watchlist(self, artist_id: str, profile_id: int = 1) -> bool: def remove_artist_from_watchlist(self, artist_id: str, profile_id: int = 1) -> bool:
"""Remove an artist from the watchlist (checks Spotify, iTunes, and Deezer IDs)""" """Remove an artist from the watchlist (checks Spotify, iTunes, Deezer, and Discogs IDs)"""
try: try:
with self._get_connection() as conn: with self._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@ -6522,15 +6522,15 @@ class MusicDatabase:
# Get artist name for logging (check all ID columns) # Get artist name for logging (check all ID columns)
cursor.execute(""" cursor.execute("""
SELECT artist_name FROM watchlist_artists SELECT artist_name FROM watchlist_artists
WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ?) AND profile_id = ? WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?) AND profile_id = ?
""", (artist_id, artist_id, artist_id, profile_id)) """, (artist_id, artist_id, artist_id, artist_id, profile_id))
result = cursor.fetchone() result = cursor.fetchone()
artist_name = result['artist_name'] if result else "Unknown" artist_name = result['artist_name'] if result else "Unknown"
cursor.execute(""" cursor.execute("""
DELETE FROM watchlist_artists DELETE FROM watchlist_artists
WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ?) AND profile_id = ? WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?) AND profile_id = ?
""", (artist_id, artist_id, artist_id, profile_id)) """, (artist_id, artist_id, artist_id, artist_id, profile_id))
if cursor.rowcount > 0: if cursor.rowcount > 0:
conn.commit() conn.commit()
@ -6545,7 +6545,7 @@ class MusicDatabase:
return False return False
def is_artist_in_watchlist(self, artist_id: str, profile_id: int = 1, artist_name: str = None) -> bool: def is_artist_in_watchlist(self, artist_id: str, profile_id: int = 1, artist_name: str = None) -> bool:
"""Check if an artist is currently in the watchlist (checks Spotify, iTunes, Deezer IDs and name)""" """Check if an artist is currently in the watchlist (checks Spotify, iTunes, Deezer, Discogs IDs and name)"""
try: try:
with self._get_connection() as conn: with self._get_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@ -6554,15 +6554,15 @@ class MusicDatabase:
if artist_name: if artist_name:
cursor.execute(""" cursor.execute("""
SELECT 1 FROM watchlist_artists SELECT 1 FROM watchlist_artists
WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR LOWER(artist_name) = LOWER(?)) AND profile_id = ? WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ? OR LOWER(artist_name) = LOWER(?)) AND profile_id = ?
LIMIT 1 LIMIT 1
""", (artist_id, artist_id, artist_id, artist_name, profile_id)) """, (artist_id, artist_id, artist_id, artist_id, artist_name, profile_id))
else: else:
cursor.execute(""" cursor.execute("""
SELECT 1 FROM watchlist_artists SELECT 1 FROM watchlist_artists
WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ?) AND profile_id = ? WHERE (spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?) AND profile_id = ?
LIMIT 1 LIMIT 1
""", (artist_id, artist_id, artist_id, profile_id)) """, (artist_id, artist_id, artist_id, artist_id, profile_id))
result = cursor.fetchone() result = cursor.fetchone()
return result is not None return result is not None
@ -6833,8 +6833,8 @@ class MusicDatabase:
cursor.execute(""" cursor.execute("""
UPDATE watchlist_artists UPDATE watchlist_artists
SET image_url = ?, updated_at = CURRENT_TIMESTAMP SET image_url = ?, updated_at = CURRENT_TIMESTAMP
WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?
""", (image_url, artist_id, artist_id, artist_id)) """, (image_url, artist_id, artist_id, artist_id, artist_id))
conn.commit() conn.commit()
return cursor.rowcount > 0 return cursor.rowcount > 0

View file

@ -36599,10 +36599,47 @@ def add_to_watchlist():
return jsonify({"success": False, "error": "Missing artist_id or artist_name"}), 400 return jsonify({"success": False, "error": "Missing artist_id or artist_name"}), 400
database = get_database() database = get_database()
# Detect ID type and determine source # Detect source from ID — check if it's a library DB ID first
is_numeric_id = artist_id.isdigit() is_numeric_id = artist_id.isdigit()
fallback_source = _get_metadata_fallback_source() source = None
source = fallback_source if is_numeric_id else 'spotify' if is_numeric_id:
# Could be a library DB ID, iTunes ID, Deezer ID, or Discogs ID
# Check if this is a library DB artist and use their actual source IDs
try:
conn = database._get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT spotify_artist_id, itunes_artist_id, deezer_id, discogs_id
FROM artists WHERE id = ? LIMIT 1
""", (artist_id,))
row = cursor.fetchone()
conn.close()
if row:
# Library artist — use the best available source ID
fallback = _get_metadata_fallback_source()
if fallback == 'discogs' and row['discogs_id']:
artist_id = row['discogs_id']
source = 'discogs'
elif fallback == 'deezer' and row['deezer_id']:
artist_id = row['deezer_id']
source = 'deezer'
elif row['spotify_artist_id']:
artist_id = row['spotify_artist_id']
source = 'spotify'
elif row['itunes_artist_id']:
artist_id = row['itunes_artist_id']
source = 'itunes'
elif row['deezer_id']:
artist_id = row['deezer_id']
source = 'deezer'
elif row['discogs_id']:
artist_id = row['discogs_id']
source = 'discogs'
except Exception:
pass
if not source:
fallback_source = _get_metadata_fallback_source()
source = fallback_source if is_numeric_id else 'spotify'
success = database.add_artist_to_watchlist(artist_id, artist_name, profile_id=get_current_profile_id(), source=source) success = database.add_artist_to_watchlist(artist_id, artist_name, profile_id=get_current_profile_id(), source=source)
if success: if success:
@ -36612,7 +36649,15 @@ def add_to_watchlist():
if is_numeric_id: if is_numeric_id:
# For numeric IDs, fetch image from the configured fallback source # For numeric IDs, fetch image from the configured fallback source
try: try:
if fallback_source == 'deezer': if source == 'discogs':
# Discogs: fetch artist image from API
from core.discogs_client import DiscogsClient
dc = DiscogsClient()
dc_data = dc.get_artist(artist_id)
if dc_data:
image_url = dc_data.get('image_url')
print(f"🖼️ Discogs artist image: {image_url[:60] if image_url else 'None'}")
elif source == 'deezer' or fallback_source == 'deezer':
# Deezer: fetch artist image directly from API # Deezer: fetch artist image directly from API
dz_resp = requests.get(f'https://api.deezer.com/artist/{artist_id}', timeout=5) dz_resp = requests.get(f'https://api.deezer.com/artist/{artist_id}', timeout=5)
if dz_resp.ok: if dz_resp.ok:
@ -37584,9 +37629,9 @@ def watchlist_artist_config(artist_id):
cur2.execute(""" cur2.execute("""
SELECT banner_url, summary, style, mood, label, genres SELECT banner_url, summary, style, mood, label, genres
FROM artists FROM artists
WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?
LIMIT 1 LIMIT 1
""", (artist_id, artist_id, artist_id)) """, (artist_id, artist_id, artist_id, artist_id))
lib_row = cur2.fetchone() lib_row = cur2.fetchone()
if lib_row: if lib_row:
artist_info['banner_url'] = lib_row[0] artist_info['banner_url'] = lib_row[0]
@ -37679,8 +37724,8 @@ def watchlist_artist_config(artist_id):
# Check if lookback_days changed — if so, clear last_scan_timestamp to force rescan # Check if lookback_days changed — if so, clear last_scan_timestamp to force rescan
cursor.execute(""" cursor.execute("""
SELECT lookback_days FROM watchlist_artists SELECT lookback_days FROM watchlist_artists
WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?
""", (artist_id, artist_id, artist_id)) """, (artist_id, artist_id, artist_id, artist_id))
old_row = cursor.fetchone() old_row = cursor.fetchone()
old_lookback = old_row[0] if old_row else None old_lookback = old_row[0] if old_row else None
lookback_changed = old_lookback != lookback_days lookback_changed = old_lookback != lookback_days
@ -37692,11 +37737,11 @@ def watchlist_artist_config(artist_id):
include_instrumentals = ?, lookback_days = ?, include_instrumentals = ?, lookback_days = ?,
last_scan_timestamp = CASE WHEN ? THEN NULL ELSE last_scan_timestamp END, last_scan_timestamp = CASE WHEN ? THEN NULL ELSE last_scan_timestamp END,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?
""", (int(include_albums), int(include_eps), int(include_singles), """, (int(include_albums), int(include_eps), int(include_singles),
int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations), int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations),
int(include_instrumentals), lookback_days, lookback_changed, int(include_instrumentals), lookback_days, lookback_changed,
artist_id, artist_id, artist_id)) artist_id, artist_id, artist_id, artist_id))
conn.commit() conn.commit()
if cursor.rowcount == 0: if cursor.rowcount == 0:
@ -37752,8 +37797,8 @@ def watchlist_artist_link_provider(artist_id):
cursor.execute(""" cursor.execute("""
SELECT id, artist_name, spotify_artist_id, itunes_artist_id SELECT id, artist_name, spotify_artist_id, itunes_artist_id
FROM watchlist_artists FROM watchlist_artists
WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? OR discogs_artist_id = ?
""", (artist_id, artist_id, artist_id)) """, (artist_id, artist_id, artist_id, artist_id))
row = cursor.fetchone() row = cursor.fetchone()
if not row: if not row:

View file

@ -38782,7 +38782,7 @@ async function showWatchlistModal() {
if (artist.itunes_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-itunes">iTunes</span>'); if (artist.itunes_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-itunes">iTunes</span>');
if (artist.deezer_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-deezer">Deezer</span>'); if (artist.deezer_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-deezer">Deezer</span>');
if (artist.discogs_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-discogs">Discogs</span>'); if (artist.discogs_artist_id) sourceBadges.push('<span class="watchlist-source-badge watchlist-source-discogs">Discogs</span>');
const artistPrimaryId = artist.spotify_artist_id || artist.itunes_artist_id || artist.deezer_artist_id; const artistPrimaryId = artist.spotify_artist_id || artist.itunes_artist_id || artist.deezer_artist_id || artist.discogs_artist_id;
return ` return `
<div class="watchlist-artist-card" <div class="watchlist-artist-card"
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}" data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}"
@ -39278,7 +39278,7 @@ async function openWatchlistArtistDetailView(artistId, artistName) {
return; return;
} }
const { config, artist, recent_releases, spotify_artist_id, itunes_artist_id, deezer_artist_id } = data; const { config, artist, recent_releases, spotify_artist_id, itunes_artist_id, deezer_artist_id, discogs_artist_id } = data;
// Remove existing overlay if any // Remove existing overlay if any
const existing = document.querySelector('.watchlist-artist-detail-overlay'); const existing = document.querySelector('.watchlist-artist-detail-overlay');
@ -39407,13 +39407,15 @@ async function openWatchlistArtistDetailView(artistId, artistName) {
const activeSrc = (currentMusicSourceName || '').toLowerCase(); const activeSrc = (currentMusicSourceName || '').toLowerCase();
if (activeSrc.includes('spotify') && spotify_artist_id) { if (activeSrc.includes('spotify') && spotify_artist_id) {
discogId = spotify_artist_id; source = 'spotify'; discogId = spotify_artist_id; source = 'spotify';
} else if (activeSrc.includes('discogs') && discogs_artist_id) {
discogId = discogs_artist_id; source = 'discogs';
} else if (activeSrc.includes('deezer') && deezer_artist_id) { } else if (activeSrc.includes('deezer') && deezer_artist_id) {
discogId = deezer_artist_id; source = 'deezer'; discogId = deezer_artist_id; source = 'deezer';
} else if (itunes_artist_id) { } else if (itunes_artist_id) {
discogId = itunes_artist_id; source = 'itunes'; discogId = itunes_artist_id; source = 'itunes';
} else { } else {
discogId = spotify_artist_id || deezer_artist_id || itunes_artist_id; discogId = spotify_artist_id || discogs_artist_id || deezer_artist_id || itunes_artist_id;
source = spotify_artist_id ? 'spotify' : deezer_artist_id ? 'deezer' : 'itunes'; source = spotify_artist_id ? 'spotify' : discogs_artist_id ? 'discogs' : deezer_artist_id ? 'deezer' : 'itunes';
} }
if (discogId) { if (discogId) {
// Close watchlist modal + detail overlay // Close watchlist modal + detail overlay