Skip to content

Commit 5bcadd3

Browse files
Louis2530nathom
andauthored
Fix deezer dynamic link parsing (#824)
* Fix links issues due tu recent PR * Add tests for URL parsing --------- Co-authored-by: Nathan Thomas <nathanthomas707@gmail.com>
1 parent db52d49 commit 5bcadd3

File tree

2 files changed

+156
-1
lines changed

2 files changed

+156
-1
lines changed

streamrip/rip/parse_url.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
logger = logging.getLogger("streamrip")
2020
URL_REGEX = re.compile(
21-
r"https?://(?:www|open|play|listen)?\.?(qobuz|tidal|deezer|dzr)\.?(com|page.link)(?:(?:/(album|artist|track|playlist|video|label))|(?:\/[-\w]+?))+\/([-\w]+)",
21+
r"https?://(?:www|open|play|listen)?\.?(qobuz|tidal|deezer)\.com?(?:(?:/(album|artist|track|playlist|video|label))|(?:\/[-\w]+?))+\/([-\w]+)",
2222
)
2323
SOUNDCLOUD_URL_REGEX = re.compile(r"https://soundcloud.com/[-\w:/]+")
2424
LASTFM_URL_REGEX = re.compile(r"https://www.last.fm/user/\w+/playlists/\w+")

tests/test_parse_url.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import unittest
2+
from unittest.mock import AsyncMock, patch
3+
4+
from streamrip.rip.parse_url import (
5+
DeezerDynamicURL,
6+
GenericURL,
7+
SoundcloudURL,
8+
parse_url,
9+
)
10+
11+
12+
class TestParseURL(unittest.TestCase):
13+
def test_deezer_dynamic_url(self):
14+
"""Test that Deezer dynamic URLs are matched correctly."""
15+
url = "https://dzr.page.link/SnV6hCyHihkmCCwUA"
16+
result = parse_url(url)
17+
18+
self.assertIsNotNone(result)
19+
self.assertIsInstance(result, DeezerDynamicURL)
20+
self.assertEqual(result.source, "deezer")
21+
22+
def test_qobuz_album_url(self):
23+
"""Test that Qobuz album URLs are matched correctly."""
24+
url = "https://www.qobuz.com/fr-fr/album/bizarre-ride-ii-the-pharcyde-the-pharcyde/0066991040005"
25+
result = parse_url(url)
26+
27+
self.assertIsNotNone(result)
28+
self.assertIsInstance(result, GenericURL)
29+
self.assertEqual(result.source, "qobuz")
30+
31+
# Verify the regex match groups
32+
groups = result.match.groups()
33+
self.assertEqual(len(groups), 3)
34+
self.assertEqual(groups[0], "qobuz") # source
35+
self.assertEqual(groups[1], "album") # media_type
36+
self.assertEqual(groups[2], "0066991040005") # item_id
37+
38+
def test_tidal_track_url(self):
39+
"""Test that Tidal track URLs are matched correctly."""
40+
url = "https://tidal.com/browse/track/3083287"
41+
result = parse_url(url)
42+
43+
self.assertIsNotNone(result)
44+
self.assertIsInstance(result, GenericURL)
45+
self.assertEqual(result.source, "tidal")
46+
47+
# Verify the regex match groups
48+
groups = result.match.groups()
49+
self.assertEqual(len(groups), 3)
50+
self.assertEqual(groups[0], "tidal") # source
51+
self.assertEqual(groups[1], "track") # media_type
52+
self.assertEqual(groups[2], "3083287") # item_id
53+
54+
def test_deezer_track_url(self):
55+
"""Test that Deezer track URLs are matched correctly."""
56+
url = "https://www.deezer.com/track/4195713"
57+
result = parse_url(url)
58+
59+
self.assertIsNotNone(result)
60+
self.assertIsInstance(result, GenericURL)
61+
self.assertEqual(result.source, "deezer")
62+
63+
# Verify the regex match groups
64+
groups = result.match.groups()
65+
self.assertEqual(len(groups), 3)
66+
self.assertEqual(groups[0], "deezer") # source
67+
self.assertEqual(groups[1], "track") # media_type
68+
self.assertEqual(groups[2], "4195713") # item_id
69+
70+
def test_invalid_url(self):
71+
"""Test that invalid URLs return None."""
72+
urls = [
73+
"https://example.com",
74+
"not a url",
75+
"https://spotify.com/track/123456", # Unsupported source
76+
"https://tidal.com/invalid/3083287", # Invalid media type
77+
]
78+
79+
for url in urls:
80+
result = parse_url(url)
81+
self.assertIsNone(result, f"URL should not parse: {url}")
82+
83+
def test_alternate_url_formats(self):
84+
"""Test various URL formats that should be valid."""
85+
# Test with different domain prefixes
86+
url1 = "https://open.tidal.com/track/3083287"
87+
url2 = "https://play.qobuz.com/album/0066991040005"
88+
url3 = "https://listen.tidal.com/track/3083287"
89+
90+
for url in [url1, url2, url3]:
91+
result = parse_url(url)
92+
self.assertIsNotNone(result, f"Should parse URL: {url}")
93+
self.assertIsInstance(result, GenericURL)
94+
95+
def test_url_with_language_code(self):
96+
"""Test URLs with different language codes."""
97+
urls = [
98+
"https://www.qobuz.com/us-en/album/name/id123456",
99+
"https://www.qobuz.com/gb-en/album/name/id123456",
100+
"https://www.deezer.com/en/track/4195713",
101+
"https://www.deezer.com/fr/track/4195713",
102+
]
103+
104+
for url in urls:
105+
result = parse_url(url)
106+
self.assertIsNotNone(result, f"Should parse URL: {url}")
107+
self.assertIsInstance(result, GenericURL)
108+
109+
def test_soundcloud_url(self):
110+
"""Test that Soundcloud URLs are matched correctly."""
111+
urls = [
112+
"https://soundcloud.com/artist-name/track-name",
113+
"https://soundcloud.com/artist-name/sets/playlist-name",
114+
]
115+
116+
for url in urls:
117+
result = parse_url(url)
118+
self.assertIsNotNone(result, f"Should parse URL: {url}")
119+
self.assertIsInstance(result, SoundcloudURL)
120+
self.assertEqual(result.source, "soundcloud")
121+
122+
123+
class TestDeezerDynamicURL(unittest.TestCase):
124+
@patch("streamrip.rip.parse_url.DeezerDynamicURL._extract_info_from_dynamic_link")
125+
def test_into_pending_album(self, mock_extract):
126+
"""Test conversion of Deezer dynamic URL to a PendingAlbum."""
127+
import asyncio
128+
129+
async def run_test():
130+
url = "https://dzr.page.link/SnV6hCyHihkmCCwUA"
131+
result = parse_url(url)
132+
133+
# Mock the extract method to return album type and ID
134+
mock_extract.return_value = ("album", "12345")
135+
136+
# Mock the client, config, db
137+
mock_client = AsyncMock()
138+
mock_client.source = "deezer"
139+
mock_config = AsyncMock()
140+
mock_db = AsyncMock()
141+
142+
# Call into_pending
143+
pending = await result.into_pending(mock_client, mock_config, mock_db)
144+
145+
# Verify the correct pending type was created
146+
self.assertEqual(pending.__class__.__name__, "PendingAlbum")
147+
self.assertEqual(pending.id, "12345")
148+
149+
# Run the coroutine
150+
asyncio.run(run_test())
151+
152+
153+
if __name__ == "__main__":
154+
unittest.main()
155+

0 commit comments

Comments
 (0)