leetcoderustapi/
profile.rs

1use serde_json::json;
2
3use crate::{
4    error::Errors,
5    resources::{
6        beat_stats::BeatStats, data_profile::ProfileData, fav_list::FavoriteList,
7        lang_stats::LanguageStats, notification::NotificationsData,
8        pub_data_profile::UserFoundData, skill_stats::SkillStats, subm_list::RecentSubmList,
9    },
10};
11
12#[derive(Debug)]
13pub struct MyProfile {
14    pub(crate) client: reqwest::Client,
15    pub(crate) fav_lists: FavoriteList,
16}
17
18impl MyProfile {
19    pub async fn create_list(&self, list_name: &str) -> Result<&MyProfile, Errors> {
20        let query = json!({ "name": list_name });
21
22        let query = serde_json::to_string(&query)?;
23
24        self.client
25            .get("https://leetcode.com/list/api/")
26            .body(query)
27            .send()
28            .await?;
29        Ok(self)
30    }
31    pub fn show_lists(&self) -> FavoriteList {
32        self.fav_lists.clone()
33    }
34
35    pub async fn rename_list(
36        &self,
37        prev_list_name: &str,
38        new_list_name: &str,
39    ) -> Result<&MyProfile, Errors> {
40        let id_hash = if let Some(id) = Self::get_id_hash(&self, prev_list_name) {
41            id
42        } else {
43            return Err(Errors::ApiError("Provided name doesn't found".into()));
44        };
45        let query = json!({
46            "favorite_id_hash": id_hash.0,
47            "is_public_favorite": id_hash.1,
48            "name": new_list_name
49        });
50
51        let query = serde_json::to_string(&query)?;
52
53        self.client
54            .put("https://leetcode.com/list/api/")
55            .body(query)
56            .send()
57            .await?;
58        Ok(self)
59    }
60
61    pub async fn set_public(&self, list_name: &str) -> Result<&MyProfile, Errors> {
62        let id_hash = if let Some(id) = Self::get_id_hash(&self, list_name) {
63            id
64        } else {
65            return Err(Errors::ApiError(
66                "Provided name doesn't found in lists".into(),
67            ));
68        };
69        let query = json!({
70            "favorite_id_hash": id_hash.0,
71            "is_public_favorite": true,
72            "name": list_name
73        });
74
75        let query = serde_json::to_string(&query)?;
76
77        self.client
78            .put("https://leetcode.com/list/api/")
79            .body(query)
80            .send()
81            .await?;
82        Ok(self)
83    }
84
85    pub async fn set_private(&self, list_name: &str) -> Result<&MyProfile, Errors> {
86        let id_hash = if let Some(id) = Self::get_id_hash(&self, list_name) {
87            id
88        } else {
89            return Err(Errors::ApiError(
90                "Provided name doesn't found in lists".into(),
91            ));
92        };
93        let query = json!({
94            "favorite_id_hash": id_hash.0,
95            "is_public_favorite": true,
96            "name": list_name
97        });
98
99        let query = serde_json::to_string(&query)?;
100
101        self.client
102            .put("https://leetcode.com/list/api/")
103            .body(query)
104            .send()
105            .await?;
106        Ok(self)
107    }
108
109    pub async fn get_share_url(&self, list_name: &str) -> Result<String, Errors> {
110        let id_hash = if let Some(id) = Self::get_id_hash(&self, list_name) {
111            id
112        } else {
113            return Err(Errors::ApiError(
114                "Provided name doesn't found in lists".into(),
115            ));
116        };
117
118        Ok(format!("https://leetcode.com/list/{}", id_hash.0))
119    }
120
121    pub async fn delete_list(&self, list_name: &str) -> Result<&MyProfile, Errors> {
122        let id_hash = if let Some(id) = Self::get_id_hash(&self, list_name) {
123            id
124        } else {
125            return Err(Errors::ApiError(
126                "Provided name doesn't found in lists".into(),
127            ));
128        };
129
130        self.client
131            .delete(format!("https://leetcode.com/list/api/{}", id_hash.0))
132            .send()
133            .await?;
134
135        Ok(self)
136    }
137
138    fn get_id_hash(&self, list_name: &str) -> Option<(String, bool)> {
139        for favourite in &self.fav_lists.data.favoritesLists.allFavorites {
140            if favourite.name == list_name.to_string() {
141                return Some((favourite.idHash.clone(), favourite.isPublicFavorite.clone()));
142            }
143        }
144        None
145    }
146
147    pub async fn get_notifications(&self) -> Result<NotificationsData, Errors> {
148        let operation_name = "fetchNotifications";
149        let variables = json!({ "first": 10 });
150        let query = r#"query fetchNotifications($first: Int!, $after: String) {
151        notifications(first: $first, after: $after) {
152            edges {
153                node {
154                    id
155                    notificationId
156                    modifiedDate
157                    actioned
158                    notificationData {
159                        id
160                        content
161                        type
162                        metadata
163                        __typename
164                    }
165                    __typename
166                }
167                __typename
168            }
169            pageInfo {
170                endCursor
171                hasNextPage
172                __typename
173            }
174            __typename
175        }
176        }"#;
177
178        let json_data = json!({
179            "operationName": operation_name,
180            "variables": variables,
181            "query": query,
182        });
183
184        let query = serde_json::to_string(&json_data)?;
185
186        let problem_info = self
187            .client
188            .post("https://leetcode.com/graphql/")
189            .body(query)
190            .send()
191            .await?
192            .text()
193            .await?;
194
195        Ok(serde_json::from_str::<NotificationsData>(&problem_info)?)
196    }
197
198    pub async fn profile_info(&self) -> Result<ProfileData, Errors> {
199        let query = r#"
200        query globalData {
201          userStatus {
202            userId
203            isSignedIn
204            isMockUser
205            isPremium
206            isVerified
207            username
208            avatar
209            isAdmin
210            isSuperuser
211            permissions
212            isTranslator
213            activeSessionId
214            checkedInToday
215            notificationStatus {
216              lastModified
217              numUnread
218            }
219          }
220        }
221        "#;
222
223        let variables = json!({});
224        let operation_name = "globalData";
225
226        let json_data = json!({
227            "query": query,
228            "variables": variables,
229            "operationName": operation_name
230        });
231
232        let query = serde_json::to_string(&json_data)?;
233
234        let data_info = self
235            .client
236            .post("https://leetcode.com/graphql/")
237            .body(query)
238            .send()
239            .await?
240            .text()
241            .await?;
242
243        Ok(serde_json::from_str::<ProfileData>(&data_info)?)
244    }
245}
246
247#[derive(Debug)]
248pub struct UserProfile {
249    pub(crate) client: reqwest::Client,
250    pub(crate) username: String,
251}
252
253impl UserProfile {
254    pub async fn user_stats(&self) -> Result<UserFoundData, Errors> {
255        let query = json!({
256            "query": "query userPublicProfile($username: String!) {\n  matchedUser(username: $username) {\n    contestBadge {\n      name\n      expired\n      hoverText\n      icon\n    }\n    username\n    githubUrl\n    twitterUrl\n    linkedinUrl\n    profile {\n      ranking\n      userAvatar\n      realName\n      aboutMe\n      school\n      websites\n      countryName\n      company\n      jobTitle\n      skillTags\n      postViewCount\n      postViewCountDiff\n      reputation\n      reputationDiff\n      solutionCount\n      solutionCountDiff\n      categoryDiscussCount\n      categoryDiscussCountDiff\n    }\n  }\n}",
257            "variables": {
258                "username": self.username
259            },
260            "operationName": "userPublicProfile"
261        });
262
263        let query = serde_json::to_string(&query)?;
264
265        let data_info = self
266            .client
267            .post("https://leetcode.com/graphql/")
268            .body(query)
269            .send()
270            .await?
271            .text()
272            .await?;
273
274        Ok(serde_json::from_str::<UserFoundData>(&data_info)?)
275    }
276
277    pub async fn language_stats(&self) -> Result<LanguageStats, Errors> {
278        let query = json!({
279            "query": "query languageStats($username: String!) {\n  matchedUser(username: $username) {\n    languageProblemCount {\n      languageName\n      problemsSolved\n    }\n  }\n}",
280            "variables": {
281                "username": self.username
282            },
283            "operationName": "languageStats"
284        });
285
286        let query = serde_json::to_string(&query)?;
287
288        let data_info = self
289            .client
290            .post("https://leetcode.com/graphql/")
291            .body(query)
292            .send()
293            .await?
294            .text()
295            .await?;
296
297        Ok(serde_json::from_str::<LanguageStats>(&data_info)?)
298    }
299
300    pub async fn skill_stats(&self) -> Result<SkillStats, Errors> {
301        let query = json!({
302            "query": r#"
303                query skillStats($username: String!) {
304                  matchedUser(username: $username) {
305                    tagProblemCounts {
306                      advanced {
307                        tagName
308                        tagSlug
309                        problemsSolved
310                      }
311                      intermediate {
312                        tagName
313                        tagSlug
314                        problemsSolved
315                      }
316                      fundamental {
317                        tagName
318                        tagSlug
319                        problemsSolved
320                      }
321                    }
322                  }
323                }
324            "#,
325            "variables": {
326                "username": self.username
327            },
328            "operationName": "skillStats"
329        });
330
331        let query = serde_json::to_string(&query)?;
332
333        let data_info = self
334            .client
335            .post("https://leetcode.com/graphql/")
336            .body(query)
337            .send()
338            .await?
339            .text()
340            .await?;
341
342        Ok(serde_json::from_str::<SkillStats>(&data_info)?)
343    }
344
345    pub async fn problem_beat_stats(&self) -> Result<BeatStats, Errors> {
346        let query = json!({
347            "query": "query userProblemsSolved($username: String!) {\n  matchedUser(username: $username) {\n    problemsSolvedBeatsStats {\n      difficulty\n      percentage\n    }\n    submitStatsGlobal {\n      acSubmissionNum {\n        difficulty\n        count\n      }\n    }\n  }\n}",
348            "variables": {
349                "username": self.username
350            },
351            "operationName": "userProblemsSolved"
352        });
353
354        let query = serde_json::to_string(&query)?;
355
356        let data_info = self
357            .client
358            .post("https://leetcode.com/graphql/")
359            .body(query)
360            .send()
361            .await?
362            .text()
363            .await?;
364
365        Ok(serde_json::from_str::<BeatStats>(&data_info)?)
366    }
367
368    pub async fn recent_subm_list(&self) -> Result<RecentSubmList, Errors> {
369        let query = json!({
370            "query": "query recentAcSubmissions($username: String!, $limit: Int!) {\n  recentAcSubmissionList(username: $username, limit: $limit) {\n    id\n    title\n    titleSlug\n    timestamp\n  }\n}",
371            "variables": {
372                "username": self.username,
373                "limit": 15
374            },
375            "operationName": "recentAcSubmissions"
376        });
377
378        let query = serde_json::to_string(&query)?;
379
380        let data_info = self
381            .client
382            .post("https://leetcode.com/graphql/")
383            .body(query)
384            .send()
385            .await?
386            .text()
387            .await?;
388
389        Ok(serde_json::from_str::<RecentSubmList>(&data_info)?)
390    }
391
392    pub async fn deactivate_token(&self) -> Result<(), Errors> {
393        self.client
394            .post("https://leetcode.com/accounts/logout/")
395            .send()
396            .await?
397            .text()
398            .await?;
399        Ok(())
400    }
401}