Skip to content

Commit 302ec09

Browse files
committed
Support lookups for 'your' URIs
This adds the ability to utilise the "spotify:your:tracks" and "spotify:your:albums" URIs through library lookups.
1 parent 797a486 commit 302ec09

File tree

5 files changed

+147
-22
lines changed

5 files changed

+147
-22
lines changed

mopidy_spotify/browse.py

+24-18
Original file line numberDiff line numberDiff line change
@@ -218,25 +218,31 @@ def _browse_toplist(config, session, variant, region):
218218
return []
219219

220220

221-
def _browse_your_music(web_client, variant):
222-
if not web_client.logged_in:
223-
return []
221+
def _load_your_music(web_client, variant):
222+
if web_client is None or not web_client.logged_in:
223+
return
224224

225-
if variant in ("tracks", "albums"):
226-
items = flatten(
227-
[
228-
page.get("items", [])
229-
for page in web_client.get_all(
230-
f"me/{variant}",
231-
params={"market": "from_token", "limit": 50},
232-
)
233-
if page
234-
]
235-
)
236-
if variant == "tracks":
237-
return list(translator.web_to_track_refs(items))
238-
else:
239-
return list(translator.web_to_album_refs(items))
225+
if variant not in ("tracks", "albums"):
226+
return
227+
228+
results = web_client.get_all(
229+
f"me/{variant}",
230+
params={"market": "from_token", "limit": 50},
231+
)
232+
for page in results:
233+
if not page:
234+
continue
235+
items = page.get("items", [])
236+
for item in items:
237+
yield item
238+
239+
240+
def _browse_your_music(web_client, variant):
241+
items = _load_your_music(web_client, variant)
242+
if variant == "tracks":
243+
return list(translator.web_to_track_refs(items))
244+
elif variant == "albums":
245+
return list(translator.web_to_album_refs(items))
240246
else:
241247
return []
242248

mopidy_spotify/lookup.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
22

33
import spotify
4-
from mopidy_spotify import playlists, translator, utils, web
4+
from mopidy_spotify import browse, playlists, translator, utils
5+
from mopidy_spotify.web import LinkType, WebLink
56

67
logger = logging.getLogger(__name__)
78

@@ -12,16 +13,18 @@
1213

1314
def lookup(config, session, web_client, uri):
1415
try:
15-
web_link = web.WebLink.from_uri(uri)
16-
if web_link.type != web.LinkType.PLAYLIST:
16+
web_link = WebLink.from_uri(uri)
17+
if web_link.type not in (LinkType.PLAYLIST, LinkType.YOUR):
1718
sp_link = session.get_link(uri)
1819
except ValueError as exc:
1920
logger.info(f"Failed to lookup {uri!r}: {exc}")
2021
return []
2122

2223
try:
23-
if web_link.type == web.LinkType.PLAYLIST:
24+
if web_link.type == LinkType.PLAYLIST:
2425
return _lookup_playlist(config, session, web_client, uri)
26+
elif web_link.type == LinkType.YOUR:
27+
return list(_lookup_your(config, session, web_client, uri))
2528
elif sp_link.type is spotify.LinkType.TRACK:
2629
return list(_lookup_track(config, sp_link))
2730
elif sp_link.type is spotify.LinkType.ALBUM:
@@ -92,3 +95,31 @@ def _lookup_playlist(config, session, web_client, uri):
9295
if playlist is None:
9396
raise spotify.Error("Playlist Web API lookup failed")
9497
return playlist.tracks
98+
99+
100+
def _lookup_your(config, session, web_client, uri):
101+
parts = uri.replace("spotify:your:", "").split(":")
102+
if len(parts) != 1:
103+
return
104+
variant = parts[0]
105+
106+
items = browse._load_your_music(web_client, variant)
107+
if variant == "tracks":
108+
for item in items:
109+
# The extra level here is to also support "saved track objects".
110+
web_track = item.get("track", item)
111+
track = translator.web_to_track(
112+
web_track, bitrate=config["bitrate"]
113+
)
114+
if track is not None:
115+
yield track
116+
elif variant == "albums":
117+
for item in items:
118+
# The extra level here is to also support "saved album objects".
119+
web_album = item.get("album", item)
120+
album_ref = translator.web_to_album_ref(web_album)
121+
if album_ref is None:
122+
continue
123+
sp_link = session.get_link(album_ref.uri)
124+
if sp_link.type is spotify.LinkType.ALBUM:
125+
yield from _lookup_album(config, sp_link)

mopidy_spotify/web.py

