Skip to content

feat: favorite #154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
CARGO_TOML_TEMPLATE_SOLUTION, CONTEST_TEMPLATE_PYTHON)
from .display import (SUBMIT_BASIC_RESULT, SUBMIT_SUCCESS_RESULT, SUBMIT_FAIL_RESULT)
from .contest_query import CONTEST_HISTORY_QUERY
from .favorite_query import ADD_QUESTION_TO_FAVORITE_QUERY, MY_FAVORITE_QUERY, FAVORITE_QUESTION_QUERY
80 changes: 80 additions & 0 deletions python/constants/favorite_query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
ADD_QUESTION_TO_FAVORITE_QUERY = """
mutation batchAddQuestionsToFavorite($favoriteSlug: String!, $questionSlugs: [String]!) {
batchAddQuestionsToFavorite(
favoriteSlug: $favoriteSlug
questionSlugs: $questionSlugs
) {
ok
error
}
}"""

MY_FAVORITE_QUERY = """
query myFavoriteList {
myCreatedFavoriteList {
favorites {
coverUrl
coverEmoji
coverBackgroundColor
hasCurrentQuestion
isPublicFavorite
lastQuestionAddedAt
name
slug
favoriteType
}
hasMore
totalLength
}
myCollectedFavoriteList {
hasMore
totalLength
favorites {
coverUrl
coverEmoji
coverBackgroundColor
hasCurrentQuestion
isPublicFavorite
name
slug
lastQuestionAddedAt
favoriteType
}
}
}"""

FAVORITE_QUESTION_QUERY = """
query favoriteQuestionList($favoriteSlug: String!, $filter: FavoriteQuestionFilterInput, $searchKeyword: String, $filtersV2: QuestionFilterInput, $sortBy: QuestionSortByInput, $limit: Int, $skip: Int, $version: String = "v2") {
favoriteQuestionList(
favoriteSlug: $favoriteSlug
filter: $filter
filtersV2: $filtersV2
searchKeyword: $searchKeyword
sortBy: $sortBy
limit: $limit
skip: $skip
version: $version
) {
questions {
difficulty
id
paidOnly
questionFrontendId
status
title
titleSlug
translatedTitle
isInMyFavorites
frequency
acRate
topicTags {
name
nameTranslated
slug
}
}
totalLength
hasMore
}
}
"""
1 change: 1 addition & 0 deletions python/lc_libs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
from .study_plan import get_user_study_plans, get_user_study_plan_progress
from .rating import get_rating
from .answers import get_answer_san_ye
from .favorite import query_my_favorites, batch_add_questions_to_favorite, query_favorite_questions
133 changes: 133 additions & 0 deletions python/lc_libs/favorite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import logging
from typing import Optional

import requests

from python.constants import LEET_CODE_BACKEND, ADD_QUESTION_TO_FAVORITE_QUERY, MY_FAVORITE_QUERY, \
FAVORITE_QUESTION_QUERY
from python.utils import general_request


def batch_add_questions_to_favorite(favorite_slug: str, questions: list, cookie: str) -> Optional[dict]:
def handle_response(response: requests.Response):
resp = response.json()
if resp.get("data", {}).get("batchAddQuestionsToFavorite", {}).get("ok"):
return {"status": "success"}
else:
error = resp.get("data", {}).get("batchAddQuestionsToFavorite", {}).get("error", "Unknown error")
logging.error(f"Failed to add questions to favorite: {error}")
return {"status": "error", "message": error}

return general_request(
LEET_CODE_BACKEND,
handle_response,
json={"query": ADD_QUESTION_TO_FAVORITE_QUERY,
"variables": {"favoriteSlug": favorite_slug, "questionSlugs": questions},
"operationName": "batchAddQuestionsToFavorite"},
cookies={"cookie": cookie}
)


def query_my_favorites(cookie: str) -> Optional[dict]:
def handle_response(response: requests.Response) -> Optional[dict]:
resp = response.json()

my_created_favorites = resp.get("data", {}).get("myCreatedFavoriteList", {})
total_length = my_created_favorites.get("totalLength", 0)
favorites = my_created_favorites.get("favorites", [])
return {
"total": total_length,
"favorites": [
{
"name": favorite.get("name"),
"slug": favorite.get("slug"),
}
for favorite in favorites
],
"has_more": my_created_favorites.get("hasMore", False)
}

