leetcode_cli/cache/
mod.rs

1//! Save bad network\'s ass.
2pub mod models;
3pub mod parser;
4pub mod schemas;
5mod sql;
6use self::models::*;
7use self::schemas::{problems::dsl::*, tags::dsl::*};
8use self::sql::*;
9use crate::helper::test_cases_path;
10use crate::{config::Config, err::Error, plugins::LeetCode};
11use anyhow::anyhow;
12use colored::Colorize;
13use diesel::prelude::*;
14use reqwest::Response;
15use serde::de::DeserializeOwned;
16use serde_json::Value;
17use std::collections::HashMap;
18
19/// sqlite connection
20pub fn conn(p: String) -> SqliteConnection {
21    SqliteConnection::establish(&p).unwrap_or_else(|_| panic!("Error connecting to {:?}", p))
22}
23
24/// Condition submit or test
25#[derive(Clone, Debug)]
26#[derive(Default)]
27pub enum Run {
28    Test,
29    #[default]
30    Submit,
31}
32
33
34/// Requests if data not download
35#[derive(Clone)]
36pub struct Cache(pub LeetCode);
37
38impl Cache {
39    /// Ref to sqlite connection
40    fn conn(&self) -> Result<SqliteConnection, Error> {
41        Ok(conn(self.0.conf.storage.cache()?))
42    }
43
44    /// Clean cache
45    pub fn clean(&self) -> Result<(), Error> {
46        Ok(std::fs::remove_file(self.0.conf.storage.cache()?)?)
47    }
48
49    /// ref to download problems
50    pub async fn update(self) -> Result<(), Error> {
51        self.download_problems().await?;
52        Ok(())
53    }
54
55    pub fn update_after_ac(self, rid: i32) -> Result<(), Error> {
56        let mut c = conn(self.0.conf.storage.cache()?);
57        let target = problems.filter(id.eq(rid));
58        diesel::update(target)
59            .set(status.eq("ac"))
60            .execute(&mut c)?;
61        Ok(())
62    }
63
64    async fn get_user_info(&self) -> Result<(String, bool), Error> {
65        let user = parser::user(self.clone().0.get_user_info().await?.json().await?);
66        match user {
67            None => Err(Error::NoneError),
68            Some(None) => Err(Error::CookieError),
69            Some(Some((s, b))) => Ok((s, b)),
70        }
71    }
72
73    async fn is_session_bad(&self) -> bool {
74        // i.e. self.get_user_info().contains_err(Error::CookieError)
75        matches!(self.get_user_info().await, Err(Error::CookieError))
76    }
77
78    async fn resp_to_json<T: DeserializeOwned>(&self, resp: Response) -> Result<T, Error> {
79        let maybe_json: Result<T, _> = resp.json().await;
80        if maybe_json.is_err() && self.is_session_bad().await {
81            Err(Error::CookieError)
82        } else {
83            Ok(maybe_json?)
84        }
85    }
86
87    /// Download leetcode problems to db
88    pub async fn download_problems(self) -> Result<Vec<Problem>, Error> {
89        info!("Fetching leetcode problems...");
90        let mut ps: Vec<Problem> = vec![];
91
92        for i in &self.0.conf.sys.categories.to_owned() {
93            let json = self
94                .0
95                .clone()
96                .get_category_problems(i)
97                .await?
98                .json() // does not require LEETCODE_SESSION
99                .await?;
100            parser::problem(&mut ps, json).ok_or(Error::NoneError)?;
101        }
102
103        diesel::replace_into(problems)
104            .values(&ps)
105            .execute(&mut self.conn()?)?;
106
107        Ok(ps)
108    }
109
110    /// Get problem
111    pub fn get_problem(&self, rfid: i32) -> Result<Problem, Error> {
112        let p: Problem = problems.filter(fid.eq(rfid)).first(&mut self.conn()?)?;
113        if p.category != "algorithms" {
114            return Err(anyhow!("No support for database and shell questions yet").into());
115        }
116
117        Ok(p)
118    }
119
120    /// Get problem from name
121    pub fn get_problem_id_from_name(&self, problem_name: &String) -> Result<i32, Error> {
122        let p: Problem = problems
123            .filter(name.eq(problem_name))
124            .first(&mut self.conn()?)?;
125        if p.category != "algorithms" {
126            return Err(anyhow!("No support for database and shell questions yet").into());
127        }
128        Ok(p.fid)
129    }
130
131    /// Get daily problem
132    pub async fn get_daily_problem_id(&self) -> Result<i32, Error> {
133        parser::daily(
134            self.clone()
135                .0
136                .get_question_daily()
137                .await?
138                .json() // does not require LEETCODE_SESSION
139                .await?,
140        )
141        .ok_or(Error::NoneError)
142    }
143
144    /// Get problems from cache
145    pub fn get_problems(&self) -> Result<Vec<Problem>, Error> {
146        Ok(problems.load::<Problem>(&mut self.conn()?)?)
147    }
148
149    /// Get question
150    #[allow(clippy::useless_let_if_seq)]
151    pub async fn get_question(&self, rfid: i32) -> Result<Question, Error> {
152        let target: Problem = problems.filter(fid.eq(rfid)).first(&mut self.conn()?)?;
153
154        let ids = match target.level {
155            1 => target.fid.to_string().green(),
156            2 => target.fid.to_string().yellow(),
157            3 => target.fid.to_string().red(),
158            _ => target.fid.to_string().dimmed(),
159        };
160
161        println!(
162            "\n[{}] {} {}\n\n",
163            &ids,
164            &target.name.bold().underline(),
165            "is on the run...".dimmed()
166        );
167
168        if target.category != "algorithms" {
169            return Err(anyhow!("No support for database and shell questions yet").into());
170        }
171
172        let mut rdesc = Question::default();
173        if !target.desc.is_empty() {
174            rdesc = serde_json::from_str(&target.desc)?;
175        } else {
176            let json: Value = self
177                .0
178                .clone()
179                .get_question_detail(&target.slug)
180                .await?
181                .json()
182                .await?;
183            debug!("{:#?}", &json);
184            match parser::desc(&mut rdesc, json) {
185                None => return Err(Error::NoneError),
186                Some(false) => {
187                    return if self.is_session_bad().await {
188                        Err(Error::CookieError)
189                    } else {
190                        Err(Error::PremiumError)
191                    }
192                }
193                Some(true) => (),
194            }
195
196            // update the question
197            let sdesc = serde_json::to_string(&rdesc)?;
198            diesel::update(&target)
199                .set(desc.eq(sdesc))
200                .execute(&mut self.conn()?)?;
201        }
202
203        Ok(rdesc)
204    }
205
206    pub async fn get_tagged_questions(self, rslug: &str) -> Result<Vec<String>, Error> {
207        trace!("Geting {} questions...", &rslug);
208        let ids: Vec<String>;
209        let rtag = tags
210            .filter(tag.eq(rslug.to_string()))
211            .first::<Tag>(&mut self.conn()?);
212        if let Ok(t) = rtag {
213            trace!("Got {} questions from local cache...", &rslug);
214            ids = serde_json::from_str(&t.refs)?;
215        } else {
216            ids = parser::tags(
217                self.clone()
218                    .0
219                    .get_question_ids_by_tag(rslug)
220                    .await?
221                    .json()
222                    .await?,
223            )
224            .ok_or(Error::NoneError)?;
225            let t = Tag {
226                r#tag: rslug.to_string(),
227                r#refs: serde_json::to_string(&ids)?,
228            };
229
230            diesel::insert_into(tags)
231                .values(&t)
232                .execute(&mut self.conn()?)?;
233        }
234
235        Ok(ids)
236    }
237
238    pub fn get_tags(&self) -> Result<Vec<Tag>, Error> {
239        Ok(tags.load::<Tag>(&mut self.conn()?)?)
240    }
241
242    /// run_code data
243    async fn pre_run_code(
244        &self,
245        run: Run,
246        rfid: i32,
247        test_case: Option<String>,
248    ) -> Result<(HashMap<&'static str, String>, [String; 2]), Error> {
249        trace!("pre-run code...");
250        use crate::helper::code_path;
251        use std::fs::File;
252        use std::io::Read;
253
254        let mut p = self.get_problem(rfid)?;
255        if p.desc.is_empty() {
256            trace!("Problem description does not exist, pull desc and exec again...");
257            self.get_question(rfid).await?;
258            p = self.get_problem(rfid)?;
259        }
260
261        let d: Question = serde_json::from_str(&p.desc)?;
262        let conf = &self.0.conf;
263        let mut json: HashMap<&'static str, String> = HashMap::new();
264        let mut code: String = "".to_string();
265
266        let maybe_file_testcases: Option<String> = test_cases_path(&p)
267            .map(|file_name| {
268                let mut tests = "".to_string();
269                File::open(file_name)
270                    .and_then(|mut file_descriptor| file_descriptor.read_to_string(&mut tests))
271                    .map(|_| Some(tests))
272                    .unwrap_or(None)
273            })
274            .unwrap_or(None);
275
276        let maybe_all_testcases: Option<String> = if d.all_cases.is_empty() {
277            None
278        } else {
279            Some(d.all_cases.to_string())
280        };
281
282        // Takes test cases using following priority
283        // 1. cli parameter
284        // 2. if test cases file exist, use the file test cases(user can edit it)
285        // 3. test cases from problem desc all test cases
286        // 4. sample test case from the task
287        let test_case = test_case
288            .or(maybe_file_testcases)
289            .or(maybe_all_testcases)
290            .unwrap_or(d.case);
291
292        File::open(code_path(&p, None)?)?.read_to_string(&mut code)?;
293
294        let code = if conf.code.edit_code_marker {
295            let begin = code.find(&conf.code.start_marker);
296            let end = code.find(&conf.code.end_marker);
297            if let (Some(l), Some(r)) = (begin, end) {
298                code.get((l + conf.code.start_marker.len())..r)
299                    .map(|s| s.to_string())
300                    .unwrap_or_else(|| code)
301            } else {
302                code
303            }
304        } else {
305            code
306        };
307
308        json.insert("lang", conf.code.lang.to_string());
309        json.insert("question_id", p.id.to_string());
310        json.insert("typed_code", code);
311
312        // pass manually data
313        json.insert("name", p.name.to_string());
314        json.insert("data_input", test_case);
315
316        let url = match run {
317            Run::Test => conf.sys.urls.test(&p.slug),
318            Run::Submit => {
319                json.insert("judge_type", "large".to_string());
320                conf.sys.urls.submit(&p.slug)
321            }
322        };
323
324        Ok((json, [url, conf.sys.urls.problem(&p.slug)]))
325    }
326
327    /// TODO: The real delay
328    async fn recur_verify(&self, rid: String) -> Result<VerifyResult, Error> {
329        use std::time::Duration;
330
331        trace!("Run verify recursion...");
332        std::thread::sleep(Duration::from_micros(3000));
333
334        let json: VerifyResult = self
335            .resp_to_json(self.clone().0.verify_result(rid.clone()).await?)
336            .await?;
337
338        Ok(json)
339    }
340
341    /// Exec problem filter —— Test or Submit
342    pub async fn exec_problem(
343        &self,
344        rfid: i32,
345        run: Run,
346        test_case: Option<String>,
347    ) -> Result<VerifyResult, Error> {
348        trace!("Exec problem filter —— Test or Submit");
349        let (json, [url, refer]) = self.pre_run_code(run.clone(), rfid, test_case).await?;
350        trace!("Pre-run code result {:#?}, {}, {}", json, url, refer);
351
352        let text = self
353            .0
354            .clone()
355            .run_code(json.clone(), url.clone(), refer.clone())
356            .await?
357            .text()
358            .await?;
359
360        let run_res: RunCode = serde_json::from_str(&text).map_err(|e| {
361            anyhow!("JSON error: {e}, please double check your session and csrf config.")
362        })?;
363
364        trace!("Run code result {:#?}", run_res);
365
366        // Check if leetcode accepted the Run request
367        if match run {
368            Run::Test => run_res.interpret_id.is_empty(),
369            Run::Submit => run_res.submission_id == 0,
370        } {
371            return Err(Error::CookieError);
372        }
373
374        let mut res: VerifyResult = VerifyResult::default();
375        while res.state != "SUCCESS" {
376            res = match run {
377                Run::Test => self.recur_verify(run_res.interpret_id.clone()).await?,
378                Run::Submit => self.recur_verify(run_res.submission_id.to_string()).await?,
379            };
380        }
381        trace!("Recur verify result {:#?}", res);
382
383        res.name = json.get("name").ok_or(Error::NoneError)?.to_string();
384        res.data_input = json.get("data_input").ok_or(Error::NoneError)?.to_string();
385        res.result_type = run;
386        Ok(res)
387    }
388
389    /// New cache
390    pub fn new() -> Result<Self, Error> {
391        let conf = Config::locate()?;
392        let mut c = conn(conf.storage.cache()?);
393        diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&mut c)?;
394        diesel::sql_query(CREATE_TAGS_IF_NOT_EXISTS).execute(&mut c)?;
395
396        Ok(Cache(LeetCode::new()?))
397    }
398}