Skip to content

Commit 59f075b

Browse files
authored
Raise Error::CookieError more often (#67)
* add graphql query for user info * add parser for graphql user info * return `CookieError` when leetcode rejects exec/test `Run` * add the `is_session_bad` method to somewhat-accurately determine when a LEETCODE_SESSION becomes invalid * When json parsing fails, if the underlying request requires user authentication, use `is_session_bad()` to check if LEETCODE_SESSION is valid.
1 parent ea066f1 commit 59f075b

File tree

3 files changed

+86
-17
lines changed

3 files changed

+86
-17
lines changed

src/cache/mod.rs

+43-17
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use self::sql::*;
99
use crate::{cfg, err::Error, plugins::LeetCode};
1010
use colored::Colorize;
1111
use diesel::prelude::*;
12+
use serde::de::DeserializeOwned;
1213
use serde_json::Value;
1314
use std::collections::HashMap;
15+
use reqwest::Response;
1416

1517
/// sqlite connection
1618
pub fn conn(p: String) -> SqliteConnection {
@@ -58,6 +60,34 @@ impl Cache {
5860
Ok(())
5961
}
6062

63+
async fn get_user_info(&self) -> Result<(String,bool), Error> {
64+
let user = parser::user(
65+
self.clone().0
66+
.get_user_info().await?
67+
.json().await?
68+
);
69+
match user {
70+
None => Err(Error::NoneError),
71+
Some(None) => Err(Error::CookieError),
72+
Some(Some((s,b))) => Ok((s,b))
73+
}
74+
}
75+
76+
async fn is_session_bad(&self) -> bool {
77+
// i.e. self.get_user_info().contains_err(Error::CookieError)
78+
match self.get_user_info().await {
79+
Err(Error::CookieError) => true,
80+
_ => false
81+
}
82+
}
83+
84+
async fn resp_to_json<T: DeserializeOwned>(&self, resp: Response) -> Result<T, Error> {
85+
let maybe_json: Result<T,_> = resp.json().await;
86+
if maybe_json.is_err() && self.is_session_bad().await {
87+
Err(Error::CookieError)
88+
} else { Ok(maybe_json?) }
89+
}
90+
6191
/// Download leetcode problems to db
6292
pub async fn download_problems(self) -> Result<Vec<Problem>, Error> {
6393
info!("Fetching leetcode problems...");
@@ -69,7 +99,7 @@ impl Cache {
6999
.clone()
70100
.get_category_problems(i)
71101
.await?
72-
.json()
102+
.json() // does not require LEETCODE_SESSION
73103
.await?;
74104
parser::problem(&mut ps, json).ok_or(Error::NoneError)?;
75105
}
@@ -100,7 +130,7 @@ impl Cache {
100130
.0
101131
.get_question_daily()
102132
.await?
103-
.json()
133+
.json() // does not require LEETCODE_SESSION
104134
.await?
105135
).ok_or(Error::NoneError)
106136
}
@@ -265,30 +295,20 @@ impl Cache {
265295

266296
/// TODO: The real delay
267297
async fn recur_verify(&self, rid: String) -> Result<VerifyResult, Error> {
268-
use serde_json::{from_str, Error as SJError};
269298
use std::time::Duration;
270299

271300
trace!("Run veriy recursion...");
272301
std::thread::sleep(Duration::from_micros(3000));
273302

274-
// debug resp raw text
275-
let debug_raw = self
303+
let json: VerifyResult = self.resp_to_json(
304+
self
276305
.clone()
277306
.0
278307
.verify_result(rid.clone())
279308
.await?
280-
.text()
281-
.await?;
282-
debug!("debug resp raw text: \n{:#?}", &debug_raw);
283-
if debug_raw.is_empty() {
284-
return Err(Error::CookieError);
285-
}
286-
287-
// debug json deserializing
288-
let debug_json: Result<VerifyResult, SJError> = from_str(&debug_raw);
289-
debug!("debug json deserializing: \n{:#?}", &debug_json);
309+
).await?;
290310

291-
Ok(debug_json?)
311+
Ok(json)
292312
}
293313

294314
/// Exec problem filter —— Test or Submit
@@ -307,10 +327,16 @@ impl Cache {
307327
.clone()
308328
.run_code(json.clone(), url.clone(), refer.clone())
309329
.await?
310-
.json()
330+
.json() // does not require LEETCODE_SESSION (very oddly)
311331
.await?;
312332
trace!("Run code result {:#?}", run_res);
313333

334+
// Check if leetcode accepted the Run request
335+
if match run {
336+
Run::Test => run_res.interpret_id.is_empty(),
337+
Run::Submit => run_res.submission_id == 0
338+
} { return Err(Error::CookieError) }
339+
314340
let mut res: VerifyResult = VerifyResult::default();
315341
while res.state != "SUCCESS" {
316342
res = match run {

src/cache/parser.rs

+15
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ pub fn daily(v: Value) -> Option<i32> {
8888
.parse().ok()
8989
}
9090

91+
/// user parser
92+
pub fn user(v: Value) -> Option<Option<(String,bool)>> {
93+
// None => error while parsing
94+
// Some(None) => User not found
95+
// Some("...") => username
96+
let user = v.as_object()?.get("data")?
97+
.as_object()?.get("user")?;
98+
if *user == Value::Null { return Some(None) }
99+
let user = user.as_object()?;
100+
Some(Some((
101+
user.get("username")?.as_str()?.to_owned(),
102+
user.get("isCurrentUserPremium")?.as_bool()?
103+
)))
104+
}
105+
91106
pub use ss::ssr;
92107
/// string or squence
93108
mod ss {

src/plugins/leetcode.rs

+28
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,34 @@ impl LeetCode {
121121
.await
122122
}
123123

124+
pub async fn get_user_info(self) -> Result<Response, Error> {
125+
trace!("Requesting user info...");
126+
let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?;
127+
let mut json: Json = HashMap::new();
128+
json.insert("operationName", "a".to_string());
129+
json.insert(
130+
"query",
131+
"query a {
132+
user {
133+
username
134+
isCurrentUserPremium
135+
}
136+
}".to_owned()
137+
);
138+
139+
Req {
140+
default_headers: self.default_headers,
141+
refer: None,
142+
info: false,
143+
json: Some(json),
144+
mode: Mode::Post,
145+
name: "get_user_info",
146+
url: (*url).to_string(),
147+
}
148+
.send(&self.client)
149+
.await
150+
}
151+
124152
/// Get daily problem
125153
pub async fn get_question_daily(self) -> Result<Response, Error> {
126154
trace!("Requesting daily problem...");

0 commit comments

Comments
 (0)