1use self::req::{Json, Mode, Req};
2use crate::{
3 config::{self, Config},
4 Result,
5};
6use reqwest::{
7 header::{HeaderMap, HeaderName, HeaderValue},
8 Client, ClientBuilder, Response,
9};
10use std::{collections::HashMap, str::FromStr, time::Duration};
11
12#[derive(Clone)]
14pub struct LeetCode {
15 pub conf: Config,
16 client: Client,
17 default_headers: HeaderMap,
18}
19
20impl LeetCode {
21 fn headers(mut headers: HeaderMap, ts: Vec<(&str, &str)>) -> Result<HeaderMap> {
23 for (k, v) in ts.into_iter() {
24 let name = HeaderName::from_str(k)?;
25 let value = HeaderValue::from_str(v)?;
26 headers.insert(name, value);
27 }
28
29 Ok(headers)
30 }
31
32 pub fn new() -> Result<LeetCode> {
34 let conf = config::Config::locate()?;
35 let (cookie, csrf) = if conf.cookies.csrf.is_empty() || conf.cookies.session.is_empty() {
36 let cookies = super::chrome::cookies()?;
37 (cookies.to_string(), cookies.csrf)
38 } else {
39 (conf.cookies.clone().to_string(), conf.cookies.clone().csrf)
40 };
41 let default_headers = LeetCode::headers(
42 HeaderMap::new(),
43 vec![
44 ("Cookie", &cookie),
45 ("x-csrftoken", &csrf),
46 ("x-requested-with", "XMLHttpRequest"),
47 ("Origin", &conf.sys.urls.base),
48 ],
49 )?;
50
51 let client = ClientBuilder::new()
52 .gzip(true)
53 .connect_timeout(Duration::from_secs(30))
54 .build()?;
55
56 Ok(LeetCode {
57 conf,
58 client,
59 default_headers,
60 })
61 }
62
63 pub async fn get_category_problems(self, category: &str) -> Result<Response> {
65 trace!("Requesting {} problems...", &category);
66 let url = &self.conf.sys.urls.problems(category);
67
68 Req {
69 default_headers: self.default_headers,
70 refer: None,
71 info: false,
72 json: None,
73 mode: Mode::Get,
74 name: "get_category_problems",
75 url: url.to_string(),
76 }
77 .send(&self.client)
78 .await
79 }
80
81 pub async fn get_question_ids_by_tag(self, slug: &str) -> Result<Response> {
82 trace!("Requesting {} ref problems...", &slug);
83 let url = &self.conf.sys.urls.graphql;
84 let mut json: Json = HashMap::new();
85 json.insert("operationName", "getTopicTag".to_string());
86 json.insert("variables", r#"{"slug": "$slug"}"#.replace("$slug", slug));
87 json.insert(
88 "query",
89 ["query getTopicTag($slug: String!) {",
90 " topicTag(slug: $slug) {",
91 " questions {",
92 " questionId",
93 " }",
94 " }",
95 "}"]
96 .join("\n"),
97 );
98
99 Req {
100 default_headers: self.default_headers,
101 refer: Some(self.conf.sys.urls.tag(slug)),
102 info: false,
103 json: Some(json),
104 mode: Mode::Post,
105 name: "get_question_ids_by_tag",
106 url: (*url).to_string(),
107 }
108 .send(&self.client)
109 .await
110 }
111
112 pub async fn get_user_info(self) -> Result<Response> {
113 trace!("Requesting user info...");
114 let url = &self.conf.sys.urls.graphql;
115 let mut json: Json = HashMap::new();
116 json.insert("operationName", "a".to_string());
117 json.insert(
118 "query",
119 "query a {
120 user {
121 username
122 isCurrentUserPremium
123 }
124 }"
125 .to_owned(),
126 );
127
128 Req {
129 default_headers: self.default_headers,
130 refer: None,
131 info: false,
132 json: Some(json),
133 mode: Mode::Post,
134 name: "get_user_info",
135 url: (*url).to_string(),
136 }
137 .send(&self.client)
138 .await
139 }
140
141 pub async fn get_question_daily(self) -> Result<Response> {
143 trace!("Requesting daily problem...");
144 let url = &self.conf.sys.urls.graphql;
145 let mut json: Json = HashMap::new();
146
147 match self.conf.cookies.site {
148 config::LeetcodeSite::LeetcodeCom => {
149 json.insert("operationName", "daily".to_string());
150 json.insert(
151 "query",
152 ["query daily {",
153 " activeDailyCodingChallengeQuestion {",
154 " question {",
155 " questionFrontendId",
156 " }",
157 " }",
158 "}"]
159 .join("\n"),
160 );
161 }
162 config::LeetcodeSite::LeetcodeCn => {
163 json.insert("operationName", "questionOfToday".to_string());
164 json.insert(
165 "query",
166 ["query questionOfToday {",
167 " todayRecord {",
168 " question {",
169 " questionFrontendId",
170 " }",
171 " }",
172 "}"]
173 .join("\n"),
174 );
175 }
176 }
177
178 Req {
179 default_headers: self.default_headers,
180 refer: None,
181 info: false,
182 json: Some(json),
183 mode: Mode::Post,
184 name: "get_question_daily",
185 url: (*url).to_string(),
186 }
187 .send(&self.client)
188 .await
189 }
190
191 pub async fn get_question_detail(self, slug: &str) -> Result<Response> {
193 trace!("Requesting {} detail...", &slug);
194 let refer = self.conf.sys.urls.problem(slug);
195 let mut json: Json = HashMap::new();
196 json.insert(
197 "query",
198 ["query getQuestionDetail($titleSlug: String!) {",
199 " question(titleSlug: $titleSlug) {",
200 " content",
201 " stats",
202 " codeDefinition",
203 " sampleTestCase",
204 " exampleTestcases",
205 " enableRunCode",
206 " metaData",
207 " translatedContent",
208 " }",
209 "}"]
210 .join("\n"),
211 );
212
213 json.insert(
214 "variables",
215 r#"{"titleSlug": "$titleSlug"}"#.replace("$titleSlug", slug),
216 );
217
218 json.insert("operationName", "getQuestionDetail".to_string());
219
220 Req {
221 default_headers: self.default_headers,
222 refer: Some(refer),
223 info: false,
224 json: Some(json),
225 mode: Mode::Post,
226 name: "get_problem_detail",
227 url: self.conf.sys.urls.graphql,
228 }
229 .send(&self.client)
230 .await
231 }
232
233 pub async fn run_code(self, j: Json, url: String, refer: String) -> Result<Response> {
235 info!("Sending code to judge...");
236 Req {
237 default_headers: self.default_headers,
238 refer: Some(refer),
239 info: false,
240 json: Some(j),
241 mode: Mode::Post,
242 name: "run_code",
243 url,
244 }
245 .send(&self.client)
246 .await
247 }
248
249 pub async fn verify_result(self, id: String) -> Result<Response> {
251 trace!("Verifying result...");
252 let url = self.conf.sys.urls.verify(&id);
253
254 Req {
255 default_headers: self.default_headers,
256 refer: None,
257 info: false,
258 json: None,
259 mode: Mode::Get,
260 name: "verify_result",
261 url,
262 }
263 .send(&self.client)
264 .await
265 }
266}
267
268mod req {
270 use super::LeetCode;
271 use crate::err::Error;
272 use reqwest::{header::HeaderMap, Client, Response};
273 use std::collections::HashMap;
274
275 pub type Json = HashMap<&'static str, String>;
277
278 pub enum Mode {
280 Get,
281 Post,
282 }
283
284 pub struct Req {
286 pub default_headers: HeaderMap,
287 pub refer: Option<String>,
288 pub json: Option<Json>,
289 pub info: bool,
290 pub mode: Mode,
291 pub name: &'static str,
292 pub url: String,
293 }
294
295 impl Req {
296 pub async fn send(self, client: &Client) -> Result<Response, Error> {
297 trace!("Running leetcode::{}...", &self.name);
298 if self.info {
299 info!("{}", &self.name);
300 }
301 let url = self.url.to_owned();
302 let headers = LeetCode::headers(
303 self.default_headers,
304 vec![("Referer", &self.refer.unwrap_or(url))],
305 )?;
306
307 let req = match self.mode {
308 Mode::Get => client.get(&self.url),
309 Mode::Post => client.post(&self.url).json(&self.json),
310 };
311
312 Ok(req.headers(headers).send().await?)
313 }
314 }
315}