Skip to content

Commit a8de745

Browse files
QuBenhaoCopilot
andauthored
feat: favorite (#154)
* feat: init favorite add favorite queries * feat: add query_favorite_questions and update favorite handling favorite methods * feat: improve error logging and optimize question retrieval in favorite handling multithread slug query * fix: bug Update python/scripts/leetcode.py Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 73b72fd commit a8de745

File tree

6 files changed

+365
-15
lines changed

6 files changed

+365
-15
lines changed

python/constants/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
CARGO_TOML_TEMPLATE_SOLUTION, CONTEST_TEMPLATE_PYTHON)
1515
from .display import (SUBMIT_BASIC_RESULT, SUBMIT_SUCCESS_RESULT, SUBMIT_FAIL_RESULT)
1616
from .contest_query import CONTEST_HISTORY_QUERY
17+
from .favorite_query import ADD_QUESTION_TO_FAVORITE_QUERY, MY_FAVORITE_QUERY, FAVORITE_QUESTION_QUERY

python/constants/favorite_query.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
ADD_QUESTION_TO_FAVORITE_QUERY = """
2+
mutation batchAddQuestionsToFavorite($favoriteSlug: String!, $questionSlugs: [String]!) {
3+
batchAddQuestionsToFavorite(
4+
favoriteSlug: $favoriteSlug
5+
questionSlugs: $questionSlugs
6+
) {
7+
ok
8+
error
9+
}
10+
}"""
11+
12+
MY_FAVORITE_QUERY = """
13+
query myFavoriteList {
14+
myCreatedFavoriteList {
15+
favorites {
16+
coverUrl
17+
coverEmoji
18+
coverBackgroundColor
19+
hasCurrentQuestion
20+
isPublicFavorite
21+
lastQuestionAddedAt
22+
name
23+
slug
24+
favoriteType
25+
}
26+
hasMore
27+
totalLength
28+
}
29+
myCollectedFavoriteList {
30+
hasMore
31+
totalLength
32+
favorites {
33+
coverUrl
34+
coverEmoji
35+
coverBackgroundColor
36+
hasCurrentQuestion
37+
isPublicFavorite
38+
name
39+
slug
40+
lastQuestionAddedAt
41+
favoriteType
42+
}
43+
}
44+
}"""
45+
46+
FAVORITE_QUESTION_QUERY = """
47+
query favoriteQuestionList($favoriteSlug: String!, $filter: FavoriteQuestionFilterInput, $searchKeyword: String, $filtersV2: QuestionFilterInput, $sortBy: QuestionSortByInput, $limit: Int, $skip: Int, $version: String = "v2") {
48+
favoriteQuestionList(
49+
favoriteSlug: $favoriteSlug
50+
filter: $filter
51+
filtersV2: $filtersV2
52+
searchKeyword: $searchKeyword
53+
sortBy: $sortBy
54+
limit: $limit
55+
skip: $skip
56+
version: $version
57+
) {
58+
questions {
59+
difficulty
60+
id
61+
paidOnly
62+
questionFrontendId
63+
status
64+
title
65+
titleSlug
66+
translatedTitle
67+
isInMyFavorites
68+
frequency
69+
acRate
70+
topicTags {
71+
name
72+
nameTranslated
73+
slug
74+
}
75+
}
76+
totalLength
77+
hasMore
78+
}
79+
}
80+
"""

python/lc_libs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
from .study_plan import get_user_study_plans, get_user_study_plan_progress
1616
from .rating import get_rating
1717
from .answers import get_answer_san_ye
18+
from .favorite import query_my_favorites, batch_add_questions_to_favorite, query_favorite_questions

