Skip to content

Commit b866f38

Browse files
saurabh-prakashSaurabh Prakash
and
Saurabh Prakash
authored
Better leetcode API (#13)
* Better leetcode API * Commented out test code bound to fail in future * python nuances Co-authored-by: Saurabh Prakash <[email protected]>
1 parent 213dbfe commit b866f38

File tree

2 files changed

+162
-38
lines changed

2 files changed

+162
-38
lines changed

Diff for: details_soup.py

+145-38
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from selenium import webdriver
1010
from selenium.webdriver.common.action_chains import ActionChains
1111

12+
from util import get_safe_nested_key
13+
1214

1315
class UsernameError(Exception):
1416
pass
@@ -294,6 +296,7 @@ def __interviewbit(self):
294296

295297
return details
296298

299+
# DEPRECATED
297300
def __leetcode(self):
298301
url = 'https://leetcode.com/{}'.format(self.__username)
299302

@@ -309,52 +312,56 @@ def __leetcode(self):
309312
# driver = webdriver.PhantomJS(executable_path='./phantomjs')
310313

311314
driver = webdriver.Chrome(options=options, executable_path=os.environ.get("CHROMEDRIVER_PATH"))
312-
driver.get(url)
315+
try:
316+
driver.get(url)
313317

314-
driver.implicitly_wait(10)
318+
driver.implicitly_wait(10)
315319

316-
hover_ranking = driver.find_element_by_xpath(
317-
'/html/body/div[1]/div[2]/div/div[1]/div[1]/div[2]/div/div[1]/div[3]/div')
318-
ActionChains(driver).move_to_element(to_element=hover_ranking).perform()
320+
hover_ranking = driver.find_element_by_xpath(
321+
'/html/body/div[1]/div[2]/div/div[1]/div[1]/div[2]/div/div[1]/div[3]/div')
322+
ActionChains(driver).move_to_element(to_element=hover_ranking).perform()
319323

320-
ranking = driver.find_element_by_xpath('/html/body/div[4]/div/div/div/div[2]').text
321-
print('rank: ', ranking)
324+
ranking = driver.find_element_by_xpath('/html/body/div[4]/div/div/div/div[2]').text
325+
print('rank: ', ranking)
322326

323-
total_problems_solved = driver.find_element_by_xpath(
324-
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[1]/div[2]').text
327+
total_problems_solved = driver.find_element_by_xpath(
328+
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[1]/div[2]').text
325329

326-
acceptance_rate_span_1 = driver.find_element_by_xpath(
327-
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[2]/div[2]/div/div[1]/span[1]').text
328-
acceptance_rate_span_2 = driver.find_element_by_xpath(
329-
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[2]/div[2]/div/div[1]/span[2]').text
330-
acceptance_rate = str(acceptance_rate_span_1) + str(acceptance_rate_span_2)
330+
acceptance_rate_span_1 = driver.find_element_by_xpath(
331+
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[2]/div[2]/div/div[1]/span[1]').text
332+
acceptance_rate_span_2 = driver.find_element_by_xpath(
333+
'/html/body/div[1]/div[2]/div/div[1]/div[2]/div/div[1]/div[2]/div[2]/div/div[1]/span[2]').text
334+
acceptance_rate = str(acceptance_rate_span_1) + str(acceptance_rate_span_2)
331335

332-
easy_questions_solved = driver.find_element_by_xpath(
333-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[1]/div[2]/span[1]').text
334-
total_easy_questions = driver.find_element_by_xpath(
335-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[1]/div[2]/span[2]').text
336+
easy_questions_solved = driver.find_element_by_xpath(
337+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[1]/div[2]/span[1]').text
338+
total_easy_questions = driver.find_element_by_xpath(
339+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[1]/div[2]/span[2]').text
336340

337-
medium_questions_solved = driver.find_element_by_xpath(
338-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[2]/div[2]/span[1]').text
339-
total_medium_questions = driver.find_element_by_xpath(
340-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[2]/div[2]/span[2]').text
341+
medium_questions_solved = driver.find_element_by_xpath(
342+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[2]/div[2]/span[1]').text
343+
total_medium_questions = driver.find_element_by_xpath(
344+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[2]/div[2]/span[2]').text
341345

342-
hard_questions_solved = driver.find_element_by_xpath(
343-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[3]/div[2]/span[1]').text
344-
total_hard_questions = driver.find_element_by_xpath(
345-
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[3]/div[2]/span[2]').text
346+
hard_questions_solved = driver.find_element_by_xpath(
347+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[3]/div[2]/span[1]').text
348+
total_hard_questions = driver.find_element_by_xpath(
349+
'//*[@id="profile-root"]/div[2]/div/div[1]/div[2]/div/div[2]/div/div[3]/div[2]/span[2]').text
346350

347-
contribution_points = driver.find_element_by_xpath(
348-
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[1]/span').text
351+
contribution_points = driver.find_element_by_xpath(
352+
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[1]/span').text
349353

350-
contribution_problems = driver.find_element_by_xpath(
351-
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[2]/span').text
354+
contribution_problems = driver.find_element_by_xpath(
355+
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[2]/span').text
352356

353-
contribution_testcases = driver.find_element_by_xpath(
354-
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[3]/span').text
357+
contribution_testcases = driver.find_element_by_xpath(
358+
'/html/body/div[1]/div[2]/div/div[1]/div[3]/div[2]/div/div/div/li[3]/span').text
355359

356-
reputation = driver.find_element_by_xpath(
357-
'/html/body/div[1]/div[2]/div/div[1]/div[4]/div[2]/div/div/div/li/span').text
360+
reputation = driver.find_element_by_xpath(
361+
'/html/body/div[1]/div[2]/div/div[1]/div[4]/div[2]/div/div/div/li/span').text
362+
finally:
363+
driver.close()
364+
driver.quit()
358365

359366
details = {'status': 'Success', 'ranking': ranking[9:],
360367
'total_problems_solved': total_problems_solved,
@@ -372,6 +379,98 @@ def __leetcode(self):
372379

373380
return details
374381

382+
def __leetcode_v2(self):
383+
384+
def __parse_response(response):
385+
total_submissions_count = 0
386+
ac_submissions_count = 0
387+
total_problems_solved = 0
388+
total_easy_questions = 0
389+
total_medium_questions = 0
390+
total_hard_questions = 0
391+
easy_questions_solved = 0
392+
medium_questions_solved = 0
393+
hard_questions_solved = 0
394+
395+
ranking = get_safe_nested_key(['data', 'matchedUser', 'profile', 'ranking'], response)
396+
if ranking > 100000:
397+
ranking = '~100000'
398+
399+
reputation = get_safe_nested_key(['data', 'matchedUser', 'profile', 'reputation'], response)
400+
401+
total_questions_stats = get_safe_nested_key(['data', 'allQuestionsCount'], response)
402+
for item in total_questions_stats:
403+
if item['difficulty'] == "Easy":
404+
total_easy_questions = item['count']
405+
if item['difficulty'] == "Medium":
406+
total_medium_questions = item['count']
407+
if item['difficulty'] == "Hard":
408+
total_hard_questions = item['count']
409+
410+
ac_submissions = get_safe_nested_key(['data', 'matchedUser', 'submitStats', 'acSubmissionNum'], response)
411+
for submission in ac_submissions:
412+
if submission['difficulty'] == "All":
413+
ac_submissions_count = submission['submissions']
414+
415+
total_submissions = get_safe_nested_key(['data', 'matchedUser', 'submitStats', 'totalSubmissionNum'],
416+
response)
417+
for submission in total_submissions:
418+
if submission['difficulty'] == "All":
419+
total_problems_solved = submission['count']
420+
total_submissions_count = submission['submissions']
421+
if submission['difficulty'] == "Easy":
422+
easy_questions_solved = submission['count']
423+
if submission['difficulty'] == "Medium":
424+
medium_questions_solved = submission['count']
425+
if submission['difficulty'] == "Hard":
426+
hard_questions_solved = submission['count']
427+
428+
if total_submissions_count > 0:
429+
acceptance_rate = round(ac_submissions_count * 100 / total_submissions_count, 2)
430+
else:
431+
acceptance_rate = 0
432+
433+
contribution_points = get_safe_nested_key(['data', 'matchedUser', 'contributions', 'points'],
434+
response)
435+
contribution_problems = get_safe_nested_key(['data', 'matchedUser', 'contributions', 'questionCount'],
436+
response)
437+
contribution_testcases = get_safe_nested_key(['data', 'matchedUser', 'contributions', 'testcaseCount'],
438+
response)
439+
440+
return {
441+
'status': 'Success',
442+
'ranking': str(ranking),
443+
'total_problems_solved': str(total_problems_solved),
444+
'acceptance_rate': f"{acceptance_rate}%",
445+
'easy_questions_solved': str(easy_questions_solved),
446+
'total_easy_questions': str(total_easy_questions),
447+
'medium_questions_solved': str(medium_questions_solved),
448+
'total_medium_questions': str(total_medium_questions),
449+
'hard_questions_solved': str(hard_questions_solved),
450+
'total_hard_questions': str(total_hard_questions),
451+
'contribution_points': str(contribution_points),
452+
'contribution_problems': str(contribution_problems),
453+
'contribution_testcases': str(contribution_testcases),
454+
'reputation': str(reputation)
455+
}
456+
457+
url = f'https://leetcode.com/{self.__username}'
458+
if requests.get(url).status_code != 200:
459+
raise UsernameError('User not Found')
460+
payload = {
461+
"operationName": "getUserProfile",
462+
"variables": {
463+
"username": self.__username
464+
},
465+
"query": "query getUserProfile($username: String!) { allQuestionsCount { difficulty count } matchedUser(username: $username) { contributions { points questionCount testcaseCount } profile { reputation ranking } submitStats { acSubmissionNum { difficulty count submissions } totalSubmissionNum { difficulty count submissions } } }}"
466+
}
467+
res = requests.post(url='https://leetcode.com/graphql',
468+
json=payload,
469+
headers={'referer': f'https://leetcode.com/{self.__username}/'})
470+
res.raise_for_status()
471+
res = res.json()
472+
return __parse_response(res)
473+
375474
def get_details(self, platform):
376475
if platform == 'codechef':
377476
return self.__codechef()
@@ -389,14 +488,22 @@ def get_details(self, platform):
389488
return self.__interviewbit()
390489

391490
if platform == 'leetcode':
392-
return self.__leetcode()
491+
return self.__leetcode_v2()
393492

394493
raise PlatformError('Platform not Found')
395494

396495

397496
if __name__ == '__main__':
398-
ud = UserData('abhijeet_ar')
399-
400-
ans = ud.get_details('codeforces')
497+
ud = UserData('uwi')
498+
ans = ud.get_details('leetcode')
401499

402500
print(ans)
501+
502+
# leetcode backward compatibility test. Commenting it out as it will fail in future
503+
# leetcode_ud = UserData('saurabhprakash')
504+
# leetcode_ans = leetcode_ud.get_details('leetcode')
505+
# assert leetcode_ans == dict(status='Success', ranking='~100000', total_problems_solved='10',
506+
# acceptance_rate='56.0%', easy_questions_solved='3', total_easy_questions='457',
507+
# medium_questions_solved='5', total_medium_questions='901', hard_questions_solved='2',
508+
# total_hard_questions='365', contribution_points='58', contribution_problems='0',
509+
# contribution_testcases='0', reputation='0')

Diff for: util.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
def get_safe_nested_key(keys, dictionary):
2+
if not isinstance(dictionary, dict):
3+
return None
4+
if isinstance(keys, str):
5+
return dictionary.get(keys)
6+
if isinstance(keys, list):
7+
if len(keys) == 1:
8+
return dictionary.get(keys[0])
9+
if len(keys) > 1:
10+
return get_safe_nested_key(keys[1:], dictionary.get(keys[0]))
11+
return None
12+
return None
13+
14+
15+
if __name__ == '__main__':
16+
assert get_safe_nested_key(['a', 'b', 'c'], {"a": {"b": {"c": "C"}}}) == "C"
17+
assert get_safe_nested_key(['a', 'b', 'c'], {"a": "A", "b": "B", "c": "C"}) is None

0 commit comments

Comments
 (0)