Skip to content

Commit 76df376

Browse files
committed
Fix It doesn't work anymore #54
Fixes #56 - added auth key and value Also manage use always the same user agent for the request to keep it consistent
1 parent 12ba9f0 commit 76df376

File tree

3 files changed

+74
-46
lines changed

3 files changed

+74
-46
lines changed

howlongtobeatpy/howlongtobeatpy/HTMLRequests.py

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class SearchAuthToken:
7878
search_url = "api/s"
7979
search_url_endpoint = "/init"
8080
auth_token = None
81+
auth_key = None
82+
auth_value = None
8183

8284
def extract_auth_token_from_response(self, response_content: requests.Response):
8385
"""
@@ -89,7 +91,15 @@ def extract_auth_token_from_response(self, response_content: requests.Response):
8991

9092
def extract_auth_token_from_json(self, json_content):
9193
self.auth_token = json_content.get('token')
92-
return self.auth_token
94+
95+
for field_name, field_value in json_content.items():
96+
lower = field_name.lower()
97+
if re.search(r'key', lower):
98+
self.auth_key = field_value
99+
elif re.search(r'val', lower):
100+
self.auth_value = field_value
101+
102+
return self
93103

94104
class HTMLRequests:
95105
BASE_URL = 'https://howlongtobeat.com/'
@@ -99,26 +109,28 @@ class HTMLRequests:
99109
SEARCH_URL = BASE_URL + "api/s/"
100110

101111
@staticmethod
102-
def get_search_request_headers(auth_token = None):
112+
def get_search_request_headers(auth_struct, user_agent):
103113
"""
104114
Generate the headers for the search request
105115
@return: The headers object for the request
106116
"""
107-
ua = UserAgent()
108117
headers = {
109118
'content-type': 'application/json',
110119
'accept': '*/*',
111-
'User-Agent': ua.random.strip(),
112-
'referer': HTMLRequests.REFERER_HEADER
120+
'User-Agent': user_agent,
121+
'Referer': HTMLRequests.REFERER_HEADER,
122+
'Origin': HTMLRequests.REFERER_HEADER
113123
}
114124

115-
if auth_token is not None:
116-
headers['x-auth-token'] = str(auth_token)
125+
if auth_struct is not None:
126+
headers['x-auth-token'] = str(auth_struct.auth_token)
127+
headers['x-hp-key'] = str(auth_struct.auth_key)
128+
headers['x-hp-val'] = str(auth_struct.auth_value)
117129

118130
return headers
119131

120132
@staticmethod
121-
def get_search_request_data(game_name: str, search_modifiers: SearchModifiers, page: int):
133+
def get_search_request_data(game_name: str, search_modifiers: SearchModifiers, page: int, auth_struct):
122134
"""
123135
Generate the data payload for the search request
124136
@param game_name: The name of the game to search
@@ -167,6 +179,9 @@ def get_search_request_data(game_name: str, search_modifiers: SearchModifiers, p
167179
'useCache': True
168180
}
169181

182+
if auth_struct is not None:
183+
payload[auth_struct.auth_key] = auth_struct.auth_value
184+
170185
return json.dumps(payload)
171186

172187
@staticmethod
@@ -179,21 +194,24 @@ def send_web_request(game_name: str, search_modifiers: SearchModifiers = SearchM
179194
@param page: The page to explore of the research, unknown if this is actually used
180195
@return: The HTML code of the research if the request returned 200(OK), None otherwise
181196
"""
197+
# Generate a single user agent to use for the whole request
198+
ua = UserAgent()
199+
request_user_agent = ua.random.strip()
182200
# Retrieve the updated URL
183-
search_info_data = HTMLRequests.send_website_request_getcode(False)
201+
search_info_data = HTMLRequests.send_website_request_getcode(False, request_user_agent)
184202
if search_info_data is None or search_info_data.search_url is None:
185-
search_info_data = HTMLRequests.send_website_request_getcode(True)
203+
search_info_data = HTMLRequests.send_website_request_getcode(True, request_user_agent)
186204
# Retrieve the request auth token
187-
auth_token = None
205+
auth_struct = None
188206
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)
207+
auth_struct = HTMLRequests.send_website_get_auth_token(search_info_data.search_url, request_user_agent)
190208
else:
191-
auth_token = HTMLRequests.send_website_get_auth_token(None)
209+
auth_struct = HTMLRequests.send_website_get_auth_token(None, request_user_agent)
192210
# Make the request
193-
headers = HTMLRequests.get_search_request_headers(auth_token)
211+
headers = HTMLRequests.get_search_request_headers(auth_struct, request_user_agent)
194212
if search_info_data is not None and search_info_data.search_url is not None:
195213
HTMLRequests.SEARCH_URL = HTMLRequests.BASE_URL + search_info_data.search_url
196-
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page)
214+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, auth_struct)
197215
resp = requests.post(HTMLRequests.SEARCH_URL, headers=headers, data=payload, timeout=60)
198216
if resp.status_code == 200:
199217
return resp.text
@@ -209,21 +227,24 @@ async def send_async_web_request(game_name: str, search_modifiers: SearchModifie
209227
@param page: The page to explore of the research, unknown if this is actually used
210228
@return: The HTML code of the research if the request returned 200(OK), None otherwise
211229
"""
230+
# Generate a single user agent to use for the whole request
231+
ua = UserAgent()
232+
request_user_agent = ua.random.strip()
212233
# Retrieve the updated URL
213-
search_info_data = HTMLRequests.send_website_request_getcode(False)
234+
search_info_data = HTMLRequests.send_website_request_getcode(False, request_user_agent)
214235
if search_info_data is None or search_info_data.search_url is None:
215-
search_info_data = HTMLRequests.send_website_request_getcode(True)
236+
search_info_data = HTMLRequests.send_website_request_getcode(True, request_user_agent)
216237
# Retrieve the request auth token
217-
auth_token = None
238+
auth_struct = None
218239
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)
240+
auth_struct = await HTMLRequests.async_send_website_get_auth_token(search_info_data.search_url, request_user_agent)
220241
else:
221-
auth_token = await HTMLRequests.async_send_website_get_auth_token(None)
242+
auth_struct = await HTMLRequests.async_send_website_get_auth_token(None, request_user_agent)
222243
# Make the request
223-
headers = HTMLRequests.get_search_request_headers(auth_token)
244+
headers = HTMLRequests.get_search_request_headers(auth_struct, request_user_agent)
224245
if search_info_data is not None and search_info_data.search_url is not None:
225246
HTMLRequests.SEARCH_URL = HTMLRequests.BASE_URL + search_info_data.search_url
226-
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page)
247+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, auth_struct)
227248
timeout = aiohttp.ClientTimeout(total=60)
228249
async with aiohttp.ClientSession() as session:
229250
async with session.post(HTMLRequests.SEARCH_URL, headers=headers, data=payload, timeout=timeout) as resp_with_key:
@@ -266,14 +287,13 @@ def get_title_request_parameters(game_id: int):
266287
return params
267288

268289
@staticmethod
269-
def get_title_request_headers():
290+
def get_title_request_headers(user_agent):
270291
"""
271292
Generate the headers for the search request
272293
@return: The headers object for the request
273294
"""
274-
ua = UserAgent()
275295
headers = {
276-
'User-Agent': ua.random,
296+
'User-Agent': user_agent,
277297
'referer': HTMLRequests.REFERER_HEADER
278298
}
279299
return headers
@@ -286,8 +306,12 @@ def get_game_title(game_id: int):
286306
@return: The game title from the given id
287307
"""
288308

309+
# This is a request to get the game title so we can generate a UserAgent
310+
ua = UserAgent()
311+
request_user_agent = ua.random.strip()
312+
289313
params = HTMLRequests.get_title_request_parameters(game_id)
290-
headers = HTMLRequests.get_title_request_headers()
314+
headers = HTMLRequests.get_title_request_headers(request_user_agent)
291315

292316
# Request and extract title
293317
contents = requests.get(HTMLRequests.GAME_URL, params=params, headers=headers, timeout=60)
@@ -301,8 +325,12 @@ async def async_get_game_title(game_id: int):
301325
@return: The game title from the given id
302326
"""
303327

328+
# This is a request to get the game title so we can generate a UserAgent
329+
ua = UserAgent()
330+
request_user_agent = ua.random.strip()
331+
304332
params = HTMLRequests.get_title_request_parameters(game_id)
305-
headers = HTMLRequests.get_title_request_headers()
333+
headers = HTMLRequests.get_title_request_headers(request_user_agent)
306334

307335
# Request and extract title
308336
timeout = aiohttp.ClientTimeout(total=60)
@@ -314,13 +342,13 @@ async def async_get_game_title(game_id: int):
314342
return None
315343

316344
@staticmethod
317-
def send_website_request_getcode(parse_all_scripts: bool):
345+
def send_website_request_getcode(parse_all_scripts: bool, user_agent):
318346
"""
319347
Function that send a request to howlongtobeat to scrape the correct search url
320348
@return: The search informations to use in the request
321349
"""
322350
# Make the post request and return the result if is valid
323-
headers = HTMLRequests.get_title_request_headers()
351+
headers = HTMLRequests.get_title_request_headers(user_agent)
324352
resp = requests.get(HTMLRequests.BASE_URL, headers=headers, timeout=60)
325353
if resp.status_code == 200 and resp.text is not None:
326354
# Parse the HTML content using BeautifulSoup
@@ -341,13 +369,13 @@ def send_website_request_getcode(parse_all_scripts: bool):
341369
return None
342370

343371
@staticmethod
344-
async def async_send_website_request_getcode(parse_all_scripts: bool):
372+
async def async_send_website_request_getcode(parse_all_scripts: bool, user_agent):
345373
"""
346374
Function that send a request to howlongtobeat to scrape the correct search url
347375
@return: The search informations to use in the request
348376
"""
349377
# Make the post request and return the result if is valid
350-
headers = HTMLRequests.get_title_request_headers()
378+
headers = HTMLRequests.get_title_request_headers(user_agent)
351379
timeout = aiohttp.ClientTimeout(total=60)
352380
async with aiohttp.ClientSession() as session:
353381
async with session.get(HTMLRequests.BASE_URL, headers=headers, timeout=timeout) as resp:
@@ -386,47 +414,47 @@ def get_auth_token_request_params():
386414
params = {
387415
't': timestamp
388416
}
389-
return params
417+
return params
390418

391419
@staticmethod
392-
def send_website_get_auth_token(parsed_search_url):
420+
def send_website_get_auth_token(parsed_search_url, user_agent):
393421
"""
394422
Function that send a request to howlongtobeat to get the x-auth-token to get in the request
395423
@return: The auth token to use
396424
"""
397425
# Make the post request and return the result if is valid
398-
headers = HTMLRequests.get_title_request_headers()
426+
headers = HTMLRequests.get_title_request_headers(user_agent)
399427
params = HTMLRequests.get_auth_token_request_params()
400-
auth_token = SearchAuthToken()
428+
auth_struct = SearchAuthToken()
401429
auth_token_url = HTMLRequests.BASE_URL
402430
if parsed_search_url is not None:
403-
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_token.search_url_endpoint
431+
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_struct.search_url_endpoint
404432
else:
405-
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url + auth_token.search_url_endpoint
433+
auth_token_url = HTMLRequests.BASE_URL + auth_struct.search_url + auth_struct.search_url_endpoint
406434
resp = requests.get(auth_token_url, params=params, headers=headers, timeout=60)
407435
if resp.status_code == 200 and resp.text is not None:
408-
return auth_token.extract_auth_token_from_response(resp)
436+
return auth_struct.extract_auth_token_from_response(resp)
409437
return None
410438

411439
@staticmethod
412-
async def async_send_website_get_auth_token(parsed_search_url):
440+
async def async_send_website_get_auth_token(parsed_search_url, user_agent):
413441
"""
414442
Function that send a request to howlongtobeat to get the x-auth-token to get in the request
415443
@return: The auth token to use
416444
"""
417445
# Make the post request and return the result if is valid
418-
headers = HTMLRequests.get_title_request_headers()
446+
headers = HTMLRequests.get_title_request_headers(user_agent)
419447
params = HTMLRequests.get_auth_token_request_params()
420-
auth_token = SearchAuthToken()
448+
auth_struct = SearchAuthToken()
421449
auth_token_url = HTMLRequests.BASE_URL
422450
if parsed_search_url is not None:
423-
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_token.search_url_endpoint
451+
auth_token_url = HTMLRequests.BASE_URL + parsed_search_url + auth_struct.search_url_endpoint
424452
else:
425-
auth_token_url = HTMLRequests.BASE_URL + auth_token.search_url + auth_token.search_url_endpoint
453+
auth_token_url = HTMLRequests.BASE_URL + auth_struct.search_url + auth_struct.search_url_endpoint
426454
timeout = aiohttp.ClientTimeout(total=60)
427455
async with aiohttp.ClientSession() as session:
428456
async with session.get(auth_token_url, params=params, headers=headers, timeout=timeout) as resp:
429457
if resp is not None and resp.status == 200:
430458
json_data = await resp.json()
431-
return auth_token.extract_auth_token_from_json(json_data)
459+
return auth_struct.extract_auth_token_from_json(json_data)
432460
return None

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.20',
7+
version='1.0.21',
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.20
5+
sonar.projectVersion=1.0.21
66
sonar.python.version=3.13
77

88
# Define separate root directories for sources and tests

0 commit comments

Comments
 (0)