extern crate reqwest; extern crate serde_json; use serde_json::Value; use std::fmt::{Display, Error, Formatter}; const PROBLEMS_URL: &str = "https://leetcode.cn/api/problems/algorithms/"; const GRAPHQL_URL: &str = "https://leetcode.cn/graphql"; const QUESTION_QUERY_STRING: &str = r#" query questionData($titleSlug: String!) { question(titleSlug: $titleSlug) { content stats codeDefinition sampleTestCase metaData } }"#; const QUESTION_QUERY_OPERATION: &str = "questionData"; pub async fn get_problem(frontend_question_id: u32) -> Option { let problems = get_problems().await.unwrap(); for problem in problems.stat_status_pairs.iter() { match problem.stat.frontend_question_id.parse::() { Ok(id) => { if id == frontend_question_id { if problem.paid_only { return None; } let client = reqwest::Client::new(); let resp: RawProblem = client .post(GRAPHQL_URL) .json(&Query::question_query( problem.stat.question_title_slug.as_ref().unwrap(), )) .send() .await .unwrap() .json() .await .unwrap(); return Some(Problem { title: problem.stat.question_title.clone().unwrap(), title_slug: problem.stat.question_title_slug.clone().unwrap(), code_definition: serde_json::from_str(&resp.data.question.code_definition).unwrap(), content: resp.data.question.content, sample_test_case: resp.data.question.sample_test_case, difficulty: problem.difficulty.to_string(), question_id: id, return_type: { let v: Value = serde_json::from_str(&resp.data.question.meta_data).unwrap(); v["return"]["type"].to_string().replace("\"", "") }, }); } } _ => {} } } None } pub async fn get_problem_async(problem_stat: StatWithStatus) -> Option { if problem_stat.paid_only { println!( "Problem {} is paid-only", &problem_stat.stat.frontend_question_id ); return None; } let resp = surf::post(GRAPHQL_URL).body_json(&Query::question_query( problem_stat.stat.question_title_slug.as_ref().unwrap(), )); if resp.is_err() { println!( "Problem {} not initialized due to some error", &problem_stat.stat.frontend_question_id ); return None; } let resp = resp.unwrap().recv_json().await; if resp.is_err() { println!( "Problem {} not initialized due to some error", &problem_stat.stat.frontend_question_id ); return None; } let resp: RawProblem = resp.unwrap(); match problem_stat.stat.frontend_question_id.parse::() { Ok(id) => { return Some(Problem { title: problem_stat.stat.question_title.clone().unwrap(), title_slug: problem_stat.stat.question_title_slug.clone().unwrap(), code_definition: serde_json::from_str(&resp.data.question.code_definition).unwrap(), content: resp.data.question.content, sample_test_case: resp.data.question.sample_test_case, difficulty: problem_stat.difficulty.to_string(), question_id: id, return_type: { let v: Value = serde_json::from_str(&resp.data.question.meta_data).unwrap(); v["return"]["type"].to_string().replace("\"", "") }, }); } _ => { None } } } pub async fn get_problems() -> Option { reqwest::get(PROBLEMS_URL).await.unwrap().json().await.unwrap() } #[derive(Serialize, Deserialize)] pub struct Problem { pub title: String, pub title_slug: String, pub content: String, #[serde(rename = "codeDefinition")] pub code_definition: Vec, #[serde(rename = "sampleTestCase")] pub sample_test_case: String, pub difficulty: String, pub question_id: u32, pub return_type: String, } #[derive(Serialize, Deserialize)] pub struct CodeDefinition { pub value: String, pub text: String, #[serde(rename = "defaultCode")] pub default_code: String, } #[derive(Debug, Serialize, Deserialize)] struct Query { #[serde(rename = "operationName")] operation_name: String, variables: serde_json::Value, query: String, } impl Query { fn question_query(title_slug: &str) -> Query { Query { operation_name: QUESTION_QUERY_OPERATION.to_owned(), variables: json!({ "titleSlug": title_slug }), query: QUESTION_QUERY_STRING.to_owned(), } } } #[derive(Debug, Serialize, Deserialize)] struct RawProblem { data: Data, } #[derive(Debug, Serialize, Deserialize)] struct Data { question: Question, } #[derive(Debug, Serialize, Deserialize)] struct Question { content: String, stats: String, #[serde(rename = "codeDefinition")] code_definition: String, #[serde(rename = "sampleTestCase")] sample_test_case: String, #[serde(rename = "metaData")] meta_data: String, } #[derive(Debug, Serialize, Deserialize)] pub struct Problems { user_name: String, num_solved: u32, num_total: u32, ac_easy: u32, ac_medium: u32, ac_hard: u32, pub stat_status_pairs: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct StatWithStatus { pub stat: Stat, difficulty: Difficulty, paid_only: bool, is_favor: bool, frequency: u32, progress: u32, } #[derive(Debug, Serialize, Deserialize)] pub struct Stat { question_id: u32, #[serde(rename = "question__article__slug")] question_article_slug: Option, #[serde(rename = "question__title")] question_title: Option, #[serde(rename = "question__title_slug")] question_title_slug: Option, #[serde(rename = "question__hide")] question_hide: bool, total_acs: u32, total_submitted: u32, pub frontend_question_id: String, is_new_question: bool, } #[derive(Debug, Serialize, Deserialize)] struct Difficulty { level: u32, } impl Display for Difficulty { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self.level { 1 => f.write_str("Easy"), 2 => f.write_str("Medium"), 3 => f.write_str("Hard"), _ => f.write_str("Unknown"), } } }