1use error::Errors;
2use problem_actions::Problem;
3use problem_build::{Filters, ProblemBuilder};
4use profile::{MyProfile, UserProfile};
5use reqwest::header::{HeaderMap, HeaderValue};
6use resources::{
7 cookie::CookieData, descr::ProblemData, fav_list::FavoriteList,
8 problemfulldata::ProblemFullData,
9};
10use serde_json::json;
11
12pub mod error;
13pub mod problem_actions;
14pub mod problem_build;
15pub mod profile;
16pub mod resources;
17
18#[derive(Debug)]
19pub struct UserApi {
20 client: reqwest::Client,
21}
22
23impl UserApi {
24 pub async fn new(cookie: &str) -> Result<Self, Errors> {
25 let mut headers = HeaderMap::new();
26
27 headers.insert("Host", HeaderValue::from_static("leetcode.com"));
28 headers.insert("User-Agent", HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"));
29 headers.insert("Origin", HeaderValue::from_static("https://leetcode.com"));
30 headers.insert("Referer", HeaderValue::from_static("https://leetcode.com/"));
31 headers.insert("content-type", HeaderValue::from_static("application/json"));
32 headers.insert("Connection", HeaderValue::from_static("keep-alive"));
33 headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("empty"));
34 headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("cors"));
35 headers.insert("Sec-Fetch-Site", HeaderValue::from_static("same-origin"));
36
37 let valid_data = Self::valid_check(headers.clone(), &cookie).await?;
38
39 let cookie = if valid_data.0 {
40 cookie
41 } else {
42 return Err(error::Errors::ApiError(
43 "Cookie is invalid or User not signed".into(),
44 ));
45 };
46
47 let token = valid_data.1;
48
49 headers.insert("Cookie", HeaderValue::from_str(&cookie).unwrap());
50 headers.insert("x-csrftoken", HeaderValue::from_str(&token).unwrap());
51
52 let client = reqwest::Client::builder()
53 .default_headers(headers)
54 .build()?;
55
56 Ok(Self { client })
57 }
58
59 async fn valid_check(mut headers: HeaderMap, cookie: &str) -> Result<(bool, String), Errors> {
60 let token = if let Some(token) = cookie
61 .strip_prefix("csrftoken=")
62 .and_then(|val| Some(&val[..64]))
63 {
64 token
65 } else {
66 return Err(Errors::ApiError("Cannot take token from cookie".into()));
67 };
68
69 headers.insert("Cookie", HeaderValue::from_str(&cookie).unwrap());
70 headers.insert("x-csrftoken", HeaderValue::from_str(&token).unwrap());
71 headers.insert("content-type", HeaderValue::from_static("application/json"));
72
73 let json_data = json!({
74 "operationName": "globalData",
75 "variables": {},
76 "query": "query globalData {\n userStatus {\n isSignedIn\n isAdmin\n isStaff\n isSuperuser\n isMockUser\n isTranslator\n isPremium\n isVerified\n checkedInToday\n username\n realName\n avatar\n optedIn\n requestRegion\n region\n activeSessionId\n permissions\n notificationStatus {\n lastModified\n numUnread\n __typename\n }\n completedFeatureGuides\n __typename\n }\n recaptchaKey\n}"
77 });
78
79 let query = serde_json::to_string(&json_data)?;
80
81 let client = reqwest::Client::new();
82
83 let cookie_info = client
84 .post("https://leetcode.com/graphql/")
85 .body(query)
86 .headers(headers.clone())
87 .send()
88 .await?
89 .text()
90 .await?;
91
92 let resp_info = serde_json::from_str::<CookieData>(&cookie_info)?;
93
94 if resp_info.data.userStatus.isSignedIn {
95 return Ok((true, String::from(token)));
96 }
97
98 Ok((false, String::from(token)))
99 }
100
101 pub async fn set_problem(&self, problem_name: &str) -> Result<Problem, Errors> {
102 let info = Self::fetch_problem_full_data(
103 &self,
104 Self::get_question_name(&self, String::from(problem_name)).await?,
105 )
106 .await?;
107
108 Ok(Problem {
109 client: self.client.clone(),
110 task_search_name: info.0,
111 full_data: info.1,
112 })
113 }
114
115 pub async fn set_problem_by_id(&self, problem_id: u32) -> Result<Problem, Errors> {
116 let info = Self::fetch_problem_full_data(
117 &self,
118 Self::get_question_name(&self, problem_id.to_string()).await?,
119 )
120 .await?;
121
122 Ok(Problem {
123 client: self.client.clone(),
124 task_search_name: info.0,
125 full_data: info.1,
126 })
127 }
128
129 async fn fetch_problem_full_data(
130 &self,
131 problem: String,
132 ) -> Result<(String, ProblemFullData), Errors> {
133 let json_obj = json!({
134 "operationName": "questionData",
135 "variables": {
136 "titleSlug": problem
137 },
138 "query": "query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n boundTopicId\n title\n titleSlug\n content\n translatedTitle\n translatedContent\n isPaidOnly\n canSeeQuestion\n difficulty\n likes\n dislikes\n isLiked\n similarQuestions\n exampleTestcases\n categoryTitle\n contributors {\n username\n profileUrl\n avatarUrl\n __typename\n }\n topicTags {\n name\n slug\n translatedName\n __typename\n }\n companyTagStats\n codeSnippets {\n lang\n langSlug\n code\n __typename\n }\n stats\n hints\n solution {\n id\n canSeeDetail\n paidOnly\n hasVideoSolution\n paidOnlyVideo\n __typename\n }\n status\n sampleTestCase\n metaData\n judgerAvailable\n judgeType\n mysqlSchemas\n enableRunCode\n enableTestMode\n enableDebugger\n envInfo\n libraryUrl\n adminUrl\n challengeQuestion {\n id\n date\n incompleteChallengeCount\n streakCount\n type\n __typename\n }\n __typename\n }\n}"
139 });
140
141 let query = serde_json::to_string(&json_obj)?;
142
143 let full_data = self
144 .client
145 .post("https://leetcode.com/graphql/")
146 .body(query)
147 .send()
148 .await?
149 .json::<ProblemFullData>()
150 .await?;
151
152 Ok((full_data.data.question.titleSlug.clone(), full_data))
153 }
154
155 pub async fn show_problm_list(
156 &self,
157 key_word: &str,
158 limit: u32,
159 ) -> Result<ProblemData, Errors> {
160 let query = json!({
161 "query": "query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) { problemsetQuestionList: questionList( categorySlug: $categorySlug limit: $limit skip: $skip filters: $filters ) { total: totalNum questions: data { acRate difficulty freqBar frontendQuestionId: questionFrontendId isFavor paidOnly: isPaidOnly status title titleSlug topicTags { name id slug } hasSolution hasVideoSolution } } }",
162 "variables": {
163 "categorySlug": "",
164 "skip": 0,
165 "limit": limit,
166 "filters": {
167 "searchKeywords": String::from(key_word)
168 }
169 },
170 "operationName": "problemsetQuestionList"
171 });
172
173 let query = serde_json::to_string(&query)?;
174
175 let problem_info = self
176 .client
177 .post("https://leetcode.com/graphql/")
178 .body(query)
179 .send()
180 .await?
181 .text()
182 .await?;
183
184 Ok(serde_json::from_str::<ProblemData>(&problem_info)?)
185 }
186
187 pub fn problem_builder(&self) -> ProblemBuilder {
188 ProblemBuilder {
189 client: self.client.clone(),
190 key_word: String::new(),
191 limit: 5,
192 category: String::new(),
193 filters: Filters::default(),
194 }
195 }
196
197 async fn get_question_name(&self, name: String) -> Result<String, Errors> {
198 let query = json!({
199 "query": "query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) { problemsetQuestionList: questionList( categorySlug: $categorySlug limit: $limit skip: $skip filters: $filters ) { questions: data { titleSlug } } }",
200 "variables": {
201 "categorySlug": "",
202 "skip": 0,
203 "limit": 1,
204 "filters": {
205 "searchKeywords": name
206 }
207 },
208 "operationName": "problemsetQuestionList"
209 });
210
211 let query = serde_json::to_string(&query)?;
212
213 let problem_info = self
214 .client
215 .post("https://leetcode.com/graphql/")
216 .body(query)
217 .send()
218 .await?
219 .text()
220 .await?;
221
222 let parsed_data: ProblemData = serde_json::from_str(&problem_info)?;
223
224 Ok(parsed_data.data.problemsetQuestionList.questions[0]
225 .titleSlug
226 .clone())
227 }
228
229 pub async fn my_profile(&self) -> Result<MyProfile, Errors> {
230 Ok(MyProfile {
231 client: self.client.clone(),
232 fav_lists: Self::fetch_fav_list_data(&self).await?,
233 })
234 }
235
236 pub fn find_profile(&self, username: &str) -> UserProfile {
237 UserProfile {
238 client: self.client.clone(),
239 username: String::from(username),
240 }
241 }
242
243 async fn fetch_fav_list_data(&self) -> Result<FavoriteList, Errors> {
244 let query = json!({
245 "operationName": "favoritesList",
246 "variables": {},
247 "query": "query favoritesList {
248 favoritesLists {
249 allFavorites {
250 idHash
251 name
252 description
253 viewCount
254 creator
255 isWatched
256 isPublicFavorite
257 questions {
258 questionId
259 status
260 title
261 titleSlug
262 __typename
263 }
264 __typename
265 }
266 watchedFavorites {
267 idHash
268 name
269 description
270 viewCount
271 creator
272 isWatched
273 isPublicFavorite
274 questions {
275 questionId
276 status
277 title
278 titleSlug
279 __typename
280 }
281 __typename
282 }
283 __typename
284 }
285 userStatus {
286 username
287 __typename
288 }
289 }"
290 });
291
292 let query = serde_json::to_string(&query)?;
293
294 let list_data = self
295 .client
296 .post("https://leetcode.com/graphql/")
297 .body(query)
298 .send()
299 .await?
300 .text()
301 .await?;
302
303 Ok(serde_json::from_str::<FavoriteList>(&list_data)?)
304 }
305}
306
307#[derive(Debug)]
308pub enum ProgrammingLanguage {
309 CPP,
310 Java,
311 Python,
312 Python3,
313 C,
314 CSharp,
315 JavaScript,
316 TypeScript,
317 Ruby,
318 Swift,
319 Go,
320 Bash,
321 Scala,
322 Kotlin,
323 Rust,
324 PHP,
325 Racket,
326 Erlang,
327 Elixir,
328 Dart,
329 Pandas,
330 React,
331}