Skip to content

Commit a58397a

Browse files
Add company-specific problem fetching
Introduced functionality to fetch LeetCode problems specific to a particular company. Updated `main.py` to handle a new `--company` argument and enhanced the `Database` and `LeetCode` classes with methods to check for and fetch company-related problems.
1 parent 8ae4b07 commit a58397a

File tree

4 files changed

+168
-9
lines changed

4 files changed

+168
-9
lines changed

database/database.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,38 @@ def does_study_plan_exist(self, slug: str) -> bool:
201201
except Exception:
202202
return False
203203

204+
def does_company_exist(self, company: str) -> bool:
205+
"""
206+
Check if a company exists in the database.
207+
:param company: The name of the company.
208+
:return: True if the company exists, False otherwise.
209+
"""
210+
sql = """
211+
SELECT EXISTS(SELECT 1 FROM leetcode.companies WHERE name = %(company)s);
212+
"""
213+
self.cursor.execute(sql, {"company": company})
214+
215+
try:
216+
result = self.cursor.fetchone()
217+
return result[0]
218+
except Exception:
219+
return False
220+
221+
def get_problems_by_company(self, company: str) -> list[Problem]:
222+
"""
223+
Get a list of problems by a specific company.
224+
:param company: The name of the company.
225+
:return: A list of Problem objects.
226+
"""
227+
sql = """
228+
SELECT p.id, p.question_id, p.title, p.slug, p.content, p.difficulty, p.topics, p.companies, p.hints, p.link
229+
FROM leetcode.problems p
230+
WHERE %(company)s = ANY(p.companies);
231+
"""
232+
self.cursor.execute(sql, {"company": company})
233+
results = self.cursor.fetchall()
234+
return [Problem(*result) for result in results]
235+
204236
def close(self):
205237
self.cursor.close()
206238
self.connection.close()

leetcode/api/client.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, Any
1+
from typing import Dict, Any, List
22

33
import requests
44

@@ -163,3 +163,71 @@ def get_study_plan_details(self, plan_slug: str) -> Dict[str, Any]:
163163
raise Exception("Study plan not found or invalid response format")
164164

165165
return response_data["data"]["studyPlanV2Detail"]
166+
167+
def get_recent_questions_for_company(
168+
self,
169+
company_slug: str,
170+
timeframe: str = "six-months",
171+
difficulties=None,
172+
top_n: int = 50,
173+
) -> Dict[str, Any]:
174+
"""
175+
Fetch the top N most recently asked questions for a specific company within a given timeframe.
176+
177+
:param company_slug: The slug of the company (e.g., 'microsoft').
178+
:param timeframe: The timeframe for questions (e.g., 'last-30-days', 'three-months', 'six-months').
179+
:param difficulties: The list of difficulties to filter the questions (default is ['EASY', 'MEDIUM']).
180+
:param top_n: The number of top questions to retrieve (default is 50).
181+
:return: A dictionary containing the list of questions.
182+
:raises Exception: If the API request fails or the response does not contain expected data.
183+
184+
"""
185+
if difficulties is None:
186+
difficulties = ["EASY", "MEDIUM"]
187+
188+
api_url = "https://leetcode.com/graphql"
189+
favorite_slug = f"{company_slug}-{timeframe}"
190+
query = """
191+
query favoriteQuestionListForCompany($favoriteSlug: String!, $filter: FavoriteQuestionFilterInput) {
192+
favoriteQuestionList(favoriteSlug: $favoriteSlug, filter: $filter) {
193+
questions {
194+
difficulty
195+
id
196+
paidOnly
197+
questionFrontendId
198+
status
199+
title
200+
titleSlug
201+
translatedTitle
202+
isInMyFavorites
203+
frequency
204+
topicTags {
205+
name
206+
nameTranslated
207+
slug
208+
}
209+
}
210+
}
211+
}
212+
"""
213+
variables = {
214+
"favoriteSlug": favorite_slug,
215+
"filter": {"difficultyList": difficulties, "positionRoleTagSlug": ""},
216+
}
217+
headers = self._get_headers()
218+
219+
response = requests.post(
220+
api_url, json={"query": query, "variables": variables}, headers=headers
221+
)
222+
response.raise_for_status() # Raise an exception for HTTP errors
223+
224+
response_data = response.json()
225+
if (
226+
"data" not in response_data
227+
or "favoriteQuestionList" not in response_data["data"]
228+
or "questions" not in response_data["data"]["favoriteQuestionList"]
229+
):
230+
raise Exception("Questions not found or invalid response format")
231+
232+
# Limit to top N questions
233+
return response_data["data"]["favoriteQuestionList"]["questions"][:top_n]

