Skip to content

Commit 39c0e75

Browse files
committed
Improve stability by parsing the used url and use it for the auth token too - new release
1 parent d715bd7 commit 39c0e75

File tree

3 files changed

+40
-20
lines changed

3 files changed

+40
-20
lines changed

howlongtobeatpy/howlongtobeatpy/HTMLRequests.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,26 @@ def __extract_search_url_script(self, script_content: str):
3838
"""
3939
Function that finds the 'fetch' call using 'method: "POST"',
4040
extracts the base endpoint path, and returns the full '/api/path'
41-
string (e.g., "/api/search").
41+
string (e.g., "/api/search" or "/api/finder").
4242
43-
This avoids relying on the exact string "search" by confirming
43+
This avoids relying on the exact string by confirming
4444
the use of the POST method, which identifies the actual search endpoint.
4545
4646
@return: The full API endpoint string (e.g., "/api/search") or None.
4747
"""
4848
# Pattern explanation:
49-
# 1. Capture Group 1: Matches the path suffix (e.g., "search" or "find").
49+
# 1. Capture Group 1: Matches the path suffix (e.g., "search", "find", "finder").
5050
# 2. Ensures the request options contain 'method: "POST"' to filter out the GET init call.
5151
pattern = re.compile(
52-
# Capture Group 1: The path suffix after /api/ (e.g., "search" or "find/v2")
52+
# Capture Group 1: The path suffix after /api/ (e.g., "search", "find", "finder")
5353
r'fetch\s*\(\s*["\']/api/([a-zA-Z0-9_/]+)[^"\']*["\']\s*,\s*{[^}]*method:\s*["\']POST["\'][^}]*}',
5454
re.DOTALL | re.IGNORECASE
5555
)
5656

5757
match = pattern.search(script_content)
5858

5959
if match:
60-
# Example captured string: "search" or "find/v2"
60+
# Example captured string: "search", "find", "finder", or "find/v2"
6161
path_suffix = match.group(1)
6262

6363
# Determine the root path (e.g., "search" from "search/v2")
@@ -66,17 +66,17 @@ def __extract_search_url_script(self, script_content: str):
6666
base_path = path_suffix.split('/')[0]
6767
else:
6868
base_path = path_suffix
69-
70-
if base_path != "find":
71-
full_endpoint = f"/api/{base_path}"
7269

73-
return full_endpoint
70+
# Accept any endpoint variant (search, find, finder, etc.)
71+
full_endpoint = f"/api/{base_path}"
72+
return full_endpoint
7473

7574
return None
7675

7776

7877
class SearchAuthToken:
79-
search_url = "api/search/init"
78+
search_url = "api/s"
79+
search_url_endpoint = "/init"
8080
auth_token = None
8181