+3
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ class LinkType(Enum):
486486
ALBUM = "album"
487487
ARTIST = "artist"
488488
PLAYLIST = "playlist"
489+
YOUR = "your"
489490

490491

491492
@dataclass
@@ -519,6 +520,8 @@ def from_uri(cls, uri):
519520
"playlist",
520521
):
521522
return cls(uri, LinkType(parts[0]), parts[1], None)
523+
elif len(parts) == 2 and parts[0] == "your":
524+
return cls(uri, LinkType(parts[0]))
522525
elif len(parts) == 3 and parts[0] == "user" and parts[2] == "starred":
523526
if parsed_uri.scheme == "spotify":
524527
return cls(uri, LinkType.PLAYLIST, None, parts[1])

tests/test_lookup.py

+84
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,87 @@ def test_lookup_of_playlist_uri_when_not_logged_in(
192192
"Failed to lookup 'spotify:playlist:alice:foo': "
193193
"Playlist Web API lookup failed" in caplog.text
194194
)
195+
196+
197+
def test_lookup_of_yourtracks_uri(web_client_mock, web_track_mock, provider):
198+
web_saved_track_mock = {"track": web_track_mock}
199+
web_client_mock.get_all.return_value = [
200+
{"items": [web_saved_track_mock, web_saved_track_mock]},
201+
{"items": [web_saved_track_mock, web_saved_track_mock]},
202+
]
203+
204+
results = provider.lookup("spotify:your:tracks")
205+
206+
assert len(results) == 4
207+
for track in results:
208+
assert track.uri == "spotify:track:abc"
209+
assert track.name == "ABC 123"
210+
assert track.bitrate == 160
211+
212+
213+
def test_lookup_of_youralbums_uri(
214+
session_mock,
215+
web_client_mock,
216+
web_album_mock,
217+
sp_album_browser_mock,
218+
provider,
219+
):
220+
web_saved_album_mock = {"album": web_album_mock}
221+
web_client_mock.get_all.return_value = [
222+
{"items": [web_saved_album_mock, web_saved_album_mock]},
223+
{"items": [web_saved_album_mock, web_saved_album_mock]},
224+
]
225+
226+
sp_album_mock = sp_album_browser_mock.album
227+
session_mock.get_link.return_value = sp_album_mock.link
228+
229+
results = provider.lookup("spotify:your:albums")
230+
231+
get_link_call = mock.call("spotify:album:def")
232+
assert session_mock.get_link.call_args_list == [
233+
get_link_call,
234+
get_link_call,
235+
get_link_call,
236+
get_link_call,
237+
]
238+
assert sp_album_mock.link.as_album.call_count == 4
239+
240+
assert sp_album_mock.browse.call_count == 4
241+
load_call = mock.call(10)
242+
assert sp_album_browser_mock.load.call_args_list == [
243+
load_call,
244+
load_call,
245+
load_call,
246+
load_call,
247+
]
248+
249+
assert len(results) == 8
250+
for track in results:
251+
assert track.uri == "spotify:track:abc"
252+
assert track.name == "ABC 123"
253+
assert track.album.uri == "spotify:album:def"
254+
assert track.bitrate == 160
255+
256+
257+
def test_lookup_of_your_uri_when_not_logged_in(web_client_mock, provider):
258+
web_client_mock.user_id = None
259+
260+
results = provider.lookup("spotify:your:tracks")
261+
262+
assert len(results) == 0
263+
264+
265+
def test_lookup_of_unhandled_your_uri(provider):
266+
results = provider.lookup("spotify:your:artists")
267+
268+
assert len(results) == 0
269+
270+
271+
def test_lookup_of_invalid_your_uri(provider, caplog):
272+
results = provider.lookup("spotify:your:tracks:invalid")
273+
274+
assert len(results) == 0
275+
assert (
276+
"Failed to lookup 'spotify:your:tracks:invalid': Could not parse"
277+
in caplog.text
278+
)

tests/test_web.py

+1
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,7 @@ def test_logged_in(self, spotify_client, user_id, expected):
11061106
("spotify:track:bar", web.LinkType.TRACK, "bar"),
11071107
("spotify:artist:blah", web.LinkType.ARTIST, "blah"),
11081108
("spotify:album:stuff", web.LinkType.ALBUM, "stuff"),
1109+
("spotify:your:albums", web.LinkType.YOUR, None),
11091110
],
11101111
)
11111112
def test_weblink_from_uri_spotify_uri(uri, type_, id_):

0 commit comments

Comments
 (0)