1use self::req::{Json, Mode, Req};
2use crate::{
3 Result,
4 config::{self, Config},
5};
6use reqwest::{
7 Client, ClientBuilder, Response,
8 header::{HeaderMap, HeaderName, HeaderValue},
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 [
90 "query getTopicTag($slug: String!) {",
91 " topicTag(slug: $slug) {",
92 " questions {",
93 " questionId",
94 " }",
95 " }",
96 "}",
97 ]
98 .join("\n"),
99 );
100
101 Req {
102 default_headers: self.default_headers,
103 refer: Some(self.conf.sys.urls.tag(slug)),
104 info: false,
105 json: Some(json),
106 mode: Mode::Post,
107 name: "get_question_ids_by_tag",
108 url: (*url).to_string(),
109 }
110 .send(&self.client)
111 .await
112 }
113
114 pub async fn get_user_info(self) -> Result<Response> {
115 trace!("Requesting user info...");
116 let url = &self.conf.sys.urls.graphql;
117 let mut json: Json = HashMap::new();
118 json.insert("operationName", "a".to_string());
119 json.insert(
120 "query",
121 "query a {
122 user {
123 username
124 isCurrentUserPremium
125 }
126 }"
127 .to_owned(),
128 );
129
130 Req {
131 default_headers: self.default_headers,
132 refer: None,
133 info: false,
134 json: Some(json),
135 mode: Mode::Post,
136 name: "get_user_info",
137 url: (*url).to_string(),
138 }
139 .send(&self.client)
140 .await
141 }
142
143 pub async fn get_question_daily(self) -> Result<Response> {
145 trace!("Requesting daily problem...");
146 let url = &self.conf.sys.urls.graphql;
147 let mut json: Json = HashMap::new();
148
149 match self.conf.cookies.site {
150 config::LeetcodeSite::LeetcodeCom => {
151 json.insert("operationName", "daily".to_string());
152 json.insert(
153 "query",
154 [
155 "query daily {",
156 " activeDailyCodingChallengeQuestion {",
157 " question {",
158 " questionFrontendId",
159 " }",
160 " }",
161 "}",
162 ]
163 .join("\n"),
164 );
165 }
166 config::LeetcodeSite::LeetcodeCn => {
167 json.insert("operationName", "questionOfToday".to_string());
168 json.insert(
169 "query",
170 [
171 "query questionOfToday {",
172 " todayRecord {",
173 " question {",
174 " questionFrontendId",
175 " }",
176 " }",
177 "}",
178 ]
179 .join("\n"),
180 );
181 }
182 }
183
184 Req {
185 default_headers: self.default_headers,
186 refer: None,
187 info: false,
188 json: Some(json),
189 mode: Mode::Post,
190 name: "get_question_daily",
191 url: (*url).to_string(),
192 }
193 .send(&self.client)
194 .await
195 }
196
197 pub async fn get_question_detail(self, slug: &str) -> Result<Response> {
199 trace!("Requesting {} detail...", &slug);
200 let refer = self.conf.sys.urls.problem(slug);
201 let mut json: Json = HashMap::new();
202 json.insert(
203 "query",
204 [
205 "query getQuestionDetail($titleSlug: String!) {",
206 " question(titleSlug: $titleSlug) {",
207 " content",
208 " stats",
209 " codeDefinition",
210 " sampleTestCase",
211 " exampleTestcases",
212 " enableRunCode",
213 " metaData",
214 " translatedContent",
215 " }",
216 "}",
217 ]
218 .join("\n"),
219 );
220
221 json.insert(
222 "variables",
223 r#"{"titleSlug": "$titleSlug"}"#.replace("$titleSlug", slug),
224 );
225
226 json.insert("operationName", "getQuestionDetail".to_string());
227
228 Req {
229 default_headers: self.default_headers,
230 refer: Some(refer),
231 info: false,
232 json: Some(json),
233 mode: Mode::Post,
234 name: "get_problem_detail",
235 url: self.conf.sys.urls.graphql,
236 }
237 .send(&self.client)
238 .await
239 }
240
241 pub async fn run_code(self, j: Json, url: String, refer: String) -> Result<Response> {
243 info!("Sending code to judge...");
244 Req {
245 default_headers: self.default_headers,
246 refer: Some(refer),
247 info: false,
248 json: Some(j),
249 mode: Mode::Post,
250 name: "run_code",
251 url,
252 }
253 .send(&self.client)
254 .await
255 }
256
257 pub async fn verify_result(self, id: String) -> Result<Response> {
259 trace!("Verifying result...");
260 let url = self.conf.sys.urls.verify(&id);
261
262 Req {
263 default_headers: self.default_headers,
264 refer: None,
265 info: false,
266 json: None,
267 mode: Mode::Get,
268 name: "verify_result",
269 url,
270 }
271 .send(&self.client)
272 .await
273 }
274}
275
276mod req {
278 use super::LeetCode;
279 use crate::err::Error;
280 use reqwest::{Client, Response, header::HeaderMap};
281 use std::collections::HashMap;
282
283 pub type Json = HashMap<&'static str, String>;
285
286 pub enum Mode {
288 Get,
289 Post,
290 }
291
292 pub struct Req {
294 pub default_headers: HeaderMap,
295 pub refer: Option<String>,
296 pub json: Option<Json>,
297 pub info: bool,
298 pub mode: Mode,
299 pub name: &'static str,
300 pub url: String,
301 }
302
303 impl Req {
304 pub async fn send(self, client: &Client) -> Result<Response, Error> {
305 trace!("Running leetcode::{}...", &self.name);
306 if self.info {
307 info!("{}", &self.name);
308 }
309 let url = self.url.to_owned();
310 let headers = LeetCode::headers(
311 self.default_headers,
312 vec![("Referer", &self.refer.unwrap_or(url))],
313 )?;
314
315 let req = match self.mode {
316 Mode::Get => client.get(&self.url),
317 Mode::Post => client.post(&self.url).json(&self.json),
318 };
319
320 Ok(req.headers(headers).send().await?)
321 }
322 }
323}