From 82f9b84e5bf19f21e7b3b941f3dfa98402a87037 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:53:30 -0700 Subject: [PATCH] =?UTF-8?q?Add=20Discogs=20to=20watchlist=20=E2=80=94=20co?= =?UTF-8?q?lumn,=20backfill,=20matching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add discogs_artist_id column to watchlist_artists table (migration) - Add discogs_artist_id to WatchlistArtist dataclass - Add to get_watchlist_artists optional_columns and constructor - Add update_watchlist_discogs_id DB method - Backfill loop includes Discogs when token is configured - Add _match_to_discogs for cross-provider artist matching - Backfill maps updated: id_attr, match_fn, update_fn all include discogs --- core/watchlist_scanner.py | 20 ++++++++++++++++++++ database/music_database.py | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/core/watchlist_scanner.py b/core/watchlist_scanner.py index ee181110..a95588c4 100644 --- a/core/watchlist_scanner.py +++ b/core/watchlist_scanner.py @@ -609,6 +609,12 @@ class WatchlistScanner: providers_to_backfill = ['itunes', 'deezer'] if self.spotify_client and self.spotify_client.is_spotify_authenticated(): providers_to_backfill.append('spotify') + try: + from config.settings import config_manager as _cfg + if _cfg.get('discogs.token', ''): + providers_to_backfill.append('discogs') + except Exception: + pass for provider in providers_to_backfill: try: @@ -979,6 +985,7 @@ class WatchlistScanner: 'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id', + 'discogs': 'discogs_artist_id', }.get(provider) if not id_attr: @@ -997,12 +1004,14 @@ class WatchlistScanner: 'spotify': self._match_to_spotify, 'itunes': self._match_to_itunes, 'deezer': self._match_to_deezer, + 'discogs': self._match_to_discogs, }.get(provider) update_fn = { 'spotify': self.database.update_watchlist_spotify_id, 'itunes': self.database.update_watchlist_itunes_id, 'deezer': getattr(self.database, 'update_watchlist_deezer_id', None), + 'discogs': getattr(self.database, 'update_watchlist_discogs_id', None), }.get(provider) if not match_fn or not update_fn: @@ -1150,6 +1159,17 @@ class WatchlistScanner: logger.warning(f"Could not match {artist_name} to Deezer: {e}") return None + def _match_to_discogs(self, artist_name: str) -> Optional[str]: + """Match artist name to Discogs ID using fuzzy name comparison.""" + try: + from core.discogs_client import DiscogsClient + client = DiscogsClient() + results = client.search_artists(artist_name, limit=5) + return self._best_artist_match(results, artist_name) + except Exception as e: + logger.warning(f"Could not match {artist_name} to Discogs: {e}") + return None + def _get_lookback_period_setting(self) -> str: """ Get the discovery lookback period setting from database. diff --git a/database/music_database.py b/database/music_database.py index 8eec72ac..530aa57c 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -88,6 +88,7 @@ class WatchlistArtist: image_url: Optional[str] = None itunes_artist_id: Optional[str] = None # Cross-provider support deezer_artist_id: Optional[str] = None # Cross-provider support + discogs_artist_id: Optional[str] = None # Cross-provider support include_albums: bool = True include_eps: bool = True include_singles: bool = True @@ -1272,6 +1273,10 @@ class MusicDatabase: cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN deezer_artist_id TEXT") logger.info("Added deezer_artist_id column to watchlist_artists table for cross-provider support") + if 'discogs_artist_id' not in columns: + cursor.execute("ALTER TABLE watchlist_artists ADD COLUMN discogs_artist_id TEXT") + logger.info("Added discogs_artist_id column to watchlist_artists table for cross-provider support") + except Exception as e: logger.error(f"Error adding itunes_artist_id column to watchlist_artists: {e}") # Don't raise - this is a migration, database can still function @@ -6444,7 +6449,7 @@ class MusicDatabase: # Check if artist already exists by name (case-insensitive) for this profile cursor.execute(""" - SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id + SELECT id, spotify_artist_id, itunes_artist_id, deezer_artist_id, discogs_artist_id FROM watchlist_artists WHERE LOWER(artist_name) = LOWER(?) AND profile_id = ? LIMIT 1 @@ -6457,7 +6462,7 @@ class MusicDatabase: if existing: # Artist already on watchlist — update with new source ID if missing - col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id'} + col_map = {'spotify': 'spotify_artist_id', 'itunes': 'itunes_artist_id', 'deezer': 'deezer_artist_id', 'discogs': 'discogs_artist_id'} col = col_map.get(source) if col and not existing[col]: cursor.execute(f""" @@ -6486,6 +6491,13 @@ class MusicDatabase: VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?) """, (artist_id, artist_name, profile_id)) logger.info(f"Added artist '{artist_name}' to watchlist (iTunes ID: {artist_id}, profile: {profile_id})") + elif source == 'discogs': + cursor.execute(""" + INSERT INTO watchlist_artists + (discogs_artist_id, artist_name, date_added, updated_at, profile_id) + VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?) + """, (artist_id, artist_name, profile_id)) + logger.info(f"Added artist '{artist_name}' to watchlist (Discogs ID: {artist_id}, profile: {profile_id})") else: cursor.execute(""" INSERT INTO watchlist_artists @@ -6572,7 +6584,7 @@ class MusicDatabase: # Build SELECT query based on existing columns base_columns = ['id', 'spotify_artist_id', 'artist_name', 'date_added', 'last_scan_timestamp', 'created_at', 'updated_at'] - optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'include_albums', 'include_eps', 'include_singles', + optional_columns = ['image_url', 'itunes_artist_id', 'deezer_artist_id', 'discogs_artist_id', 'include_albums', 'include_eps', 'include_singles', 'include_live', 'include_remixes', 'include_acoustic', 'include_compilations', 'include_instrumentals', 'lookback_days'] @@ -6600,6 +6612,7 @@ class MusicDatabase: image_url = row['image_url'] if 'image_url' in existing_columns else None itunes_artist_id = row['itunes_artist_id'] if 'itunes_artist_id' in existing_columns else None deezer_artist_id = row['deezer_artist_id'] if 'deezer_artist_id' in existing_columns else None + discogs_artist_id = row['discogs_artist_id'] if 'discogs_artist_id' in existing_columns else None include_albums = bool(row['include_albums']) if 'include_albums' in existing_columns else True include_eps = bool(row['include_eps']) if 'include_eps' in existing_columns else True include_singles = bool(row['include_singles']) if 'include_singles' in existing_columns else True @@ -6621,6 +6634,7 @@ class MusicDatabase: image_url=image_url, itunes_artist_id=itunes_artist_id, deezer_artist_id=deezer_artist_id, + discogs_artist_id=discogs_artist_id, include_albums=include_albums, include_eps=include_eps, include_singles=include_singles, @@ -6889,6 +6903,23 @@ class MusicDatabase: logger.error(f"Error updating watchlist Deezer ID: {e}") return False + def update_watchlist_discogs_id(self, watchlist_id: int, discogs_id: str) -> bool: + """Update the Discogs artist ID for a watchlist artist (cross-provider support)""" + try: + with self._get_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + UPDATE watchlist_artists + SET discogs_artist_id = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + """, (discogs_id, watchlist_id)) + conn.commit() + logger.info(f"Updated Discogs ID for watchlist artist {watchlist_id}: {discogs_id}") + return cursor.rowcount > 0 + except Exception as e: + logger.error(f"Error updating watchlist Discogs ID: {e}") + return False + def update_watchlist_artist_itunes_id(self, spotify_artist_id: str, itunes_id: str) -> bool: """Update the iTunes artist ID for a watchlist artist by Spotify ID (for cross-provider caching)""" try: