Skip to content

Commit 8ce01f8

Browse files
committed
Fix #31 - fixed api key search
1 parent 76d3d36 commit 8ce01f8

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

howlongtobeatpy/howlongtobeatpy/HTMLRequests.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def get_search_request_data(game_name: str, search_modifiers: SearchModifiers, p
6565
'min': 0,
6666
'max': 0
6767
},
68+
'rangeYear':
69+
{
70+
'max': "",
71+
'min': ""
72+
},
6873
'gameplay': {
6974
'perspective': "",
7075
'flow': "",
@@ -73,14 +78,18 @@ def get_search_request_data(game_name: str, search_modifiers: SearchModifiers, p
7378
'modifier': search_modifiers.value,
7479
},
7580
'users': {
76-
'id': api_key,
7781
'sortCategory': "postcount"
7882
},
7983
'filter': "",
8084
'sort': 0,
8185
'randomizer': 0
8286
}
8387
}
88+
89+
# If api_key is passed add it to the dict
90+
if api_key is not None:
91+
payload['searchOptions']['users']['id'] = api_key
92+
8493
return json.dumps(payload)
8594

8695
@staticmethod
@@ -97,10 +106,17 @@ def send_web_request(game_name: str, search_modifiers: SearchModifiers = SearchM
97106
api_key_result = HTMLRequests.send_website_request_getcode(False)
98107
if api_key_result is None:
99108
api_key_result = HTMLRequests.send_website_request_getcode(True)
100-
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, api_key_result)
101-
# Make the post request and return the result if is valid
102-
search_url_with_key = HTMLRequests.SEARCH_URL
109+
# Make the request
110+
# The main method currently is the call to api/search/key
111+
search_url_with_key = HTMLRequests.SEARCH_URL + "/" + api_key_result
112+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, None)
103113
resp = requests.post(search_url_with_key, headers=headers, data=payload, timeout=60)
114+
if resp.status_code == 200:
115+
return resp.text
116+
# Try to call with the standard url adding the api key to the user
117+
search_url = HTMLRequests.SEARCH_URL
118+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, api_key_result)
119+
resp = requests.post(search_url, headers=headers, data=payload, timeout=60)
104120
if resp.status_code == 200:
105121
return resp.text
106122
return None
@@ -119,6 +135,22 @@ async def send_async_web_request(game_name: str, search_modifiers: SearchModifie
119135
api_key_result = await HTMLRequests.async_send_website_request_getcode(False)
120136
if api_key_result is None:
121137
api_key_result = await HTMLRequests.async_send_website_request_getcode(True)
138+
# Make the request
139+
# The main method currently is the call to api/search/key
140+
search_url_with_key = HTMLRequests.SEARCH_URL + "/" + api_key_result
141+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, None)
142+
async with aiohttp.ClientSession() as session:
143+
async with session.post(search_url_with_key, headers=headers, data=payload) as resp_with_key:
144+
if resp_with_key is not None and resp_with_key.status == 200:
145+
return await resp_with_key.text()
146+
else:
147+
search_url = HTMLRequests.SEARCH_URL
148+
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, api_key_result)
149+
async with session.post(search_url, headers=headers, data=payload) as resp_user_id:
150+
if resp_user_id is not None and resp_user_id.status == 200:
151+
return await resp_user_id.text()
152+
return None
153+
122154
payload = HTMLRequests.get_search_request_data(game_name, search_modifiers, page, api_key_result)
123155
# Make the post request and return the result if is valid
124156
search_url_with_key = HTMLRequests.SEARCH_URL + "/" + api_key_result
@@ -208,11 +240,35 @@ async def async_get_game_title(game_id: int):
208240
return HTMLRequests.__cut_game_title(text)
209241
return None
210242

243+
@staticmethod
244+
def extract_api_from_script(script_content: str):
245+
"""
246+
Function that extract the htlb code to use in the request from the given script
247+
@return: the string of the api key found
248+
"""
249+
# Try multiple find one after the other as hltb keep changing format
250+
# Test 1 - The API Key is in the user id in the request json
251+
user_id_api_key_pattern = r'users\s*:\s*{\s*id\s*:\s*"([^"]+)"'
252+
matches = re.findall(user_id_api_key_pattern, script_content)
253+
if matches:
254+
key = ''.join(matches)
255+
return key
256+
# Test 2 - The API Key is in format fetch("/api/search/".concat("X").concat("Y")...
257+
concat_api_key_pattern = r'\/api\/search\/"(?:\.concat\("[^"]*"\))*'
258+
matches = re.findall(concat_api_key_pattern, script_content)
259+
if matches:
260+
matches = str(matches).split('.concat')
261+
matches = [re.sub(r'["\(\)\[\]\']', '', match) for match in matches[1:]]
262+
key = ''.join(matches)
263+
return key
264+
# Unable to find :(
265+
return None
266+
211267
@staticmethod
212268
def send_website_request_getcode(parse_all_scripts: bool):
213269
"""
214270
Function that send a request to howlongtobeat to scrape the /api/search key
215-
@return: The string key to use on /api/search
271+
@return: The string key to use
216272
"""
217273
# Make the post request and return the result if is valid
218274
headers = HTMLRequests.get_title_request_headers()
@@ -230,23 +286,22 @@ def send_website_request_getcode(parse_all_scripts: bool):
230286
script_url = HTMLRequests.BASE_URL + script_url
231287
script_resp = requests.get(script_url, headers=headers, timeout=60)
232288
if script_resp.status_code == 200 and script_resp.text is not None:
233-
pattern = r'users\s*:\s*{\s*id\s*:\s*"([^"]+)"'
234-
matches = re.findall(pattern, script_resp.text)
235-
for match in matches:
236-
return match
289+
api_key_result = HTMLRequests.extract_api_from_script(script_resp.text)
290+
if api_key_result is not None:
291+
return api_key_result
237292
return None
238293

239294
@staticmethod
240295
async def async_send_website_request_getcode(parse_all_scripts: bool):
241296
"""
242297
Function that send a request to howlongtobeat to scrape the /api/search key
243-
@return: The string key to use on /api/search
298+
@return: The string key to use
244299
"""
245300
# Make the post request and return the result if is valid
246301
headers = HTMLRequests.get_title_request_headers()
247302
async with aiohttp.ClientSession() as session:
248303
async with session.get(HTMLRequests.BASE_URL, headers=headers) as resp:
249-
if resp is not None and str(resp.status) == "200":
304+
if resp is not None and resp.status == 200:
250305
resp_text = await resp.text()
251306
# Parse the HTML content using BeautifulSoup
252307
soup = BeautifulSoup(resp_text, 'html.parser')
@@ -260,12 +315,11 @@ async def async_send_website_request_getcode(parse_all_scripts: bool):
260315
script_url = HTMLRequests.BASE_URL + script_url
261316
async with aiohttp.ClientSession() as session:
262317
async with session.get(script_url, headers=headers) as script_resp:
263-
if script_resp is not None and str(resp.status) == "200":
318+
if script_resp is not None and resp.status == 200:
264319
script_resp_text = await script_resp.text()
265-
pattern = r'users\s*:\s*{\s*id\s*:\s*"([^"]+)"'
266-
matches = re.findall(pattern, script_resp_text)
267-
for match in matches:
268-
return match
320+
api_key_result = HTMLRequests.extract_api_from_script(script_resp_text)
321+
if api_key_result is not None:
322+
return api_key_result
269323
else:
270324
return None
271325
else:

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.10',
7+
version='1.0.11',
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.10
5+
sonar.projectVersion=1.0.11
66
sonar.python.version=3.9
77

88
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.

0 commit comments

Comments
 (0)