Add Deezer ARL favorite artists support

Add get_user_favorite_artists(limit=200) to DeezerDownloadClient to fetch a user's favorite artists via the public API using an ARL-authenticated session (paginated, error-handled, returns deezer_id, name, image_url).

Update web_server to treat Deezer as connected if either OAuth or ARL is authenticated, and to fetch favorite artists from OAuth client when available or from soulseek_client.deezer_dl (ARL) otherwise. Fetched artists are upserted into the database and appropriate log/console messages and counters are updated.
This commit is contained in:
Broque Thomas 2026-04-13 19:46:39 -07:00
parent 8fc4484846
commit 453eb90f19
2 changed files with 61 additions and 11 deletions

View file

@ -282,6 +282,47 @@ class DeezerDownloadClient:
logger.info(f"Fetched {len(playlists)} user playlists from Deezer") logger.info(f"Fetched {len(playlists)} user playlists from Deezer")
return playlists return playlists
def get_user_favorite_artists(self, limit: int = 200) -> list:
"""Fetch the authenticated user's favorite artists via public API with ARL cookies."""
if not self._authenticated or not self._user_data:
return []
user_id = self._user_data.get('USER_ID')
if not user_id:
return []
artists = []
index = 0
while len(artists) < limit:
try:
resp = self._session.get(
f'https://api.deezer.com/user/{user_id}/artists',
params={'index': index, 'limit': min(100, limit - len(artists))},
timeout=15
)
resp.raise_for_status()
data = resp.json()
if 'error' in data:
logger.warning(f"Deezer artists error: {data['error']}")
break
items = data.get('data', [])
if not items:
break
for a in items:
artists.append({
'deezer_id': str(a.get('id', '')),
'name': a.get('name', ''),
'image_url': a.get('picture_xl') or a.get('picture_big') or a.get('picture_medium', ''),
})
if not data.get('next'):
break
index += len(items)
except Exception as e:
logger.error(f"Error fetching favorite artists at index {index}: {e}")
break
logger.info(f"Fetched {len(artists)} favorite artists from Deezer (ARL)")
return artists
def get_playlist_tracks(self, playlist_id: str) -> Optional[dict]: def get_playlist_tracks(self, playlist_id: str) -> Optional[dict]:
"""Fetch full playlist details with tracks via public API (ARL cookies grant private access).""" """Fetch full playlist details with tracks via public API (ARL cookies grant private access)."""
try: try:

View file

@ -42405,10 +42405,13 @@ def get_your_artists_sources():
# Last.fm # Last.fm
if config_manager.get('lastfm.api_key', '') and config_manager.get('lastfm.session_key', ''): if config_manager.get('lastfm.api_key', '') and config_manager.get('lastfm.session_key', ''):
connected.append('lastfm') connected.append('lastfm')
# Deezer # Deezer — OAuth token OR ARL token both count as connected
try: try:
deezer_cl = _get_deezer_client() deezer_cl = _get_deezer_client()
if deezer_cl and hasattr(deezer_cl, 'is_user_authenticated') and deezer_cl.is_user_authenticated(): deezer_oauth = deezer_cl and hasattr(deezer_cl, 'is_user_authenticated') and deezer_cl.is_user_authenticated()
deezer_arl = (hasattr(soulseek_client, 'deezer_dl') and soulseek_client.deezer_dl
and soulseek_client.deezer_dl.is_authenticated())
if deezer_oauth or deezer_arl:
connected.append('deezer') connected.append('deezer')
except Exception: except Exception:
pass pass
@ -42518,22 +42521,28 @@ def _fetch_and_match_liked_artists(profile_id: int):
except Exception as e: except Exception as e:
logger.error(f"[Your Artists] Last.fm fetch error: {e}") logger.error(f"[Your Artists] Last.fm fetch error: {e}")
# 4. Fetch from Deezer (favorite artists — requires OAuth) # 4. Fetch from Deezer (favorite artists — OAuth or ARL)
try: try:
if 'deezer' not in enabled_sources: if 'deezer' not in enabled_sources:
print("[Your Artists] Deezer skipped (disabled in sources config)") print("[Your Artists] Deezer skipped (disabled in sources config)")
else: else:
deezer_cl = _get_deezer_client() deezer_cl = _get_deezer_client()
artists = []
if deezer_cl and hasattr(deezer_cl, 'is_user_authenticated') and deezer_cl.is_user_authenticated(): if deezer_cl and hasattr(deezer_cl, 'is_user_authenticated') and deezer_cl.is_user_authenticated():
print("[Your Artists] Fetching favorite artists from Deezer...") print("[Your Artists] Fetching favorite artists from Deezer (OAuth)...")
artists = deezer_cl.get_user_favorite_artists(limit=200) artists = deezer_cl.get_user_favorite_artists(limit=200)
for a in artists: elif (hasattr(soulseek_client, 'deezer_dl') and soulseek_client.deezer_dl
database.upsert_liked_artist( and soulseek_client.deezer_dl.is_authenticated()):
artist_name=a['name'], source_service='deezer', print("[Your Artists] Fetching favorite artists from Deezer (ARL)...")
source_id=a.get('deezer_id'), source_id_type='deezer', artists = soulseek_client.deezer_dl.get_user_favorite_artists(limit=200)
image_url=a.get('image_url'), profile_id=profile_id for a in artists:
) database.upsert_liked_artist(
fetched += len(artists) artist_name=a['name'], source_service='deezer',
source_id=a.get('deezer_id'), source_id_type='deezer',
image_url=a.get('image_url'), profile_id=profile_id
)
fetched += len(artists)
if artists:
print(f"[Your Artists] Fetched {len(artists)} from Deezer") print(f"[Your Artists] Fetched {len(artists)} from Deezer")
except Exception as e: except Exception as e:
logger.error(f"[Your Artists] Deezer fetch error: {e}") logger.error(f"[Your Artists] Deezer fetch error: {e}")