diff --git a/README.md b/README.md
index d6bf9e3..82701a2 100644
--- a/README.md
+++ b/README.md
@@ -2285,257 +2285,6 @@ print(Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
"height": 200
}
],
- "availableCountryCodes": [
- "ET",
- "NU",
- "PW",
- "RS",
- "NP",
- "CC",
- "PY",
- "CH",
- "FO",
- "BH",
- "AO",
- "EE",
- "SX",
- "GG",
- "SS",
- "RE",
- "GP",
- "PR",
- "TZ",
- "YT",
- "ZW",
- "SH",
- "SG",
- "GE",
- "SA",
- "GN",
- "TW",
- "SJ",
- "HN",
- "UZ",
- "GR",
- "ML",
- "NC",
- "PE",
- "BY",
- "SE",
- "MO",
- "TO",
- "MU",
- "YE",
- "GB",
- "AI",
- "PS",
- "AQ",
- "RW",
- "FI",
- "KW",
- "CX",
- "CD",
- "LI",
- "BI",
- "MZ",
- "SM",
- "BS",
- "CA",
- "SB",
- "LC",
- "BJ",
- "DO",
- "CO",
- "CL",
- "BM",
- "HR",
- "MK",
- "PG",
- "GI",
- "LS",
- "GU",
- "KP",
- "BO",
- "TC",
- "KN",
- "TJ",
- "AZ",
- "HM",
- "HT",
- "VA",
- "VN",
- "IM",
- "DK",
- "SL",
- "GL",
- "EG",
- "NL",
- "NG",
- "IQ",
- "DZ",
- "PL",
- "TD",
- "LU",
- "CK",
- "ST",
- "TK",
- "AS",
- "VE",
- "CN",
- "MG",
- "AL",
- "BA",
- "MV",
- "AT",
- "FJ",
- "US",
- "VI",
- "AD",
- "DJ",
- "VU",
- "MQ",
- "SY",
- "ES",
- "AW",
- "LY",
- "NZ",
- "TG",
- "CF",
- "NO",
- "RO",
- "CY",
- "GF",
- "KH",
- "BG",
- "IL",
- "MX",
- "TH",
- "AG",
- "LB",
- "TV",
- "GT",
- "KZ",
- "GD",
- "JO",
- "TT",
- "KR",
- "LV",
- "VC",
- "AR",
- "TM",
- "DM",
- "NR",
- "DE",
- "SO",
- "HU",
- "VG",
- "MY",
- "BZ",
- "FK",
- "WS",
- "CR",
- "GS",
- "SV",
- "NA",
- "GW",
- "JM",
- "JP",
- "KI",
- "TR",
- "BW",
- "CI",
- "CW",
- "AU",
- "TF",
- "LA",
- "MS",
- "GH",
- "MN",
- "PN",
- "BT",
- "ZA",
- "AX",
- "GM",
- "IT",
- "IR",
- "MW",
- "UM",
- "MR",
- "CZ",
- "BF",
- "ZM",
- "TL",
- "BV",
- "PA",
- "LT",
- "MC",
- "CG",
- "ID",
- "GA",
- "KY",
- "MP",
- "NE",
- "JE",
- "IO",
- "KM",
- "ER",
- "NF",
- "MF",
- "UG",
- "EH",
- "SR",
- "UY",
- "WF",
- "BL",
- "MD",
- "IE",
- "SK",
- "SC",
- "MA",
- "FM",
- "SZ",
- "GY",
- "MT",
- "IN",
- "SI",
- "PH",
- "CM",
- "SD",
- "CU",
- "KG",
- "PT",
- "BB",
- "AF",
- "BN",
- "LK",
- "SN",
- "OM",
- "PF",
- "BE",
- "CV",
- "AE",
- "AM",
- "BR",
- "BQ",
- "TN",
- "MM",
- "BD",
- "GQ",
- "KE",
- "IS",
- "QA",
- "LR",
- "PM",
- "EC",
- "RU",
- "HK",
- "UA",
- "MH",
- "NI",
- "PK",
- "FR",
- "ME"
- ],
"isFamilySafe": true,
"keywords": "NoCopyrightSounds ncs no copyright sounds copyrighted music free royalty royaltyfree uncopyrighted copyrightfree",
"tags": [
@@ -2560,6 +2309,28 @@ print(Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
+
+#### Retrieve channel playlists
+```py
+from youtubesearchpython import Channel
+
+channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+print(len(channel.result["playlists"]))
+while channel.has_more_playlists():
+ channel.next()
+ print(len(channel.result["playlists"]))
+```
+
+
+ Example Result
+
+```
+30
+49
+```
+
+
+
## Contributors
Thanks to everyone contributing to this library, including those not mentioned here.
@@ -2586,6 +2357,12 @@ Contributors are added irrespective of order.
Maintainer and reviewer of PRs. Author of Hashtag class.
+
+ Fabian Wunsch
+
+ - Fixes to ChannelSearch & retrieving Playlists from Channel class
+
+
Felix Stupp
diff --git a/asyncExample.py b/asyncExample.py
index d901189..4281f74 100644
--- a/asyncExample.py
+++ b/asyncExample.py
@@ -233,6 +233,15 @@ async def main():
print(await Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
+ # Retrieve playlists of a channel
+ channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+ await channel.init()
+ print(len(channel.result["playlists"]))
+ while channel.has_more_playlists():
+ await channel.next()
+ print(len(channel.result["playlists"]))
+
+
'''
diff --git a/syncExample.py b/syncExample.py
index d862f7e..b2f7013 100644
--- a/syncExample.py
+++ b/syncExample.py
@@ -235,6 +235,14 @@
print(Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
+# Retrieve playlists of a channel
+channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+print(len(channel.result["playlists"]))
+while channel.has_more_playlists():
+ channel.next()
+ print(len(channel.result["playlists"]))
+
+
'''
You may add/omit the optional parameters according to your requirement & use case.
diff --git a/tests/async/extras.py b/tests/async/extras.py
index a9b82bb..41dd644 100644
--- a/tests/async/extras.py
+++ b/tests/async/extras.py
@@ -49,5 +49,13 @@ async def main():
print(await Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
+ # Retrieve playlists of a channel
+ channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+ await channel.init()
+ print(len(channel.result["playlists"]))
+ while channel.has_more_playlists():
+ await channel.next()
+ print(len(channel.result["playlists"]))
+
asyncio.run(main())
diff --git a/tests/async/search.py b/tests/async/search.py
index 074ed07..f61a370 100644
--- a/tests/async/search.py
+++ b/tests/async/search.py
@@ -32,8 +32,18 @@ async def main():
print(result)
channel = ChannelSearch('The Beatles - Topic', 'UC2XdaAVUannpujzv32jcouQ')
- result = await search.next()
+ result = await channel.next()
+ print(result)
+
+ """
+ channel = ChannelPlaylistSearch('PewDiePie', 'UC-lHJZR3Gqxm24_Vd_AJ5Yw')
+ result = await channel.next()
+ print(result)
+
+ channel = ChannelPlaylistSearch('The Beatles - Topic', 'UC2XdaAVUannpujzv32jcouQ')
+ result = await channel.next()
print(result)
+ """
search = VideosSearch('NoCopyrightSounds')
diff --git a/tests/sync/extras.py b/tests/sync/extras.py
index eb70651..500f717 100644
--- a/tests/sync/extras.py
+++ b/tests/sync/extras.py
@@ -9,17 +9,14 @@
print(videoFormats)
-
suggestions = Suggestions(language = 'en', region = 'US')
print(suggestions.get('NoCopyrightSounds', mode = ResultMode.json))
-
hashtag = Hashtag('ncs', limit = 1)
print(hashtag.result())
-
fetcher = StreamURLFetcher()
videoA = Video.get("https://www.youtube.com/watch?v=aqz-KE-bpKQ")
videoB = Video.get("https://www.youtube.com/watch?v=ZwNxYJfW-eU")
@@ -30,8 +27,6 @@
print(allUrlsB)
-
-
comments = Comments("_ZdsmLgCVdU")
print(len(comments.comments["result"]))
@@ -42,7 +37,6 @@
print("Found all comments")
-
print(Transcript.get("https://www.youtube.com/watch?v=L7kF4MXXCoA"))
@@ -53,5 +47,12 @@
print(transcript_2)
-
print(Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
+
+
+# Retrieve playlists of a channel
+channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+print(len(channel.result["playlists"]))
+while channel.has_more_playlists():
+ channel.next()
+ print(len(channel.result["playlists"]))
diff --git a/tests/sync/search.py b/tests/sync/search.py
index 007cf73..8b861b2 100644
--- a/tests/sync/search.py
+++ b/tests/sync/search.py
@@ -42,3 +42,9 @@
channel = ChannelSearch('The Beatles - Topic', 'UC2XdaAVUannpujzv32jcouQ')
print(channel.result(mode=ResultMode.json))
+
+#channel = ChannelPlaylistSearch('PewDiePie', 'UC-lHJZR3Gqxm24_Vd_AJ5Yw')
+#print(channel.result())
+
+#channel = ChannelPlaylistSearch('The Beatles - Topic', 'UC2XdaAVUannpujzv32jcouQ')
+#print(channel.result())
diff --git a/youtubesearchpython/__future__/README.md b/youtubesearchpython/__future__/README.md
index 91ac858..5805816 100644
--- a/youtubesearchpython/__future__/README.md
+++ b/youtubesearchpython/__future__/README.md
@@ -2012,257 +2012,6 @@ print(await Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
"height": 200
}
],
- "availableCountryCodes": [
- "ET",
- "NU",
- "PW",
- "RS",
- "NP",
- "CC",
- "PY",
- "CH",
- "FO",
- "BH",
- "AO",
- "EE",
- "SX",
- "GG",
- "SS",
- "RE",
- "GP",
- "PR",
- "TZ",
- "YT",
- "ZW",
- "SH",
- "SG",
- "GE",
- "SA",
- "GN",
- "TW",
- "SJ",
- "HN",
- "UZ",
- "GR",
- "ML",
- "NC",
- "PE",
- "BY",
- "SE",
- "MO",
- "TO",
- "MU",
- "YE",
- "GB",
- "AI",
- "PS",
- "AQ",
- "RW",
- "FI",
- "KW",
- "CX",
- "CD",
- "LI",
- "BI",
- "MZ",
- "SM",
- "BS",
- "CA",
- "SB",
- "LC",
- "BJ",
- "DO",
- "CO",
- "CL",
- "BM",
- "HR",
- "MK",
- "PG",
- "GI",
- "LS",
- "GU",
- "KP",
- "BO",
- "TC",
- "KN",
- "TJ",
- "AZ",
- "HM",
- "HT",
- "VA",
- "VN",
- "IM",
- "DK",
- "SL",
- "GL",
- "EG",
- "NL",
- "NG",
- "IQ",
- "DZ",
- "PL",
- "TD",
- "LU",
- "CK",
- "ST",
- "TK",
- "AS",
- "VE",
- "CN",
- "MG",
- "AL",
- "BA",
- "MV",
- "AT",
- "FJ",
- "US",
- "VI",
- "AD",
- "DJ",
- "VU",
- "MQ",
- "SY",
- "ES",
- "AW",
- "LY",
- "NZ",
- "TG",
- "CF",
- "NO",
- "RO",
- "CY",
- "GF",
- "KH",
- "BG",
- "IL",
- "MX",
- "TH",
- "AG",
- "LB",
- "TV",
- "GT",
- "KZ",
- "GD",
- "JO",
- "TT",
- "KR",
- "LV",
- "VC",
- "AR",
- "TM",
- "DM",
- "NR",
- "DE",
- "SO",
- "HU",
- "VG",
- "MY",
- "BZ",
- "FK",
- "WS",
- "CR",
- "GS",
- "SV",
- "NA",
- "GW",
- "JM",
- "JP",
- "KI",
- "TR",
- "BW",
- "CI",
- "CW",
- "AU",
- "TF",
- "LA",
- "MS",
- "GH",
- "MN",
- "PN",
- "BT",
- "ZA",
- "AX",
- "GM",
- "IT",
- "IR",
- "MW",
- "UM",
- "MR",
- "CZ",
- "BF",
- "ZM",
- "TL",
- "BV",
- "PA",
- "LT",
- "MC",
- "CG",
- "ID",
- "GA",
- "KY",
- "MP",
- "NE",
- "JE",
- "IO",
- "KM",
- "ER",
- "NF",
- "MF",
- "UG",
- "EH",
- "SR",
- "UY",
- "WF",
- "BL",
- "MD",
- "IE",
- "SK",
- "SC",
- "MA",
- "FM",
- "SZ",
- "GY",
- "MT",
- "IN",
- "SI",
- "PH",
- "CM",
- "SD",
- "CU",
- "KG",
- "PT",
- "BB",
- "AF",
- "BN",
- "LK",
- "SN",
- "OM",
- "PF",
- "BE",
- "CV",
- "AE",
- "AM",
- "BR",
- "BQ",
- "TN",
- "MM",
- "BD",
- "GQ",
- "KE",
- "IS",
- "QA",
- "LR",
- "PM",
- "EC",
- "RU",
- "HK",
- "UA",
- "MH",
- "NI",
- "PK",
- "FR",
- "ME"
- ],
"isFamilySafe": true,
"keywords": "NoCopyrightSounds ncs no copyright sounds copyrighted music free royalty royaltyfree uncopyrighted copyrightfree",
"tags": [
@@ -2286,6 +2035,27 @@ print(await Channel.get("UC_aEa8K-EOJ3D6gOs7HcyNg"))
```
+#### Retrieve channel playlists
+```py
+from youtubesearchpython.__future__ import Channel
+
+channel = Channel("UC_aEa8K-EOJ3D6gOs7HcyNg")
+await channel.init()
+print(len(channel.result["playlists"]))
+while channel.has_more_playlists():
+ await channel.next()
+ print(len(channel.result["playlists"]))
+```
+
+
+ Example Result
+
+```
+30
+49
+```
+
+
## Dependencies
- Thanks to [Encode](https://github.com/encode) for [httpx](https://github.com/encode/httpx).
diff --git a/youtubesearchpython/__future__/extras.py b/youtubesearchpython/__future__/extras.py
index c7008e5..1fddd7c 100644
--- a/youtubesearchpython/__future__/extras.py
+++ b/youtubesearchpython/__future__/extras.py
@@ -3,7 +3,7 @@
from youtubesearchpython.core import VideoCore
from youtubesearchpython.core.comments import CommentsCore
-from youtubesearchpython.core.constants import ResultMode
+from youtubesearchpython.core.constants import ResultMode, ChannelRequestType
from youtubesearchpython.core.hashtag import HashtagCore
from youtubesearchpython.core.playlist import PlaylistCore
from youtubesearchpython.core.suggestions import SuggestionsCore
@@ -13,7 +13,8 @@
class Video:
@staticmethod
- async def get(videoLink: str, resultMode: int = ResultMode.dict, timeout: int = 2, get_upload_date: bool = False) -> Union[dict, None]:
+ async def get(videoLink: str, resultMode: int = ResultMode.dict, timeout: int = 2, get_upload_date: bool = False) -> \
+ Union[dict, None]:
'''Fetches information and formats for the given video link or ID.
Returns None if video is unavailable.
@@ -263,7 +264,7 @@ async def get(videoLink: str, resultMode: int = ResultMode.dict, timeout: int =
await video.async_html_create()
await video.async_create()
return video.result
-
+
@staticmethod
async def getInfo(videoLink: str, resultMode: int = ResultMode.dict, timeout: int = 2) -> Union[dict, None]:
'''Fetches only information for the given video link or ID.
@@ -567,6 +568,7 @@ class Suggestions:
]
}
'''
+
@staticmethod
async def get(query: str, language: str = 'en', region: str = 'US', mode: int = ResultMode.dict):
'''Fetches & returns the search suggestions for the given query.
@@ -578,7 +580,7 @@ async def get(query: str, language: str = 'en', region: str = 'US', mode: int =
Returns:
Union[str, dict]: Returns JSON or dictionary.
'''
- suggestionsInternal = SuggestionsCore(language = language, region = region)
+ suggestionsInternal = SuggestionsCore(language=language, region=region)
suggestions = await suggestionsInternal._getAsync(query, mode)
return suggestions
@@ -611,6 +613,7 @@ def __init__(self, playlistLink: str):
'''Fetches more susequent videos of the playlist, and appends to the `videos` list.
`hasMoreVideos` bool indicates whether more videos can be fetched or not.
'''
+
async def getNextVideos(self) -> None:
if not self.info:
self.__playlist = PlaylistCore(self.playlistLink, None, ResultMode.dict, 2)
@@ -623,7 +626,7 @@ async def getNextVideos(self) -> None:
await self.__playlist._async_next()
self.videos = self.__playlist.playlistComponent['videos']
self.hasMoreVideos = self.__playlist.continuationKey != None
-
+
@staticmethod
async def get(playlistLink: str) -> Union[dict, str, None]:
'''Fetches information and videos for the given playlist link.
@@ -1176,7 +1179,7 @@ async def get(playlistLink: str) -> Union[dict, str, None]:
playlist = PlaylistCore(playlistLink, None, ResultMode.dict, 2)
await playlist.async_create()
return playlist.playlistComponent
-
+
@staticmethod
async def getInfo(playlistLink: str) -> Union[dict, str, None]:
'''Fetches only information for the given playlist link.
@@ -1825,6 +1828,7 @@ class Hashtag(HashtagCore):
]
}
'''
+
def __init__(self, hashtag: str, limit: int = 60, language: str = 'en', region: str = 'US', timeout: int = None):
super().__init__(hashtag, limit, language, region, timeout)
@@ -1876,9 +1880,19 @@ async def get(videoLink: str, params: str = None):
await transcript_core.async_create()
return transcript_core.result
-class Channel:
+
+class Channel(ChannelCore):
+ def __init__(self, channel_id: str, request_type: str = ChannelRequestType.playlists):
+ super().__init__(channel_id, request_type)
+
+ async def init(self):
+ await self.async_create()
+
+ async def next(self):
+ await self.async_next()
+
@staticmethod
- async def get(channelId: str):
- channel_core = ChannelCore(channelId)
+ async def get(channel_id: str, request_type: str = ChannelRequestType.playlists):
+ channel_core = ChannelCore(channel_id, request_type)
await channel_core.async_create()
return channel_core.result
diff --git a/youtubesearchpython/core/__init__.py b/youtubesearchpython/core/__init__.py
index bbeecec..0a181de 100644
--- a/youtubesearchpython/core/__init__.py
+++ b/youtubesearchpython/core/__init__.py
@@ -1,2 +1,2 @@
from .video import VideoCore
-from .constants import *
\ No newline at end of file
+from .constants import *
diff --git a/youtubesearchpython/core/channel.py b/youtubesearchpython/core/channel.py
index 6856916..f1c83df 100644
--- a/youtubesearchpython/core/channel.py
+++ b/youtubesearchpython/core/channel.py
@@ -8,11 +8,13 @@
from youtubesearchpython.core.componenthandler import getValue, getVideoId
-
class ChannelCore(RequestCore):
- def __init__(self, channelId: str):
+ def __init__(self, channel_id: str, request_params: str):
super().__init__()
- self.browseId = channelId
+ self.browseId = channel_id
+ self.params = request_params
+ self.result = {}
+ self.continuation = None
def prepare_request(self):
self.url = 'https://www.youtube.com/youtubei/v1/browse' + "?" + urlencode({
@@ -20,26 +22,62 @@ def prepare_request(self):
"prettyPrint": "false"
})
self.data = copy.deepcopy(requestPayload)
- self.data["params"] = "EgVhYm91dA%3D%3D"
- self.data["browseId"] = self.browseId
+ if not self.continuation:
+ self.data["params"] = self.params
+ self.data["browseId"] = self.browseId
+ else:
+ self.data["continuation"] = self.continuation
+
+ def playlist_parse(self, i) -> dict:
+ return {
+ "id": getValue(i, ["playlistId"]),
+ "thumbnails": getValue(i, ["thumbnail", "thumbnails"]),
+ "title": getValue(i, ["title", "runs", 0, "text"]),
+ "videoCount": getValue(i, ["videoCountShortText", "simpleText"]),
+ "lastEdited": getValue(i, ["publishedTimeText", "simpleText"]),
+ }
def parse_response(self):
response = self.data.json()
thumbnails = []
- thumbnails.extend(getValue(response, ["header", "c4TabbedHeaderRenderer", "avatar", "thumbnails"]))
- thumbnails.extend(getValue(response, ["metadata", "channelMetadataRenderer", "avatar", "thumbnails"]))
- thumbnails.extend(getValue(response, ["microformat", "microformatDataRenderer", "thumbnail", "thumbnails"]))
-
- tabData = {}
+ try:
+ thumbnails.extend(getValue(response, ["header", "c4TabbedHeaderRenderer", "avatar", "thumbnails"]))
+ except:
+ pass
+ try:
+ thumbnails.extend(getValue(response, ["metadata", "channelMetadataRenderer", "avatar", "thumbnails"]))
+ except:
+ pass
+ try:
+ thumbnails.extend(getValue(response, ["microformat", "microformatDataRenderer", "thumbnail", "thumbnails"]))
+ except:
+ pass
+
+ tabData: dict = {}
+ playlists: list = []
for tab in getValue(response, ["contents", "twoColumnBrowseResultsRenderer", "tabs"]):
- t = getValue(tab, ["tabRenderer", "selected"])
- if t:
+ tab: dict
+ title = getValue(tab, ["tabRenderer", "title"])
+ if title == "Playlists":
+ playlist = getValue(tab,
+ ["tabRenderer", "content", "sectionListRenderer", "contents", 0, "itemSectionRenderer",
+ "contents", 0, "gridRenderer", "items"])
+ if playlist is not None and getValue(playlist, [0, "gridPlaylistRenderer"]):
+ for i in playlist:
+ if getValue(i, ["continuationItemRenderer"]):
+ self.continuation = getValue(i, ["continuationItemRenderer", "continuationEndpoint",
+ "continuationCommand", "token"])
+ break
+ i: dict = i["gridPlaylistRenderer"]
+ playlists.append(self.playlist_parse(i))
+ elif title == "About":
tabData = tab["tabRenderer"]
-
- metadata = getValue(tabData, ["content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0, "channelAboutFullMetadataRenderer"])
+ metadata = getValue(tabData,
+ ["content", "sectionListRenderer", "contents", 0, "itemSectionRenderer", "contents", 0,
+ "channelAboutFullMetadataRenderer"])
self.result = {
"id": getValue(response, ["metadata", "channelMetadataRenderer", "externalId"]),
@@ -48,28 +86,60 @@ def parse_response(self):
"title": getValue(response, ["metadata", "channelMetadataRenderer", "title"]),
"banners": getValue(response, ["header", "c4TabbedHeaderRenderer", "banner", "thumbnails"]),
"subscribers": {
- "simpleText": getValue(response, ["header", "c4TabbedHeaderRenderer", "subscriberCountText", "simpleText"]),
- "label": getValue(response, ["header", "c4TabbedHeaderRenderer", "subscriberCountText", "accessibility", "accessibilityData", "label"])
+ "simpleText": getValue(response,
+ ["header", "c4TabbedHeaderRenderer", "subscriberCountText", "simpleText"]),
+ "label": getValue(response, ["header", "c4TabbedHeaderRenderer", "subscriberCountText", "accessibility",
+ "accessibilityData", "label"])
},
"thumbnails": thumbnails,
- "availableCountryCodes": getValue(response, ["metadata", "channelMetadataRenderer", "availableCountryCodes"]),
+ "availableCountryCodes": getValue(response,
+ ["metadata", "channelMetadataRenderer", "availableCountryCodes"]),
"isFamilySafe": getValue(response, ["metadata", "channelMetadataRenderer", "isFamilySafe"]),
"keywords": getValue(response, ["metadata", "channelMetadataRenderer", "keywords"]),
"tags": getValue(response, ["microformat", "microformatDataRenderer", "tags"]),
- "views": getValue(metadata, ["viewCountText", "simpleText"]),
- "joinedDate": getValue(metadata, ["joinedDateText", "runs", -1, "text"]),
- "country": getValue(metadata, ["country", "simpleText"])
+ "views": getValue(metadata, ["viewCountText", "simpleText"]) if metadata else None,
+ "joinedDate": getValue(metadata, ["joinedDateText", "runs", -1, "text"]) if metadata else None,
+ "country": getValue(metadata, ["country", "simpleText"]) if metadata else None,
+ "playlists": playlists,
}
+ def parse_next_response(self):
+ response = self.data.json()
+
+ self.continuation = None
+
+ response = getValue(response, ["onResponseReceivedActions", 0, "appendContinuationItemsAction", "continuationItems"])
+ for i in response:
+ if getValue(i, ["continuationItemRenderer"]):
+ self.continuation = getValue(i, ["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"])
+ break
+ elif getValue(i, ['gridPlaylistRenderer']):
+ self.result["playlists"].append(self.playlist_parse(getValue(i, ['gridPlaylistRenderer'])))
+ # TODO: Handle other types like gridShowRenderer
+
+ async def async_next(self):
+ if not self.continuation:
+ return
+ self.prepare_request()
+ self.data = await self.asyncPostRequest()
+ self.parse_next_response()
+
+ def sync_next(self):
+ if not self.continuation:
+ return
+ self.prepare_request()
+ self.data = self.syncPostRequest()
+ self.parse_next_response()
+
+ def has_more_playlists(self):
+ return self.continuation is not None
async def async_create(self):
self.prepare_request()
self.data = await self.asyncPostRequest()
self.parse_response()
-
+
def sync_create(self):
self.prepare_request()
self.data = self.syncPostRequest()
self.parse_response()
-
-
diff --git a/youtubesearchpython/core/channelsearch.py b/youtubesearchpython/core/channelsearch.py
index 9550ec4..aaa8397 100644
--- a/youtubesearchpython/core/channelsearch.py
+++ b/youtubesearchpython/core/channelsearch.py
@@ -7,6 +7,7 @@
from youtubesearchpython.handlers.componenthandler import ComponentHandler
from youtubesearchpython.core.constants import *
+
class ChannelSearchCore(RequestCore, ComponentHandler):
response = None
responseSource = None
@@ -84,14 +85,13 @@ async def _asyncRequest(self) -> None:
def result(self, mode: int = ResultMode.dict) -> Union[str, dict]:
'''Returns the search result.
-
Args:
mode (int, optional): Sets the type of result. Defaults to ResultMode.dict.
-
Returns:
Union[str, dict]: Returns JSON or dictionary.
'''
if mode == ResultMode.json:
return json.dumps({'result': self.response}, indent=4)
elif mode == ResultMode.dict:
- return {'result': self.response}
\ No newline at end of file
+ return {'result': self.response}
+
diff --git a/youtubesearchpython/core/constants.py b/youtubesearchpython/core/constants.py
index 8e84d8b..f50f492 100644
--- a/youtubesearchpython/core/constants.py
+++ b/youtubesearchpython/core/constants.py
@@ -67,3 +67,8 @@ class VideoSortOrder:
uploadDate = 'CAISAhAB'
viewCount = 'CAMSAhAB'
rating = 'CAESAhAB'
+
+
+class ChannelRequestType:
+ info = "EgVhYm91dA%3D%3D"
+ playlists = "EglwbGF5bGlzdHMYAyABcAA%3D"
diff --git a/youtubesearchpython/extras.py b/youtubesearchpython/extras.py
index 423d6e2..d442ac2 100644
--- a/youtubesearchpython/extras.py
+++ b/youtubesearchpython/extras.py
@@ -1828,10 +1828,17 @@ def get(videoLink: str, params: str = None):
transcript_core.sync_create()
return transcript_core.result
-class Channel:
+
+class Channel(ChannelCore):
+ def __init__(self, channel_id: str, request_type: str = ChannelRequestType.playlists):
+ super().__init__(channel_id, request_type)
+ self.sync_create()
+
+ def next(self):
+ self.sync_next()
+
@staticmethod
- def get(channelId: str):
- channel_core = ChannelCore(channelId)
+ def get(channel_id: str, request_type: str = ChannelRequestType.info):
+ channel_core = ChannelCore(channel_id, request_type)
channel_core.sync_create()
return channel_core.result
-
diff --git a/youtubesearchpython/handlers/componenthandler.py b/youtubesearchpython/handlers/componenthandler.py
index 4dbb7b1..5f0c2d2 100644
--- a/youtubesearchpython/handlers/componenthandler.py
+++ b/youtubesearchpython/handlers/componenthandler.py
@@ -85,12 +85,25 @@ def _getChannelSearchComponent(self, elements: list) -> list:
for element in elements:
responsetype = None
- try:
- element = element["itemSectionRenderer"]["contents"][0]["videoRenderer"]
- responsetype = "video"
- except:
- element = element["itemSectionRenderer"]["contents"][0]["playlistRenderer"]
- responsetype = "playlist"
+ if 'gridPlaylistRenderer' in element:
+ element = element['gridPlaylistRenderer']
+ responsetype = 'gridplaylist'
+ elif 'itemSectionRenderer' in element:
+ first_content = element["itemSectionRenderer"]["contents"][0]
+ if 'videoRenderer' in first_content:
+ element = first_content['videoRenderer']
+ responsetype = "video"
+ elif 'playlistRenderer' in first_content:
+ element = first_content["playlistRenderer"]
+ responsetype = "playlist"
+ else:
+ raise Exception(f'Unexpected first_content {first_content}')
+ elif 'continuationItemRenderer' in element:
+ # for endless scrolling, not needed here
+ # TODO: Implement endless scrolling
+ continue
+ else:
+ raise Exception(f'Unexpected element {element}')
if responsetype == "video":
json = {
@@ -118,7 +131,7 @@ def _getChannelSearchComponent(self, elements: list) -> list:
},
"type": responsetype
}
- else:
+ elif responsetype == 'playlist':
json = {
"id": self._getValue(element, ["playlistId"]),
"videos": self._getVideoFromChannelSearch(self._getValue(element, ["videos"])),
@@ -132,6 +145,16 @@ def _getChannelSearchComponent(self, elements: list) -> list:
},
"type": responsetype
}
+ else:
+ json = {
+ "id": self._getValue(element, ["playlistId"]),
+ "thumbnails": {
+ "normal": self._getValue(element, ["thumbnail", "thumbnails", 0]),
+ },
+ "title": self._getValue(element, ["title", "runs", 0, "text"]),
+ "uri": self._getValue(element, ["navigationEndpoint", "commandMetadata", "webCommandMetadata", "url"]),
+ "type": 'playlist'
+ }
channelsearch.append(json)
return channelsearch