Skip to content

Commit

Permalink
get_artist_albums: add order param (#441)
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Jan 10, 2024
1 parent f2be194 commit e864a6b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 12 deletions.
17 changes: 12 additions & 5 deletions tests/mixins/test_browsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,22 @@ def test_get_artist(self, yt):
assert len(results) >= 11

def test_get_artist_albums(self, yt):
artist = yt.get_artist("UCj5ZiBBqpe0Tg4zfKGHEFuQ")
artist = yt.get_artist("UCAeLFBCQS7FvI8PvBrWvSBg")
results = yt.get_artist_albums(artist["albums"]["browseId"], artist["albums"]["params"])
assert len(results) == 100
results = yt.get_artist_albums(artist["singles"]["browseId"], artist["singles"]["params"])
assert len(results) < 100
assert len(results) == 100

results_unsorted = yt.get_artist_albums(
artist["albums"]["browseId"], artist["albums"]["params"], limit=None
)
assert len(results_unsorted) >= 300

artist = yt.get_artist("UC6LfFqHnWV8iF94n54jwYGw")
results = yt.get_artist_albums(artist["albums"]["browseId"], artist["albums"]["params"], limit=None)
assert len(results) >= 300
results_sorted = yt.get_artist_albums(
artist["albums"]["browseId"], artist["albums"]["params"], limit=None, order="alphabetical order"
)
assert len(results_sorted) >= 300
assert results_sorted != results_unsorted

def test_get_user(self, yt):
results = yt.get_user("UC44hbeRoCZVVMVg5z0FfIww")
Expand Down
65 changes: 60 additions & 5 deletions ytmusicapi/mixins/browsing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import re
from typing import Any, Dict, List, Optional

from ytmusicapi.continuations import get_continuations
from ytmusicapi.continuations import (
get_continuations,
get_reloadable_continuation_params,
)
from ytmusicapi.helpers import YTM_DOMAIN, sum_total_duration
from ytmusicapi.parsers.albums import parse_album_header
from ytmusicapi.parsers.browsing import parse_album, parse_content_list, parse_mixed_content, parse_playlist
Expand Down Expand Up @@ -252,28 +255,80 @@ def get_artist(self, channelId: str) -> Dict:
artist.update(self.parser.parse_artist_contents(results))
return artist

def get_artist_albums(self, channelId: str, params: str, limit: int | None = 100) -> List[Dict]:
def get_artist_albums(
self, channelId: str, params: str, limit: Optional[int] = 100, order: Optional[str] = None
) -> List[Dict]:
"""
Get the full list of an artist's albums or singles
:param channelId: browseId of the artist as returned by :py:func:`get_artist`
:param params: params obtained by :py:func:`get_artist`
:param limit: Number of albums to return. `None` retrieves them all. Default: 100
:param order: Order of albums to return. Allowed values: 'Recency', 'Popularity', 'Alphabetical order'. Default: Default order.
:return: List of albums in the format of :py:func:`get_library_albums`,
except artists key is missing.
"""
body = {"browseId": channelId, "params": params}
endpoint = "browse"
response = self._send_request(endpoint, body)
results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST_ITEM)

request_func = lambda additionalParams: self._send_request(endpoint, body, additionalParams)
parse_func = lambda contents: parse_albums(contents)

if order:
# pick the correct continuation from response depending on the order chosen
sort_options = nav(
response,
SINGLE_COLUMN_TAB
+ SECTION
+ HEADER_SIDE
+ [
"endItems",
0,
"musicSortFilterButtonRenderer",
"menu",
"musicMultiSelectMenuRenderer",
"options",
],
)
continuation = next(
(
nav(
option,
MULTI_SELECT
+ [
"selectedCommand",
"commandExecutorCommand",
"commands",
-1,
"browseSectionListReloadEndpoint",
],
)
for option in sort_options
if nav(option, MULTI_SELECT + TITLE_TEXT).lower() == order.lower()
),
None,
)
# if a valid order was provided, request continuation and replace original response
if continuation:
additionalParams = get_reloadable_continuation_params(
{"continuations": [continuation["continuation"]]}
)
response = request_func(additionalParams)
results = nav(response, SECTION_LIST_CONTINUATION + CONTENT)
else:
raise ValueError(f"Invalid order parameter {order}")

else:
# just use the results from the first request
results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST_ITEM)

contents = nav(results, GRID_ITEMS, True) or nav(results, CAROUSEL_CONTENTS)
albums = parse_albums(contents)

results = nav(results, GRID, True)
if "continuations" in results:
request_func = lambda additionalParams: self._send_request(endpoint, body, additionalParams)
parse_func = lambda contents: parse_albums(contents)
remaining_limit = None if limit is None else (limit - len(albums))
albums.extend(
get_continuations(results, "gridContinuation", remaining_limit, request_func, parse_func)
Expand Down
7 changes: 5 additions & 2 deletions ytmusicapi/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
TAB_1_CONTENT = ["tabs", 1, "tabRenderer", "content"]
SINGLE_COLUMN = ["contents", "singleColumnBrowseResultsRenderer"]
SINGLE_COLUMN_TAB = SINGLE_COLUMN + TAB_CONTENT
SECTION_LIST = ["sectionListRenderer", "contents"]
SECTION_LIST_ITEM = ["sectionListRenderer"] + CONTENT
SECTION = ["sectionListRenderer"]
SECTION_LIST = SECTION + ["contents"]
SECTION_LIST_ITEM = SECTION + CONTENT
ITEM_SECTION = ["itemSectionRenderer"] + CONTENT
MUSIC_SHELF = ["musicShelfRenderer"]
GRID = ["gridRenderer"]
Expand Down Expand Up @@ -58,7 +59,9 @@
TASTE_PROFILE_ARTIST = ["title", "runs"]
SECTION_LIST_CONTINUATION = ["continuationContents", "sectionListContinuation"]
MENU_PLAYLIST_ID = MENU_ITEMS + [0, "menuNavigationItemRenderer"] + NAVIGATION_WATCH_PLAYLIST_ID
MULTI_SELECT = ["musicMultiSelectMenuItemRenderer"]
HEADER_DETAIL = ["header", "musicDetailHeaderRenderer"]
HEADER_SIDE = ["header", "musicSideAlignedItemRenderer"]
DESCRIPTION_SHELF = ["musicDescriptionShelfRenderer"]
DESCRIPTION = ["description"] + RUN_TEXT
CAROUSEL = ["musicCarouselShelfRenderer"]
Expand Down

0 comments on commit e864a6b

Please sign in to comment.