Skip to content

Commit e26e502

Browse files
authored
Update channelsearch.py
1 parent b14ef85 commit e26e502

1 file changed

Lines changed: 98 additions & 59 deletions

File tree

Lines changed: 98 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
import json
21
import copy
3-
from typing import Union
2+
from typing import Union, Optional
3+
import json
44
from urllib.parse import urlencode
55

6-
from youtubesearchpython.core.constants import *
76
from youtubesearchpython.core.requests import RequestCore
8-
from youtubesearchpython.handlers.componenthandler import ComponentHandler
7+
from youtubesearchpython.core.componenthandler import ComponentHandler
8+
from youtubesearchpython.core.constants import *
9+
from youtubesearchpython.core.exceptions import YouTubeRequestError, YouTubeParseError
10+
import httpx
911

1012

1113
class ChannelSearchCore(RequestCore, ComponentHandler):
1214
response = None
1315
responseSource = None
16+
resultComponents = []
1417

1518
def __init__(self, query: str, language: str, region: str, searchPreferences: str, browseId: str, timeout: int):
1619
super().__init__()
@@ -21,7 +24,6 @@ def __init__(self, query: str, language: str, region: str, searchPreferences: st
2124
self.searchPreferences = searchPreferences
2225
self.continuationKey = None
2326
self.timeout = timeout
24-
self.resultComponents = []
2527

2628
def sync_create(self):
2729
self._syncRequest()
@@ -32,79 +34,116 @@ async def next(self):
3234
await self._asyncRequest()
3335
self._parseChannelSearchSource()
3436
self.response = self._getChannelSearchComponent(self.response)
35-
return self.response
37+
return {'result': self.response}
3638

3739
def _parseChannelSearchSource(self) -> None:
3840
try:
39-
contents = self.response.get("contents", {})
40-
41-
tabs = (
42-
contents.get("twoColumnBrowseResultsRenderer", {}).get("tabs")
43-
or contents.get("singleColumnBrowseResultsRenderer", {}).get("tabs")
44-
or []
45-
)
46-
47-
last_tab = tabs[-1] if tabs else {}
48-
49-
if "expandableTabRenderer" in last_tab:
50-
self.response = (
51-
last_tab["expandableTabRenderer"]
52-
.get("content", {})
53-
.get("sectionListRenderer", {})
54-
.get("contents", [])
55-
)
56-
else:
57-
tab_renderer = last_tab.get("tabRenderer", {})
58-
content = tab_renderer.get("content")
59-
if content and "sectionListRenderer" in content:
60-
self.response = content["sectionListRenderer"].get("contents", [])
41+
# Try to get tabs from response
42+
tabs = self.response.get("contents", {}).get("twoColumnBrowseResultsRenderer", {}).get("tabs", [])
43+
if not tabs:
44+
# Try alternative structure
45+
tabs = self.response.get("contents", {}).get("singleColumnBrowseResultsRenderer", {}).get("tabs", [])
46+
47+
if not tabs:
48+
self.response = []
49+
return
50+
51+
# Get the last tab (usually the search results tab)
52+
last_tab = tabs[-1]
53+
54+
# Try expandableTabRenderer first
55+
if 'expandableTabRenderer' in last_tab:
56+
expandable = last_tab["expandableTabRenderer"]
57+
# Check if content exists
58+
if 'content' in expandable:
59+
content = expandable["content"]
60+
if 'sectionListRenderer' in content:
61+
self.response = content["sectionListRenderer"].get("contents", [])
62+
else:
63+
self.response = []
64+
else:
65+
# Try to get from expandableTabRenderer directly
66+
if 'sectionListRenderer' in expandable:
67+
self.response = expandable["sectionListRenderer"].get("contents", [])
68+
else:
69+
self.response = []
70+
# Try tabRenderer
71+
elif 'tabRenderer' in last_tab:
72+
tab_renderer = last_tab["tabRenderer"]
73+
if 'content' in tab_renderer:
74+
content = tab_renderer["content"]
75+
if 'sectionListRenderer' in content:
76+
self.response = content["sectionListRenderer"].get("contents", [])
77+
else:
78+
self.response = []
6179
else:
6280
self.response = []
63-
except Exception:
64-
raise Exception("ERROR: Could not parse YouTube response.")
81+
else:
82+
self.response = []
83+
except (KeyError, AttributeError, IndexError) as e:
84+
raise YouTubeParseError(f'Failed to parse YouTube response: {str(e)}')
85+
except Exception as e:
86+
raise YouTubeParseError(f'Unexpected error parsing response: {str(e)}')
6587

6688
def _getRequestBody(self):
89+
''' Fixes #47 '''
6790
requestBody = copy.deepcopy(requestPayload)
68-
requestBody["query"] = self.query or ""
69-
70-
context = requestBody.setdefault("context", {})
71-
client = context.setdefault("client", {})
72-
client.update({
73-
"hl": self.language or client.get("hl"),
74-
"gl": self.region or client.get("gl"),
91+
requestBody['query'] = self.query
92+
requestBody['client'] = {
93+
'hl': self.language,
94+
'gl': self.region,
95+
}
96+
requestBody['params'] = self.searchPreferences
97+
requestBody['browseId'] = self.browseId
98+
self.url = 'https://www.youtube.com/youtubei/v1/browse' + '?' + urlencode({
99+
'key': searchKey,
75100
})
76-
77-
if self.searchPreferences:
78-
requestBody["params"] = self.searchPreferences
79-
if self.browseId:
80-
requestBody["browseId"] = self.browseId
81-
if self.continuationKey:
82-
requestBody["continuation"] = self.continuationKey
83-
84-
if not searchKey:
85-
raise Exception("INNERTUBE API key (searchKey) is not set.")
86-
87-
self.url = "https://www.youtube.com/youtubei/v1/browse?" + urlencode({"key": searchKey})
88101
self.data = requestBody
89102

90103
def _syncRequest(self) -> None:
104+
''' Fixes #47 '''
91105
self._getRequestBody()
92-
request = self.syncPostRequest()
106+
93107
try:
94-
self.response = request.json() if hasattr(request, "json") else json.loads(request.text)
95-
except Exception:
96-
raise Exception("ERROR: Could not make request.")
108+
request = self.syncPostRequest()
109+
if request.status_code != 200:
110+
raise YouTubeRequestError(f'Request failed with status code {request.status_code}. URL: {self.url}')
111+
self.response = request.json()
112+
except httpx.RequestError as e:
113+
raise YouTubeRequestError(f'Failed to make request to {self.url}: {str(e)}')
114+
except httpx.HTTPStatusError as e:
115+
raise YouTubeRequestError(f'HTTP error {e.response.status_code} for {self.url}: {str(e)}')
116+
except json.JSONDecodeError as e:
117+
raise YouTubeRequestError(f'Failed to decode JSON response: {str(e)}')
118+
except Exception as e:
119+
raise YouTubeRequestError(f'Unexpected error making request: {str(e)}')
97120

98121
async def _asyncRequest(self) -> None:
122+
''' Fixes #47 '''
99123
self._getRequestBody()
100-
request = await self.asyncPostRequest()
124+
101125
try:
102-
self.response = request.json() if hasattr(request, "json") else json.loads(request.text)
103-
except Exception:
104-
raise Exception("ERROR: Could not make request.")
126+
request = await self.asyncPostRequest()
127+
if request.status_code != 200:
128+
raise YouTubeRequestError(f'Request failed with status code {request.status_code}. URL: {self.url}')
129+
self.response = request.json()
130+
except httpx.RequestError as e:
131+
raise YouTubeRequestError(f'Failed to make request to {self.url}: {str(e)}')
132+
except httpx.HTTPStatusError as e:
133+
raise YouTubeRequestError(f'HTTP error {e.response.status_code} for {self.url}: {str(e)}')
134+
except json.JSONDecodeError as e:
135+
raise YouTubeRequestError(f'Failed to decode JSON response: {str(e)}')
136+
except Exception as e:
137+
raise YouTubeRequestError(f'Unexpected error making request: {str(e)}')
105138

106139
def result(self, mode: int = ResultMode.dict) -> Union[str, dict]:
140+
'''Returns the search result.
141+
Args:
142+
mode (int, optional): Sets the type of result. Defaults to ResultMode.dict.
143+
Returns:
144+
Union[str, dict]: Returns JSON or dictionary.
145+
'''
107146
if mode == ResultMode.json:
108-
return json.dumps({"result": self.response}, indent=4)
147+
return json.dumps({'result': self.response}, indent=4)
109148
elif mode == ResultMode.dict:
110-
return {"result": self.response}
149+
return {'result': self.response}

0 commit comments

Comments
 (0)