|
1 | 1 | import json
|
2 |
| -from typing import cast |
| 2 | +from typing import Any, Mapping, cast |
3 | 3 |
|
4 | 4 | import tornado
|
5 | 5 | from tornado.gen import multi
|
|
10 | 10 |
|
11 | 11 | LEETCODE_GRAPHQL_URL = "https://leetcode.com/graphql"
|
12 | 12 |
|
13 |
| -type QueryType = dict[str, str | dict[str, str]] |
| 13 | +type QueryType = dict[str, str | Mapping[str, Any]] |
14 | 14 |
|
15 | 15 |
|
16 | 16 | class LeetCodeHandler(BaseHandler):
|
@@ -174,3 +174,93 @@ async def get(self):
|
174 | 174 | )
|
175 | 175 | )
|
176 | 176 | self.finish(res)
|
| 177 | + |
| 178 | + |
| 179 | +class LeetCodeQuestionHandler(LeetCodeHandler): |
| 180 | + route = r"leetcode/questions" |
| 181 | + |
| 182 | + @tornado.web.authenticated |
| 183 | + async def post(self): |
| 184 | + body = self.get_json_body() |
| 185 | + if not body: |
| 186 | + self.set_status(400) |
| 187 | + self.finish(json.dumps({"message": "Request body is required"})) |
| 188 | + return |
| 189 | + |
| 190 | + body = cast("dict[str, str|int]", body) |
| 191 | + skip = cast(int, body.get("skip", 0)) |
| 192 | + limit = cast(int, body.get("limit", 0)) |
| 193 | + keyword = cast(str, body.get("keyword", "")) |
| 194 | + sortField = cast(str, body.get("sortField", "CUSTOM")) |
| 195 | + sortOrder = cast(str, body.get("sortOrder", "ASCENDING")) |
| 196 | + |
| 197 | + await self.graphql( |
| 198 | + name="question_list", |
| 199 | + query={ |
| 200 | + "query": """query problemsetQuestionListV2($filters: QuestionFilterInput, |
| 201 | + $limit: Int, |
| 202 | + $searchKeyword: String, |
| 203 | + $skip: Int, |
| 204 | + $sortBy: QuestionSortByInput, |
| 205 | + $categorySlug: String) { |
| 206 | + problemsetQuestionListV2( |
| 207 | + filters: $filters |
| 208 | + limit: $limit |
| 209 | + searchKeyword: $searchKeyword |
| 210 | + skip: $skip |
| 211 | + sortBy: $sortBy |
| 212 | + categorySlug: $categorySlug |
| 213 | + ) { |
| 214 | + questions { |
| 215 | + id |
| 216 | + titleSlug |
| 217 | + title |
| 218 | + translatedTitle |
| 219 | + questionFrontendId |
| 220 | + paidOnly |
| 221 | + difficulty |
| 222 | + topicTags { |
| 223 | + name |
| 224 | + slug |
| 225 | + nameTranslated |
| 226 | + } |
| 227 | + status |
| 228 | + isInMyFavorites |
| 229 | + frequency |
| 230 | + acRate |
| 231 | + } |
| 232 | + totalLength |
| 233 | + finishedLength |
| 234 | + hasMore |
| 235 | + } |
| 236 | + }""", |
| 237 | + "variables": { |
| 238 | + "skip": skip, |
| 239 | + "limit": limit, |
| 240 | + "searchKeyword": keyword, |
| 241 | + "categorySlug": "algorithms", |
| 242 | + "filters": { |
| 243 | + "filterCombineType": "ALL", |
| 244 | + "statusFilter": { |
| 245 | + "questionStatuses": ["TO_DO"], |
| 246 | + "operator": "IS", |
| 247 | + }, |
| 248 | + "difficultyFilter": { |
| 249 | + "difficulties": ["MEDIUM", "HARD"], |
| 250 | + "operator": "IS", |
| 251 | + }, |
| 252 | + "languageFilter": {"languageSlugs": [], "operator": "IS"}, |
| 253 | + "topicFilter": {"topicSlugs": [], "operator": "IS"}, |
| 254 | + "acceptanceFilter": {}, |
| 255 | + "frequencyFilter": {}, |
| 256 | + "frontendIdFilter": {}, |
| 257 | + "lastSubmittedFilter": {}, |
| 258 | + "publishedFilter": {}, |
| 259 | + "companyFilter": {"companySlugs": [], "operator": "IS"}, |
| 260 | + "positionFilter": {"positionSlugs": [], "operator": "IS"}, |
| 261 | + "premiumFilter": {"premiumStatus": [], "operator": "IS"}, |
| 262 | + }, |
| 263 | + "sortBy": {"sortField": sortField, "sortOrder": sortOrder}, |
| 264 | + }, |
| 265 | + }, |
| 266 | + ) |
0 commit comments