From 12a2d4cf5f4ba30b5b34bda6242511ada7668188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Dudzi=C5=84ski?= Date: Mon, 29 Sep 2025 19:45:05 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Added=20`mix`=20downloading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_utils.py | 6 ++++++ tiddl/api.py | 19 +++++++++++++++++++ tiddl/cli/download/__init__.py | 10 ++++++++++ tiddl/models/api.py | 7 +++++++ tiddl/utils.py | 11 +++++++++-- 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 15b268a..3b81d03 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,6 +17,12 @@ class TestTidalResource(unittest.TestCase): ("playlist/12345678", "playlist", "12345678"), ("https://tidal.com/browse/artist/12345678", "artist", "12345678"), ("artist/12345678", "artist", "12345678"), + ( + "https://tidal.com/browse/mix/f93b015796bf93b015796b", + "mix", + "f93b015796bf93b015796b", + ), + ("mix/f93b015796bf93b015796b", "mix", "f93b015796bf93b015796b"), ] for resource, expected_type, expected_id in positive_cases: diff --git a/tiddl/api.py b/tiddl/api.py index 4d81c12..662cfd8 100644 --- a/tiddl/api.py +++ b/tiddl/api.py @@ -27,6 +27,7 @@ from tiddl.models.api import ( Video, VideoStream, Lyrics, + MixItems, ) from tiddl.models.constants import TrackQuality @@ -53,6 +54,7 @@ class Limits: ALBUM_ITEMS = 10 ALBUM_ITEMS_MAX = 100 PLAYLIST = 50 + MIX_ITEMS = 100 class TidalApi: @@ -175,6 +177,23 @@ class TidalApi: expire_after=3600, ) + def getMix( + self, + mix_id: str | int, + limit=LIMITS.MIX_ITEMS, + offset=0, + ): + return self.fetch( + MixItems, + f"mixes/{mix_id}/items", + { + "countryCode": self.country_code, + "limit": limit, + "offset": offset, + }, + expire_after=3600, + ) + def getFavorites(self): return self.fetch( Favorites, diff --git a/tiddl/cli/download/__init__.py b/tiddl/cli/download/__init__.py index e8edeed..2f6911b 100644 --- a/tiddl/cli/download/__init__.py +++ b/tiddl/cli/download/__init__.py @@ -405,6 +405,16 @@ def DownloadCommand( downloadAlbum(album) + case "mix": + mix = api.getMix(resource.id) + + for mix_item in mix.items: + filename = formatResource( + TEMPLATE or ctx.obj.config.template.track, mix_item.item + ) + + submitItem(mix_item.item, filename) + case "artist": artist = api.getArtist(resource.id) logging.info(f"Artist {artist.name!r}") diff --git a/tiddl/models/api.py b/tiddl/models/api.py index 68b2cfa..02933ad 100644 --- a/tiddl/models/api.py +++ b/tiddl/models/api.py @@ -102,6 +102,13 @@ class PlaylistItems(Items): items: List[Union[PlaylistTrackItem, PlaylistVideoItem]] +class MixItems(Items): + class MixItem(BaseModel): + item: Track + type: ItemType = "track" + + items: List[MixItem] + class Favorites(BaseModel): PLAYLIST: List[str] ALBUM: List[str] diff --git a/tiddl/utils.py b/tiddl/utils.py index 76ee06d..2fa0b08 100644 --- a/tiddl/utils.py +++ b/tiddl/utils.py @@ -14,7 +14,7 @@ from typing import Literal, Union, get_args from tiddl.models.constants import TrackQuality, QUALITY_TO_ARG from tiddl.models.resource import Track, Video -ResourceTypeLiteral = Literal["track", "video", "album", "playlist", "artist"] +ResourceTypeLiteral = Literal["track", "video", "album", "playlist", "artist", "mix"] class TidalResource(BaseModel): @@ -41,7 +41,14 @@ class TidalResource(BaseModel): if resource_type not in get_args(ResourceTypeLiteral): raise ValueError(f"Invalid resource type: {resource_type}") - if not resource_id.isdigit() and resource_type != "playlist": + digit_resource_types: list[ResourceTypeLiteral] = [ + "track", + "album", + "video", + "artist", + ] + + if resource_type in digit_resource_types and not resource_id.isdigit(): raise ValueError(f"Invalid resource id: {resource_id}") return cls(type=resource_type, id=resource_id) # type: ignore