Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions tests/parsers/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
OWNED_PLAYLIST = {
"thumbnailRenderer": {"musicThumbnailRenderer": {"thumbnail": {"thumbnails": []}}},
"title": {
"runs": [
{
"text": "Family playlist",
"navigationEndpoint": {"browseEndpoint": {"browseId": "VLPL_family"}},
}
]
},
"subtitle": {
"runs": [
{
"text": "Owner",
"navigationEndpoint": {"browseEndpoint": {"browseId": "UC_owner"}},
},
{"text": " • "},
{"text": "1 track"},
]
},
"menu": {
"menuRenderer": {
"items": [
{
"menuNavigationItemRenderer": {
"text": {"runs": [{"text": "Edit playlist"}]},
"navigationEndpoint": {"playlistEditorEndpoint": {"playlistId": "PL_family"}},
}
}
]
}
},
}
20 changes: 20 additions & 0 deletions tests/parsers/test_browsing.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
from copy import deepcopy

import pytest

from tests.parsers.data import OWNED_PLAYLIST
from ytmusicapi.navigation import MRLIR, MTRIR
from ytmusicapi.parsers.browsing import parse_content_list, parse_playlist


def test_parse_playlist_marks_playlist_with_editor_endpoint_as_owned():
playlist = parse_playlist(OWNED_PLAYLIST)

assert playlist["owned"] is True


def test_parse_playlist_marks_playlist_without_editor_endpoint_as_not_owned():
saved_playlist = deepcopy(OWNED_PLAYLIST)
saved_playlist["menu"]["menuRenderer"]["items"][0]["menuNavigationItemRenderer"]["navigationEndpoint"] = {
"watchPlaylistEndpoint": {"playlistId": "PL_family"}
}

playlist = parse_playlist(saved_playlist)

assert playlist["owned"] is False


def _playlist_item(thumbnail_renderer: dict) -> dict:
return {
"title": {
Expand Down
7 changes: 4 additions & 3 deletions ytmusicapi/mixins/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ def get_library_playlists(self, limit: int | None = 25) -> JsonList:
Retrieves the playlists in the user's library.

:param limit: Number of playlists to retrieve. ``None`` retrieves them all.
:return: List of owned playlists.
:return: List of playlists in the user's library.

Each item is in the following format::

{
'playlistId': 'PLQwVIlKxHM6rz0fDJVv_0UlXGEWf-bFys',
'title': 'Playlist title',
'thumbnails: [...],
'count': 5
'thumbnails': [...],
'count': '5',
'owned': True
}
"""
self._check_auth()
Expand Down
18 changes: 17 additions & 1 deletion ytmusicapi/parsers/browsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,30 @@ def parse_video(result: JsonDict) -> JsonDict:


def parse_playlist(data: JsonDict) -> JsonDict:
playlist_id = nav(data, TITLE + NAVIGATION_BROWSE_ID)[2:]
menu_items = nav(data, ["menu", "menuRenderer", "items"], True) or []
playlist = {
"title": nav(
data,
TITLE_TEXT,
none_if_absent=True, # rare but possible for playlist title to be missing
),
"playlistId": nav(data, TITLE + NAVIGATION_BROWSE_ID)[2:],
"playlistId": playlist_id,
"thumbnails": nav(data, THUMBNAIL_RENDERER, True),
"owned": any(
nav(
item,
[
"menuNavigationItemRenderer",
"navigationEndpoint",
"playlistEditorEndpoint",
"playlistId",
],
True,
)
== playlist_id
for item in menu_items
),
}
subtitle = data["subtitle"]
if "runs" in subtitle:
Expand Down
Loading