return general_request(
LEET_CODE_BACKEND,
handle_response,
json={"query": MY_FAVORITE_QUERY, "operationName": "myFavoriteList", "variables": {}},
cookies={"cookie": cookie}
)


def query_favorite_questions(favorite_slug: str, cookie: str, limit: int = 100, skip: int = 0) -> Optional[dict]:
def handle_response(response: requests.Response) -> Optional[dict]:
data = response.json().get("data", {}).get("favoriteQuestionList", {})
total = data.get("totalLength", 0)
questions = data.get("questions", [])
return {
"total": total,
"questions": [
{
"title": question.get("title"),
"title_slug": question.get("titleSlug"),
"translated_title": question.get("translatedTitle"),
"difficulty": question.get("difficulty"),
"id": question.get("id"),
"question_frontend_id": question.get("questionFrontendId"),
}
for question in questions
],
"has_more": data.get("hasMore", False)
}

return general_request(
LEET_CODE_BACKEND,
handle_response,
json={
"query": FAVORITE_QUESTION_QUERY,
"variables": {
"skip": skip,
"limit": limit,
"favoriteSlug": favorite_slug,
"filtersV2": {
"filterCombineType": "ALL",
"statusFilter": {
"questionStatuses": [],
"operator": "IS"
},
"difficultyFilter": {
"difficulties": [],
"operator": "IS"
},
"languageFilter": {
"languageSlugs": [],
"operator": "IS"
},
"topicFilter": {
"topicSlugs": [],
"operator": "IS"
},
"acceptanceFilter": {},
"frequencyFilter": {},
"frontendIdFilter": {},
"lastSubmittedFilter": {},
"publishedFilter": {},
"companyFilter": {
"companySlugs": [],
"operator": "IS"
},
"positionFilter": {
"positionSlugs": [],
"operator": "IS"
},
"premiumFilter": {
"premiumStatus": [],
"operator": "IS"
}
},
"searchKeyword": "",
"sortBy": {
"sortField": "CUSTOM",
"sortOrder": "ASCENDING"
}
},
"operationName": "favoriteQuestionList"
},
cookies={"cookie": cookie}
)
32 changes: 19 additions & 13 deletions python/scripts/get_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ def process_single_database_problem(problem_folder: str, problem_id: str, proble
logging.info(f"Add question: [{problem_id}]{problem_slug}")


def get_question_slug_by_id(
problem_id: str,
problem_category: Optional[str] = None,
cookie: Optional[str] = None) -> Optional[str]:
questions = get_questions_by_key_word(problem_id, problem_category) if problem_category \
else get_questions_by_key_word(problem_id)
if not questions:
logging.error(f"Unable to find any questions with problem_id {problem_id}")
return None
for question in questions:
if question["paidOnly"] and not cookie:
continue
if question["frontendQuestionId"] == problem_id:
return question["titleSlug"]
logging.error(f"Unable to find any questions with problem_id {problem_id}, possible questions: {questions}")
return None


def main(origin_problem_id: Optional[str] = None, problem_slug: Optional[str] = None,
problem_category: Optional[str] = None, force: bool = False, cookie: Optional[str] = None,
fetch_all: bool = False, premium_only: bool = False, replace_problem_id: bool = False,
Expand All @@ -153,20 +171,8 @@ def main(origin_problem_id: Optional[str] = None, problem_slug: Optional[str] =
logging.critical("Requires at least one of problem_id or problem_slug to fetch in single mode.")
return 1
if not problem_slug:
questions = get_questions_by_key_word(origin_problem_id, problem_category) if problem_category \
else get_questions_by_key_word(origin_problem_id)
if not questions:
logging.error(f"Unable to find any questions with problem_id {origin_problem_id}")
return 1
for question in questions:
if question["paidOnly"] and not cookie:
continue
if question["frontendQuestionId"] == origin_problem_id:
problem_slug = question["titleSlug"]
break
problem_slug = get_question_slug_by_id(origin_problem_id, problem_category, cookie)
if not problem_slug:
logging.error(f"Unable to find any questions with problem_id {origin_problem_id}"
f", possible questions: {questions}")
return 1
question_info = get_question_info(problem_slug, cookie)
if not question_info:
Expand Down
Loading