leetcode/leetcode.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import threading
33
import time
44
from concurrent.futures import ThreadPoolExecutor, as_completed
5-
from typing import Dict
5+
from typing import Dict, List
66

77
from requests import HTTPError, RequestException
88

@@ -46,9 +46,11 @@ def __init__(self, client: Client, database: Database = None):
4646

4747
self.problems: Dict[str, Problem] = {}
4848
self.study_plans: Dict[str, StudyPlan] = {}
49+
self.companies: Dict[str, List[Problem]] = {}
4950

5051
self.problems_lock = threading.Lock()
5152
self.study_plans_lock = threading.Lock()
53+
self.companies_lock = threading.Lock()
5254
self.database_lock = threading.Lock()
5355

5456
def _fetch_and_store_problem(self, slug: str) -> Problem:
@@ -129,6 +131,45 @@ def get_problem(self, slug: str) -> Problem:
129131
"""
130132
return self.problems[slug] if slug in self.problems else None
131133

134+
def fetch_and_store_company_problems(self, company: str) -> List[Problem]:
135+
"""
136+
Fetch problems from LeetCode by the company tag and store them in the companies' dictionary.
137+
138+
:param company: The company tag.
139+
:return: A dictionary of fetched Problem objects with the company tag as the key.
140+
"""
141+
with self.companies_lock:
142+
if company in self.companies:
143+
print(f"Company {company} problems already fetched")
144+
return self.companies[company]
145+
146+
with self.database_lock:
147+
if self.database.does_company_exist(company):
148+
company_problems = self.database.get_problems_by_company(company)
149+
print(f"Company {company} problems already fetched")
150+
with self.companies_lock:
151+
self.companies[company] = company_problems
152+
return company_problems
153+
154+
company_problems = []
155+
156+
questions = _fetch_with_retries(
157+
lambda: self.client.get_recent_questions_for_company(company)
158+
)
159+
160+
if not questions:
161+
raise Exception("No problems found for the company")
162+
163+
for question in questions:
164+
slug = question["titleSlug"]
165+
problem = self.get_problem(slug) or self._fetch_and_store_problem(slug)
166+
company_problems[slug].append(problem)
167+
168+
with self.companies_lock:
169+
self.companies[company] = company_problems
170+
171+
return company_problems
172+
132173
def fetch_and_store_study_plan(self, plan_slug: str) -> StudyPlan:
133174
"""
134175
Fetch a study plan from LeetCode by its slug and store it in the study plan dictionary.

main.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
load_dotenv()
1414

1515

16-
def main(csrf_token, leetcode_session, plans):
16+
def main(csrf_token, leetcode_session, plans, company):
1717
if not leetcode_session and not csrf_token:
1818
print(
1919
"Using Non-Premium LeetCode account. Some premium only data will not be returned."
@@ -29,14 +29,26 @@ def main(csrf_token, leetcode_session, plans):
2929
client = Client(configuration)
3030
leetcode = LeetCode(client, database=Database())
3131

32-
for plan in plans:
33-
print(f"Fetching study plan problems: {plan}")
32+
if plans:
33+
for plan in plans:
34+
print(f"Fetching study plan problems: {plan}")
35+
try:
36+
study_plan = leetcode.fetch_and_store_study_plan(plan)
37+
print(
38+
"====================================================================================================="
39+
)
40+
print(study_plan)
41+
except Exception as e:
42+
print(e)
43+
44+
if company:
45+
print(f"Fetching company related problems: {company}")
3446
try:
35-
study_plan = leetcode.fetch_and_store_study_plan(plan)
47+
company_problems = leetcode.fetch_and_store_company_problems(company)
3648
print(
3749
"====================================================================================================="
3850
)
39-
print(study_plan)
51+
print(company_problems)
4052
except Exception as e:
4153
print(e)
4254

@@ -70,10 +82,16 @@ def main(csrf_token, leetcode_session, plans):
7082
"--plans",
7183
type=str,
7284
nargs="+",
73-
default=["top-interview-150", "leetcode-75"],
85+
default=None,
7486
help="Slugs of the study plans to fetch",
7587
)
88+
parser.add_argument(
89+
"--company",
90+
type=str,
91+
default=None,
92+
help="Company name to filter problems by",
93+
)
7694

7795
args = parser.parse_args()
7896

79-
main(args.csrf_token, args.leetcode_session, args.plans)
97+
main(args.csrf_token, args.leetcode_session, args.plans, args.company)

0 commit comments

Comments
 (0)