add TidalResource parser

This commit is contained in:
oskvr37
2025-01-01 21:20:03 +01:00
parent 1bad1cea3e
commit 7d803a929d
3 changed files with 113 additions and 20 deletions
+46
View File
@@ -0,0 +1,46 @@
import unittest
from tiddl.utils import TidalResource
class TestTidalResource(unittest.TestCase):
def test_resource_parsing(self):
positive_cases = [
("https://tidal.com/browse/track/12345678", "track", "12345678"),
("track/12345678", "track", "12345678"),
("https://tidal.com/browse/album/12345678", "album", "12345678"),
("album/12345678", "album", "12345678"),
("https://tidal.com/browse/playlist/12345678", "playlist", "12345678"),
("playlist/12345678", "playlist", "12345678"),
("https://tidal.com/browse/artist/12345678", "artist", "12345678"),
("artist/12345678", "artist", "12345678"),
]
for resource, expected_type, expected_id in positive_cases:
with self.subTest(resource=resource):
tidal_url = TidalResource(resource)
self.assertEqual(tidal_url.resource_type, expected_type)
self.assertEqual(tidal_url.resource_id, expected_id)
def test_failing_cases(self):
failing_cases = [
"https://tidal.com/browse/invalid/12345678",
"invalid/12345678",
"https://tidal.com/browse/track/invalid",
"track/invalid",
"",
"invalid",
"https://tidal.com/browse/track/",
"track/",
"/12345678",
]
for resource in failing_cases:
with self.subTest(resource=resource):
with self.assertRaises(ValueError):
TidalResource(resource)
if __name__ == "__main__":
unittest.main()
+26 -20
View File
@@ -1,36 +1,42 @@
import click
from ..ctx import Context, passContext
from tiddl.types import Track
from urllib import parse as urlparse
from tiddl.utils import TidalResource
class URL(click.ParamType):
# TODO: create correct Tidal URL parsing, maybe with regex
name = "url"
def convert(self, value, param, ctx):
if not isinstance(value, tuple):
value = urlparse.urlparse(value)
if value.scheme not in ("http", "https"):
self.fail(
f"invalid URL scheme ({value.scheme}). Only HTTP URLs are allowed",
param,
ctx,
)
return value
class TidalURL(click.ParamType):
def convert(self, value: str, param, ctx) -> TidalResource:
try:
return TidalResource(value)
except ValueError as e:
self.fail(message=str(e), param=param, ctx=ctx)
@click.group("url")
@click.argument("url", type=URL())
@click.argument("url", type=TidalURL())
@passContext
def UrlGroup(ctx: Context, url: URL, filename):
"""Get Tidal URLs"""
def UrlGroup(ctx: Context, url: TidalResource):
"""
Get Tidal URL.
print(url, filename)
It can be Tidal link or `resource_type/resource_id` format.
The resource can be a track, album, playlist or artist.
"""
tracks: list[Track] = []
# TODO: parse the URL list
# TODO: fetch api
match url.resource_type:
case "track":
pass
case "album":
pass
case "playlist":
pass
case "artist":
pass
ctx.obj.tracks.extend(tracks)
+41
View File
@@ -0,0 +1,41 @@
from urllib.parse import urlparse
from typing import Literal, get_args
ResourceTypeLiteral = Literal["track", "album", "playlist", "artist"]
class TidalResource:
"""
A parser for Tidal resource URLs or strings.
Extracts the resource type (e.g., "track", "album") and resource ID
from a given input string. The input string can either be a full URL or a
shorthand string in the format "resource_type/resource_id" (e.g., "track/12345678").
"""
resource: str
resource_type: ResourceTypeLiteral
resource_id: str
url: str
def __init__(self, resource: str) -> None:
self.resource = resource
path = urlparse(self.resource).path
resource_type, resource_id = path.split("/")[-2:]
if resource_type not in get_args(ResourceTypeLiteral):
raise ValueError(f"Invalid resource type: {resource_type}")
self.resource_type = resource_type # type: ignore
if not resource_id.isdigit() and self.resource_type != "playlist":
raise ValueError(f"Invalid resource id: {resource_id}")
self.resource_id = resource_id
self.url = f"https://listen.tidal.com/{self.resource_type}/{self.resource_id}"
def __str__(self) -> str:
return f"{self.resource_type}/{self.resource_id}"