Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion streamrip/client/deezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ class DeezerClient(Client):

source = "deezer"
max_quality = 2
max_favorites = 10_000

def __init__(self, config: Config):
self.global_config = config
self.client = deezer.Deezer()
self.logged_in = False
self.config = config.session.deezer
self.logged_in_user_id: int | None = None

async def login(self):
# Used for track downloads
Expand All @@ -51,6 +53,7 @@ async def login(self):
success = self.client.login_via_arl(arl)
if not success:
raise AuthenticationError
self.logged_in_user_id = self.client.gw.get_user_data()["USER"]["USER_ID"]
self.logged_in = True

async def get_metadata(self, item_id: str, media_type: str) -> dict:
Expand Down Expand Up @@ -98,6 +101,9 @@ async def get_album(self, item_id: str) -> dict:
return album_metadata

async def get_playlist(self, item_id: str) -> dict:
if item_id.startswith("favorites:"):
return await self.get_user_favorites(item_id[len("favorites:"):])

pl_metadata, pl_tracks = await asyncio.gather(
asyncio.to_thread(self.client.api.get_playlist, item_id),
asyncio.to_thread(self.client.api.get_playlist_tracks, item_id),
Expand All @@ -106,6 +112,23 @@ async def get_playlist(self, item_id: str) -> dict:
pl_metadata["track_total"] = len(pl_tracks["data"])
return pl_metadata

async def get_user_favorites(self, user_id: str) -> dict:
# deezer-py's get_user_tracks() drops the limit arg when routing to
# get_my_favorite_tracks(), so we detect own profile and call it directly.
if int(user_id) == self.logged_in_user_id:
tracks = await asyncio.to_thread(
self.client.gw.get_my_favorite_tracks, self.max_favorites
)
else:
tracks = await asyncio.to_thread(
self.client.gw.get_user_tracks, int(user_id), self.max_favorites
)
return {
"title": "Loved Tracks",
"tracks": tracks,
"track_total": len(tracks),
}

async def get_artist(self, item_id: str) -> dict:
artist, albums = await asyncio.gather(
asyncio.to_thread(self.client.api.get_artist, item_id),
Expand Down Expand Up @@ -148,7 +171,7 @@ async def get_downloadable(
# TODO: optimize such that all of the ids are requested at once
dl_info: dict = {"quality": quality, "id": item_id}

track_info = self.client.gw.get_track(item_id)
track_info = await asyncio.to_thread(self.client.gw.get_track, item_id)

fallback_id = track_info.get("FALLBACK", {}).get("SNG_ID")

Expand Down
23 changes: 23 additions & 0 deletions streamrip/rip/parse_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,28 @@ async def _extract_info_from_dynamic_link(
raise Exception("Unable to extract Deezer dynamic link.")


class DeezerFavoriteURL(URL):
favorite_re = re.compile(
r"https://(?:www\.)?deezer\.com/[a-z]{2}/profile/(\d+)/loved"
)

@classmethod
def from_str(cls, url: str) -> URL | None:
match = cls.favorite_re.match(url)
if match is None:
return None
return cls(match, "deezer")

async def into_pending(
self,
client: Client,
config: Config,
db: Database,
) -> Pending:
user_id = self.match.group(1)
return PendingPlaylist(f"favorites:{user_id}", client, config, db)


class SoundcloudURL(URL):
source = "soundcloud"

Expand Down Expand Up @@ -228,6 +250,7 @@ def parse_url(url: str) -> URL | None:
"""
url = url.strip()
parsed_urls: list[URL | None] = [
DeezerFavoriteURL.from_str(url),
GenericURL.from_str(url),
QobuzInterpreterURL.from_str(url),
SoundcloudURL.from_str(url),
Expand Down