From 85be65c402d5cddad271cff2b3e70028c34f64e5 Mon Sep 17 00:00:00 2001 From: Bent Hillerkus <29630575+benthillerkus@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:34:47 +0200 Subject: [PATCH 1/4] fix: parse id from URLs with tracking(?) query parameter Such as [https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J?si=asdf](open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J?si=asdf) --- spotify_to_ytmusic/spotify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spotify_to_ytmusic/spotify.py b/spotify_to_ytmusic/spotify.py index f546e5f..ea4cd47 100644 --- a/spotify_to_ytmusic/spotify.py +++ b/spotify_to_ytmusic/spotify.py @@ -1,6 +1,6 @@ import html import string -from urllib.parse import urlparse +import re import spotipy from spotipy import CacheFileHandler @@ -112,10 +112,10 @@ def build_results(tracks, album=None): return results -def get_id_from_url(url): - url_parts = parse_url(url) - return url_parts.path.split("/")[2] +_id_extraction_regex = re.compile(r"playlist\/(?P\w{22})\W") -def parse_url(url): - return urlparse(url) +def get_id_from_url(url): + url_parts = parse_url(url) + matches = re.search(url) + return matches.group("id) From 81b8354eab398ad8ad24a63d9e99de3180eaec40 Mon Sep 17 00:00:00 2001 From: Bent Hillerkus <29630575+benthillerkus@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:23:07 +0200 Subject: [PATCH 2/4] refactor: make this a classmethod --- spotify_to_ytmusic/spotify.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/spotify_to_ytmusic/spotify.py b/spotify_to_ytmusic/spotify.py index ea4cd47..56cd969 100644 --- a/spotify_to_ytmusic/spotify.py +++ b/spotify_to_ytmusic/spotify.py @@ -42,9 +42,7 @@ def __init__(self): self.api = spotipy.Spotify(client_credentials_manager=client_credentials_manager) def getSpotifyPlaylist(self, url): - playlistId = get_id_from_url(url) - if len(playlistId) != 22: - raise Exception(f"Bad playlist id: {playlistId}") + playlistId = self.getPlaylistIdFromUrl(url) print("Getting Spotify tracks...") results = self.api.playlist(playlistId) @@ -91,6 +89,16 @@ def getLikedPlaylist(self): "description": "Your liked tracks from spotify", } + __id_extraction_regex = re.compile(r"playlist\/(?P\w{22})\W?") + + @classmethod + def getPlaylistIdFromUrl(cls, url) -> str: + if (match := cls.__id_extraction_regex.search(url)): + return match.group("id") + elif (match := re.search(r"playlist\/(?P\w+)\W?", url)): + raise ValueError(f"Bad playlist id: {id}") + else: + raise ValueError(f"No id found in playlist url: {url}") def build_results(tracks, album=None): results = [] @@ -110,12 +118,3 @@ def build_results(tracks, album=None): ) return results - - -_id_extraction_regex = re.compile(r"playlist\/(?P\w{22})\W") - - -def get_id_from_url(url): - url_parts = parse_url(url) - matches = re.search(url) - return matches.group("id) From 7a447e17512b70929c081392d50093baa0d006a0 Mon Sep 17 00:00:00 2001 From: Bent Hillerkus <29630575+benthillerkus@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:23:23 +0200 Subject: [PATCH 3/4] chore: add tests --- tests/test_parsing.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/test_parsing.py diff --git a/tests/test_parsing.py b/tests/test_parsing.py new file mode 100644 index 0000000..9a43d99 --- /dev/null +++ b/tests/test_parsing.py @@ -0,0 +1,21 @@ +import unittest + +from spotify_to_ytmusic.spotify import Spotify + + +class TestParsing(unittest.TestCase): + def test_idFromSpotifyUrl(self): + url = "https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J" + self.assertEqual(Spotify.getPlaylistIdFromUrl(url), "37i9dQZF1DX5KpP2LN299J") + + def test_idFromSpotifyUrlWithShareId(self): + url = "http://open.spotify.com/playlist/37i9dQZF1DZ06evO3siF1W?si=grips" + self.assertEqual(Spotify.getPlaylistIdFromUrl(url), "37i9dQZF1DZ06evO3siF1W") + + def test_idFromSpotifyUrlRejectBadId(self): + url = "https://open.spotify.com/playlist/420" + self.assertRaises(ValueError, Spotify.getPlaylistIdFromUrl, url) + + def test_idFromSpotifyUrlRejectBadUrl(self): + url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + self.assertRaises(ValueError, Spotify.getPlaylistIdFromUrl, url) From 40bf3967d396ea1984d4203af4036047b50fc9e6 Mon Sep 17 00:00:00 2001 From: Bent Hillerkus <29630575+benthillerkus@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:33:42 +0200 Subject: [PATCH 4/4] refactor: don't cache regex --- spotify_to_ytmusic/spotify.py | 23 +++++++++++------------ tests/test_parsing.py | 10 +++++----- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/spotify_to_ytmusic/spotify.py b/spotify_to_ytmusic/spotify.py index 56cd969..1657e5e 100644 --- a/spotify_to_ytmusic/spotify.py +++ b/spotify_to_ytmusic/spotify.py @@ -42,7 +42,7 @@ def __init__(self): self.api = spotipy.Spotify(client_credentials_manager=client_credentials_manager) def getSpotifyPlaylist(self, url): - playlistId = self.getPlaylistIdFromUrl(url) + playlistId = extract_playlist_id_from_url(url) print("Getting Spotify tracks...") results = self.api.playlist(playlistId) @@ -89,17 +89,6 @@ def getLikedPlaylist(self): "description": "Your liked tracks from spotify", } - __id_extraction_regex = re.compile(r"playlist\/(?P\w{22})\W?") - - @classmethod - def getPlaylistIdFromUrl(cls, url) -> str: - if (match := cls.__id_extraction_regex.search(url)): - return match.group("id") - elif (match := re.search(r"playlist\/(?P\w+)\W?", url)): - raise ValueError(f"Bad playlist id: {id}") - else: - raise ValueError(f"No id found in playlist url: {url}") - def build_results(tracks, album=None): results = [] for track in tracks: @@ -118,3 +107,13 @@ def build_results(tracks, album=None): ) return results + + +def extract_playlist_id_from_url(url: str) -> str: + if (match := re.search(r"playlist\/(?P\w{22})\W?", url)): + return match.group("id") + elif (match := re.search(r"playlist\/(?P\w+)\W?", url)): + id = match.group("id") + raise ValueError(f"Bad playlist id: {id}\nA playlist id should be 22 characters long, not {len(id)}") + else: + raise ValueError(f"Couldn't understand playlist url: {url}\nA playlist url should look like this: https://open.spotify.com/playlist/37i9dQZF1DZ06evO41HwPk") diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 9a43d99..5e213e3 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,21 +1,21 @@ import unittest -from spotify_to_ytmusic.spotify import Spotify +from spotify_to_ytmusic.spotify import extract_playlist_id_from_url class TestParsing(unittest.TestCase): def test_idFromSpotifyUrl(self): url = "https://open.spotify.com/playlist/37i9dQZF1DX5KpP2LN299J" - self.assertEqual(Spotify.getPlaylistIdFromUrl(url), "37i9dQZF1DX5KpP2LN299J") + self.assertEqual(extract_playlist_id_from_url(url), "37i9dQZF1DX5KpP2LN299J") def test_idFromSpotifyUrlWithShareId(self): url = "http://open.spotify.com/playlist/37i9dQZF1DZ06evO3siF1W?si=grips" - self.assertEqual(Spotify.getPlaylistIdFromUrl(url), "37i9dQZF1DZ06evO3siF1W") + self.assertEqual(extract_playlist_id_from_url(url), "37i9dQZF1DZ06evO3siF1W") def test_idFromSpotifyUrlRejectBadId(self): url = "https://open.spotify.com/playlist/420" - self.assertRaises(ValueError, Spotify.getPlaylistIdFromUrl, url) + self.assertRaisesRegex(ValueError, r"Bad playlist id", extract_playlist_id_from_url, url) def test_idFromSpotifyUrlRejectBadUrl(self): url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - self.assertRaises(ValueError, Spotify.getPlaylistIdFromUrl, url) + self.assertRaises(ValueError, extract_playlist_id_from_url, url)