diff --git a/docs/source/reference.rst b/docs/source/reference.rst index d568663b..8623d398 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -29,6 +29,7 @@ Browsing .. automethod:: YTMusic.get_album_browse_id .. automethod:: YTMusic.get_user .. automethod:: YTMusic.get_user_playlists +.. automethod:: YTMusic.get_user_videos .. automethod:: YTMusic.get_song .. automethod:: YTMusic.get_song_related .. automethod:: YTMusic.get_lyrics diff --git a/tests/mixins/test_browsing.py b/tests/mixins/test_browsing.py index a8d9a7e3..51a1496d 100644 --- a/tests/mixins/test_browsing.py +++ b/tests/mixins/test_browsing.py @@ -68,6 +68,15 @@ def test_get_user_playlists(self, yt, yt_auth): results_empty = yt.get_user_playlists(channel, user["playlists"]["params"]) assert len(results_empty) == 0 + def test_get_user_videos(self, yt, yt_auth): + channel = "UCus8EVJ7Oc9zINhs-fg8l1Q" # Turbo + user = yt_auth.get_user(channel) + results = yt_auth.get_user_videos(channel, user["videos"]["params"]) + assert len(results) > 100 + + results_empty = yt.get_user_videos(channel, user["videos"]["params"]) + assert len(results_empty) == 0 + def test_get_album_browse_id(self, yt, sample_album): warnings.filterwarnings(action="ignore", category=DeprecationWarning) browse_id = yt.get_album_browse_id("OLAK5uy_nMr9h2VlS-2PULNz3M3XVXQj_P3C2bqaY") diff --git a/ytmusicapi/mixins/browsing.py b/ytmusicapi/mixins/browsing.py index 09f51441..c3467b72 100644 --- a/ytmusicapi/mixins/browsing.py +++ b/ytmusicapi/mixins/browsing.py @@ -8,7 +8,13 @@ ) from ytmusicapi.helpers import YTM_DOMAIN, sum_total_duration from ytmusicapi.parsers.albums import parse_album_header_2024 -from ytmusicapi.parsers.browsing import parse_album, parse_content_list, parse_mixed_content, parse_playlist +from ytmusicapi.parsers.browsing import ( + parse_album, + parse_content_list, + parse_mixed_content, + parse_playlist, + parse_video, +) from ytmusicapi.parsers.library import parse_albums from ytmusicapi.parsers.playlists import parse_playlist_items @@ -355,6 +361,15 @@ def get_user(self, channelId: str) -> dict: """ Retrieve a user's page. A user may own videos or playlists. + Use :py:func:`get_user_playlists` to retrieve all playlists:: + + result = get_user(channelId) + get_user_playlists(channelId, result["playlists"]["params"]) + + Similarly, use :py:func:`get_user_videos` to retrieve all videos:: + + get_user_videos(channelId, result["videos"]["params"]) + :param channelId: channelId of the user :return: Dictionary with information about a user. @@ -413,7 +428,7 @@ def get_user_playlists(self, channelId: str, params: str) -> list[dict]: Call this function again with the returned ``params`` to get the full list. :param channelId: channelId of the user. - :param params: params obtained by :py:func:`get_artist` + :param params: params obtained by :py:func:`get_user` :return: List of user playlists in the format of :py:func:`get_library_playlists` """ @@ -428,6 +443,27 @@ def get_user_playlists(self, channelId: str, params: str) -> list[dict]: return user_playlists + def get_user_videos(self, channelId: str, params: str) -> list[dict]: + """ + Retrieve a list of videos for a given user. + Call this function again with the returned ``params`` to get the full list. + + :param channelId: channelId of the user. + :param params: params obtained by :py:func:`get_user` + :return: List of user videos + + """ + endpoint = "browse" + body = {"browseId": channelId, "params": params} + response = self._send_request(endpoint, body) + results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST_ITEM + GRID_ITEMS, True) + if not results: + return [] + + user_videos = parse_content_list(results, parse_video) + + return user_videos + def get_album_browse_id(self, audioPlaylistId: str) -> Optional[str]: """ Get an album's browseId based on its audioPlaylistId