8282
def extract_auth_token_from_response(self, response_content: requests.Response):
@@ -179,12 +179,18 @@ def send_web_request(game_name: str, search_modifiers: SearchModifiers = SearchM
179179
@param page: The page to explore of the research, unknown if this is actually used
180180
@return: The HTML code of the research if the request returned 200(OK), None otherwise
181181
"""
182-
auth_token = HTMLRequests.send_website_get_auth_token()
183-
headers = HTMLRequests.get_search_request_headers(auth_token)
182+
# Retrieve the updated URL
184183
search_info_data = HTMLRequests.send_website_request_getcode(False)
185184
if search_info_data is None or search_info_data.search_url is None:
186185
search_info_data = HTMLRequests.send_website_request_getcode(True)
186+
# Retrieve the request auth token
187+
auth_token = None
188+
if search_info_data is not None and search_info_data.search_url is not None:
189+
auth_token = HTMLRequests.send_website_get_auth_token(search_info_data.search_url)
190+
else:
191+
auth_token = HTMLRequests.send_website_get_auth_token(None)
187192
# Make the request
193+
headers = HTMLRequests.get_search_request_headers(auth_token)
188194
if search_info_data is not None and search_info_data.search_url is not None:
189195
HTMLRequests.SEARCH_URL = HTMLRequests.BASE_URL + search_info_data.search_url
190196
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page)
@@ -203,12 +209,18 @@ async def send_async_web_request(game_name: str, search_modifiers: SearchModifie
203209
@param page: The page to explore of the research, unknown if this is actually used
204210
@return: The HTML code of the research if the request returned 200(OK), None otherwise
205211
"""
206-
auth_token = await HTMLRequests.async_send_website_get_auth_token()
207-
headers = HTMLRequests.get_search_request_headers(auth_token)
212+
# Retrieve the updated URL
208213
search_info_data = HTMLRequests.send_website_request_getcode(False)
209214
if search_info_data is None or search_info_data.search_url is None:
210215
search_info_data = HTMLRequests.send_website_request_getcode(True)
216+
# Retrieve the request auth token
217+
auth_token = None
218+
if search_info_data is not None and search_info_data.search_url is not None:
219+
auth_token = await HTMLRequests.async_send_website_get_auth_token(search_info_data.search_url)
220+
else:
221+
auth_token = await HTMLRequests.async_send_website_get_auth_token(None)
211222
# Make the request
223+
headers = HTMLRequests.get_search_request_headers(auth_token)
212224
if search_info_data is not None and search_info_data.search_url is not None:
213225
HTMLRequests.SEARCH_URL = HTMLRequests.BASE_URL + search_info_data.search_url
214226
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page)
@@ -377,7 +389,7 @@ def get_auth_token_request_params():
377389
return params
378390

379391
@staticmethod
380-
def send_website_get_auth_token():
392+
def send_website_get_auth_token(parsed_search_url):
381393
"""
382394
Function that send a request to howlongtobeat to get the x-auth-token to get in the request
383395
@return: The auth token to use
@@ -386,14 +398,18 @@ def send_website_get_auth_token():
386398
headers = HTMLRequests.get_title_request_headers()
387399
params = HTMLRequests.get_auth_token_request_params()
388400
auth_token = SearchAuthToken()
389-
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url
401+
auth_token_url = HTMLRequests.BASE_URL
402+
if parsed_search_url is not None:
403+
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_token.search_url_endpoint
404+
else:
405+
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url + auth_token.search_url_endpoint
390406
resp = requests.get(auth_token_url, params=params, headers=headers, timeout=60)
391407
if resp.status_code == 200 and resp.text is not None:
392408
return auth_token.extract_auth_token_from_response(resp)
393409
return None
394410

395411
@staticmethod
396-
async def async_send_website_get_auth_token():
412+
async def async_send_website_get_auth_token(parsed_search_url):
397413
"""
398414
Function that send a request to howlongtobeat to get the x-auth-token to get in the request
399415
@return: The auth token to use
@@ -402,7 +418,11 @@ async def async_send_website_get_auth_token():
402418
headers = HTMLRequests.get_title_request_headers()
403419
params = HTMLRequests.get_auth_token_request_params()
404420
auth_token = SearchAuthToken()
405-
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url
421+
auth_token_url = HTMLRequests.BASE_URL
422+
if parsed_search_url is not None:
423+
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_token.search_url_endpoint
424+
else:
425+
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url + auth_token.search_url_endpoint
406426
timeout = aiohttp.ClientTimeout(total=60)
407427
async with aiohttp.ClientSession() as session:
408428
async with session.get(auth_token_url, params=params, headers=headers, timeout=timeout) as resp:

howlongtobeatpy/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
long_description = fh.read()
55

66
setup(name='howlongtobeatpy',
7-
version='1.0.19',
7+
version='1.0.20',
88
packages=find_packages(exclude=['tests']),
99
description='A Python API for How Long to Beat',
1010
long_description=long_description,

sonar-project.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ sonar.organization=scrappycocco-github
22
sonar.projectKey=ScrappyCocco_HowLongToBeat-PythonAPI
33

44
sonar.projectName=HowLongToBeat-PythonAPI
5-
sonar.projectVersion=1.0.19
5+
sonar.projectVersion=1.0.20
66
sonar.python.version=3.13
77

88
# Define separate root directories for sources and tests

0 commit comments

Comments
 (0)