mirror of
https://github.com/oskvr37/tiddl.git
synced 2026-06-13 04:05:08 +03:00
✨ add TidalResource parser
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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}"
|
||||
Reference in New Issue
Block a user