python/lc_libs/favorite.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import logging
2+
from typing import Optional
3+
4+
import requests
5+
6+
from python.constants import LEET_CODE_BACKEND, ADD_QUESTION_TO_FAVORITE_QUERY, MY_FAVORITE_QUERY, \
7+
FAVORITE_QUESTION_QUERY
8+
from python.utils import general_request
9+
10+
11+
def batch_add_questions_to_favorite(favorite_slug: str, questions: list, cookie: str) -> Optional[dict]:
12+
def handle_response(response: requests.Response):
13+
resp = response.json()
14+
if resp.get("data", {}).get("batchAddQuestionsToFavorite", {}).get("ok"):
15+
return {"status": "success"}
16+
else:
17+
error = resp.get("data", {}).get("batchAddQuestionsToFavorite", {}).get("error", "Unknown error")
18+
logging.error(f"Failed to add questions to favorite: {error}")
19+
return {"status": "error", "message": error}
20+
21+
return general_request(
22+
LEET_CODE_BACKEND,
23+
handle_response,
24+
json={"query": ADD_QUESTION_TO_FAVORITE_QUERY,
25+
"variables": {"favoriteSlug": favorite_slug, "questionSlugs": questions},
26+
"operationName": "batchAddQuestionsToFavorite"},
27+
cookies={"cookie": cookie}
28+
)
29+
30+
31+
def query_my_favorites(cookie: str) -> Optional[dict]:
32+
def handle_response(response: requests.Response) -> Optional[dict]:
33+
resp = response.json()
34+
35+
my_created_favorites = resp.get("data", {}).get("myCreatedFavoriteList", {})
36+
total_length = my_created_favorites.get("totalLength", 0)
37+
favorites = my_created_favorites.get("favorites", [])
38+
return {
39+
"total": total_length,
40+
"favorites": [
41+
{
42+
"name": favorite.get("name"),
43+
"slug": favorite.get("slug"),
44+
}
45+
for favorite in favorites
46+
],
47+
"has_more": my_created_favorites.get("hasMore", False)
48+
}
49+
50+
return general_request(
51+
LEET_CODE_BACKEND,
52+
handle_response,
53+
json={"query": MY_FAVORITE_QUERY, "operationName": "myFavoriteList", "variables": {}},
54+
cookies={"cookie": cookie}
55+
)
56+
57+
58+
def query_favorite_questions(favorite_slug: str, cookie: str, limit: int = 100, skip: int = 0) -> Optional[dict]:
59+
def handle_response(response: requests.Response) -> Optional[dict]:
60+
data = response.json().get("data", {}).get("favoriteQuestionList", {})
61+
total = data.get("totalLength", 0)
62+
questions = data.get("questions", [])
63+
return {
64+
"total": total,
65+
"questions": [
66+
{
67+
"title": question.get("title"),
68+
"title_slug": question.get("titleSlug"),
69+
"translated_title": question.get("translatedTitle"),
70+
"difficulty": question.get("difficulty"),
71+
"id": question.get("id"),
72+
"question_frontend_id": question.get("questionFrontendId"),
73+
}
74+
for question in questions
75+
],
76+
"has_more": data.get("hasMore", False)
77+
}
78+
79+
return general_request(
80+
LEET_CODE_BACKEND,
81+
handle_response,
82+
json={
83+
"query": FAVORITE_QUESTION_QUERY,
84+
"variables": {
85+
"skip": skip,
86+
"limit": limit,
87+
"favoriteSlug": favorite_slug,
88+
"filtersV2": {
89+
"filterCombineType": "ALL",
90+
"statusFilter": {
91+
"questionStatuses": [],
92+
"operator": "IS"
93+
},
94+
"difficultyFilter": {
95+
"difficulties": [],
96+
"operator": "IS"
97+
},
98+
"languageFilter": {
99+
"languageSlugs": [],
100+
"operator": "IS"
101+
},
102+
"topicFilter": {
103+
"topicSlugs": [],
104+
"operator": "IS"
105+
},
106+
"acceptanceFilter": {},
107+
"frequencyFilter": {},
108+
"frontendIdFilter": {},
109+
"lastSubmittedFilter": {},
110+
"publishedFilter": {},
111+
"companyFilter": {
112+
"companySlugs": [],
113+
"operator": "IS"
114+
},
115+
"positionFilter": {
116+
"positionSlugs": [],
117+
"operator": "IS"
118+
},
119+
"premiumFilter": {
120+
"premiumStatus": [],
121+
"operator": "IS"
122+
}
123+
},
124+
"searchKeyword": "",
125+
"sortBy": {
126+
"sortField": "CUSTOM",
127+
"sortOrder": "ASCENDING"
128+
}
129+
},
130+
"operationName": "favoriteQuestionList"
131+
},
132+
cookies={"cookie": cookie}
133+
)

python/scripts/get_problem.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,24 @@ def process_single_database_problem(problem_folder: str, problem_id: str, proble
144144
logging.info(f"Add question: [{problem_id}]{problem_slug}")
145145

146146

147+
def get_question_slug_by_id(
148+
problem_id: str,
149+
problem_category: Optional[str] = None,
150+
cookie: Optional[str] = None) -> Optional[str]:
151+
questions = get_questions_by_key_word(problem_id, problem_category) if problem_category \
152+
else get_questions_by_key_word(problem_id)
153+
if not questions:
154+
logging.error(f"Unable to find any questions with problem_id {problem_id}")
155+
return None
156+
for question in questions:
157+
if question["paidOnly"] and not cookie:
158+
continue
159+
if question["frontendQuestionId"] == problem_id:
160+
return question["titleSlug"]
161+
logging.error(f"Unable to find any questions with problem_id {problem_id}, possible questions: {questions}")
162+
return None
163+
164+
147165
def main(origin_problem_id: Optional[str] = None, problem_slug: Optional[str] = None,
148166
problem_category: Optional[str] = None, force: bool = False, cookie: Optional[str] = None,
149167
fetch_all: bool = False, premium_only: bool = False, replace_problem_id: bool = False,
@@ -153,20 +171,8 @@ def main(origin_problem_id: Optional[str] = None, problem_slug: Optional[str] =
153171
logging.critical("Requires at least one of problem_id or problem_slug to fetch in single mode.")
154172
return 1
155173
if not problem_slug:
156-
questions = get_questions_by_key_word(origin_problem_id, problem_category) if problem_category \
157-
else get_questions_by_key_word(origin_problem_id)
158-
if not questions:
159-
logging.error(f"Unable to find any questions with problem_id {origin_problem_id}")
160-
return 1
161-
for question in questions:
162-
if question["paidOnly"] and not cookie:
163-
continue
164-
if question["frontendQuestionId"] == origin_problem_id:
165-
problem_slug = question["titleSlug"]
166-
break
174+
problem_slug = get_question_slug_by_id(origin_problem_id, problem_category, cookie)
167175
if not problem_slug:
168-
logging.error(f"Unable to find any questions with problem_id {origin_problem_id}"
169-
f", possible questions: {questions}")
170176
return 1
171177
question_info = get_question_info(problem_slug, cookie)
172178
if not question_info:

0 commit comments

Comments
 (0)