codeforces_api/obj/requests.rs
1//! Contains the structs etc. required to interface with the Codeforces API
2//! and the testcases scraper.
3
4use rand::{self, Rng};
5use sha2::{Digest, Sha512};
6use std::time::SystemTime;
7
8#[cfg(feature = "use_testcase_fetcher")]
9use lazy_static::lazy_static;
10#[cfg(feature = "use_testcase_fetcher")]
11use regex::Regex;
12#[cfg(feature = "use_testcase_fetcher")]
13use select::document::Document;
14#[cfg(feature = "use_testcase_fetcher")]
15use select::predicate::{Class, Descendant, Name};
16
17use super::error::*;
18use super::responses;
19
20const API_STUB: &str = "https://codeforces.com/api/";
21
22/// Wrapper enum for all API methods of form `blogEntry.<method>`.
23///
24/// More details for the blogEntry command can be found
25/// [here](https://codeforces.com/apiHelp/methods#blogEntry.comments).
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum CFBlogEntryCommand {
28 /// Struct for sending `blogEntry.comments` requests to the Codeforces API.
29 ///
30 /// Returns a list of comments on a specified blog entry.
31 ///
32 /// If correctly parsed, the response object will be of type
33 /// [`responses::CFResult::CFCommentVec`].
34 ///
35 /// More details for the `blogEntry.comments` command can be found
36 /// [here](https://codeforces.com/apiHelp/methods#blogEntry.comments).
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// # use codeforces_api::requests::*;
42 /// # use codeforces_api::responses::*;
43 /// # let api_key = codeforces_api::TEST_API_KEY;
44 /// # let api_secret = codeforces_api::TEST_API_SECRET;
45 /// let x = CFBlogEntryCommand::Comments {
46 /// blog_entry_id: 82347,
47 /// };
48 ///
49 /// match x.get(api_key, api_secret) {
50 /// Ok(CFResult::CFCommentVec(v)) => {
51 /// // your code here
52 /// },
53 /// _ => {
54 /// panic!("API request failed");
55 /// }
56 /// }
57 /// ```
58 Comments {
59 /// blogEntryId of a blog (can be seen in the url of a blog, eg.
60 /// [`/blog/entry/82347`](https://codeforces.com/blog/entry/82347)).
61 blog_entry_id: i64,
62 },
63 /// Struct for sending `blogEntry.view` requests to the Codeforces API.
64 ///
65 /// Returns a specified blog entry.
66 ///
67 /// If correctly parsed, the response object will be of type
68 /// [`responses::CFResult::CFBlogEntry`].
69 ///
70 /// More details for the `blogEntry.view` command can be found
71 /// [here](https://codeforces.com/apiHelp/methods#blogEntry.view).
72 ///
73 /// # Examples
74 ///
75 /// ```
76 /// # use codeforces_api::requests::*;
77 /// # use codeforces_api::responses::*;
78 /// # let api_key = codeforces_api::TEST_API_KEY;
79 /// # let api_secret = codeforces_api::TEST_API_SECRET;
80 /// let x = CFBlogEntryCommand::View {
81 /// blog_entry_id: 82347,
82 /// };
83 ///
84 /// match x.get(api_key, api_secret) {
85 /// Ok(CFResult::CFBlogEntry(e)) => {
86 /// // your code here
87 /// },
88 /// _ => {
89 /// panic!("API request failed");
90 /// }
91 /// }
92 /// ```
93 View {
94 /// blogEntryId of a blog (can be seen in the url of a blog, eg.
95 /// [`/blog/entry/82347`](https://codeforces.com/blog/entry/82347)).
96 blog_entry_id: i64,
97 },
98}
99
100/// Wrapper enum for all API methods of form `contest.<method>`.
101///
102/// More details for the contest command can be found
103/// [here](https://codeforces.com/apiHelp/methods#contest.hacks).
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum CFContestCommand {
106 /// Struct for sending `contest.hacks` requests to the Codeforces API.
107 ///
108 /// Returns list of hacks in the specified contest. Full information about
109 /// hacks is available only after some time after the contest end. During
110 /// the contest, a user can see only their own hacks.
111 ///
112 /// If correctly parsed, the response object will be of type
113 /// [`responses::CFResult::CFHackVec`].
114 ///
115 /// More details for the `contest.hacks` command can be found
116 /// [here](https://codeforces.com/apiHelp/methods#contest.hacks).
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// # use codeforces_api::requests::*;
122 /// # use codeforces_api::responses::*;
123 /// # let api_key = codeforces_api::TEST_API_KEY;
124 /// # let api_secret = codeforces_api::TEST_API_SECRET;
125 /// let x = CFContestCommand::Hacks {
126 /// contest_id: 1485,
127 /// };
128 ///
129 /// match x.get(api_key, api_secret) {
130 /// Ok(CFResult::CFHackVec(v)) => {
131 /// // your code here
132 /// },
133 /// _ => {
134 /// panic!("API request failed");
135 /// }
136 /// }
137 /// ```
138 Hacks {
139 /// contestId of a contest (can be seen in the url of a contest, eg.
140 /// [`/contest/1485`](https://codeforces.com/contest/1485)).
141 contest_id: i64,
142 },
143 /// Struct for sending `contest.list` requests to the Codeforces API.
144 ///
145 /// Returns information about all available contests.
146 ///
147 /// If correctly parsed, the response object will be of type
148 /// [`responses::CFResult::CFContestVec`].
149 ///
150 /// More details for the `contest.list` command can be found
151 /// [here](https://codeforces.com/apiHelp/methods#contest.list).
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// # use codeforces_api::requests::*;
157 /// # use codeforces_api::responses::*;
158 /// # let api_key = codeforces_api::TEST_API_KEY;
159 /// # let api_secret = codeforces_api::TEST_API_SECRET;
160 /// let x = CFContestCommand::List {
161 /// gym: Some(false),
162 /// };
163 ///
164 /// match x.get(api_key, api_secret) {
165 /// Ok(CFResult::CFContestVec(v)) => {
166 /// // your code here
167 /// },
168 /// _ => {
169 /// panic!("API request failed");
170 /// }
171 /// }
172 /// ```
173 List {
174 /// If `Some(true)`, then gym contests are returned. Otherwise, regular
175 /// contests are returned.
176 gym: Option<bool>,
177 },
178 /// Struct for sending `contest.ratingChanges` requests to the Codeforces
179 /// API.
180 ///
181 /// Returns rating changes after a specified contest.
182 ///
183 /// If correctly parsed, the response object will be of type
184 /// [`responses::CFResult::CFRatingChangeVec`].
185 ///
186 /// More details for the `contest.ratingChanges` command can be found
187 /// [here](https://codeforces.com/apiHelp/methods#contest.ratingChanges).
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// # use codeforces_api::requests::*;
193 /// # use codeforces_api::responses::*;
194 /// # let api_key = codeforces_api::TEST_API_KEY;
195 /// # let api_secret = codeforces_api::TEST_API_SECRET;
196 /// let x = CFContestCommand::RatingChanges {
197 /// contest_id: 1485,
198 /// };
199 ///
200 /// match x.get(api_key, api_secret) {
201 /// Ok(CFResult::CFRatingChangeVec(v)) => {
202 /// // your code here
203 /// },
204 /// _ => {
205 /// panic!("API request failed");
206 /// }
207 /// }
208 /// ```
209 RatingChanges {
210 /// contestId of a contest (can be seen in the url of a contest, eg.
211 /// [`/contest/1485`](https://codeforces.com/contest/1485)).
212 contest_id: i64,
213 },
214 /// Struct for sending `contest.standings` requests to the Codeforces API.
215 ///
216 /// Returns a description of a specified contest as well as the requested
217 /// part of the standings.
218 ///
219 /// If correctly parsed, the response object will be of type
220 /// [`responses::CFResult::CFContestStandings`].
221 ///
222 /// More details for the `contest.standings` command can be found
223 /// [here](https://codeforces.com/apiHelp/methods#contest.standings).
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// # use codeforces_api::requests::*;
229 /// # use codeforces_api::responses::*;
230 /// # let api_key = codeforces_api::TEST_API_KEY;
231 /// # let api_secret = codeforces_api::TEST_API_SECRET;
232 /// let x = CFContestCommand::Standings {
233 /// contest_id: 1485,
234 /// from: Some(1),
235 /// count: Some(3),
236 /// handles: Some(vec!["thud".to_string()]),
237 /// room: None,
238 /// show_unofficial: Some(false),
239 /// };
240 ///
241 /// match x.get(api_key, api_secret) {
242 /// Ok(CFResult::CFContestStandings(s)) => {
243 /// // your code here
244 /// },
245 /// _ => {
246 /// panic!("API request failed");
247 /// }
248 /// }
249 /// ```
250 Standings {
251 /// contestId of a contest (can be seen in the url of a contest, eg.
252 /// [`/contest/1485`](https://codeforces.com/contest/1485)).
253 contest_id: i64,
254 /// 1-based index of the standings row to start the ranklist (most
255 /// recent first).
256 from: Option<i64>,
257 /// Number of standing rows to return.
258 count: Option<i64>,
259 /// Vec of handles. No more than 10000 handles is allowed by Codeforces.
260 handles: Option<Vec<String>>,
261 /// If specified, then only participants from this room will be shown
262 /// in the result. If not, all the participants will be shown.
263 room: Option<i64>,
264 /// If true, then all participants (virtual, out of competition) are
265 /// shown. Otherwise, only official contestants are shown.
266 show_unofficial: Option<bool>,
267 },
268 /// Struct for sending `contest.status` requests to the Codeforces API.
269 ///
270 /// Returns submissions for specified contest. Optionally can return
271 /// submissions of specified user.
272 ///
273 /// If correctly parsed, the response object will be of type
274 /// [`responses::CFResult::CFSubmissionVec`].
275 ///
276 /// More details for the `contest.status` command can be found
277 /// [here](https://codeforces.com/apiHelp/methods#contest.status).
278 ///
279 /// # Examples
280 ///
281 /// ```
282 /// # use codeforces_api::requests::*;
283 /// # use codeforces_api::responses::*;
284 /// # let api_key = codeforces_api::TEST_API_KEY;
285 /// # let api_secret = codeforces_api::TEST_API_SECRET;
286 /// let x = CFContestCommand::Status {
287 /// contest_id: 1485,
288 /// handle: None,
289 /// from: Some(1),
290 /// count: Some(3),
291 /// };
292 ///
293 /// match x.get(api_key, api_secret) {
294 /// Ok(CFResult::CFSubmissionVec(v)) => {
295 /// // your code here
296 /// },
297 /// _ => {
298 /// panic!("API request failed");
299 /// }
300 /// }
301 /// ```
302 Status {
303 /// contestId of a contest (can be seen in the url of a contest, eg.
304 /// [`/contest/1485`](https://codeforces.com/contest/1485)).
305 contest_id: i64,
306 /// If specified, then only this user's submissions are returned.
307 handle: Option<String>,
308 /// 1-based index of the standings row to start the ranklist (most
309 /// recent first).
310 from: Option<i64>,
311 /// Number of submissions to return.
312 count: Option<i64>,
313 },
314}
315
316/// Wrapper enum for all API methods of form `problemset.<method>`.
317///
318/// More details for the problemset command can be found
319/// [here](https://codeforces.com/apiHelp/methods#problemset.problems).
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub enum CFProblemsetCommand {
322 /// Struct for sending `problemset.problems` requests to the Codeforces API.
323 ///
324 /// Returns all problems from problemset. Problems can be filtered by tags.
325 ///
326 /// If correctly parsed, the response object will be of type
327 /// [`responses::CFResult::CFProblemset`].
328 ///
329 /// More details for the `problemset.problems` command can be found
330 /// [here](https://codeforces.com/apiHelp/methods#problemset.problems).
331 ///
332 /// # Examples
333 ///
334 /// ```
335 /// # use codeforces_api::requests::*;
336 /// # use codeforces_api::responses::*;
337 /// # let api_key = codeforces_api::TEST_API_KEY;
338 /// # let api_secret = codeforces_api::TEST_API_SECRET;
339 /// let x = CFProblemsetCommand::Problems {
340 /// tags: Some(vec!["dp".to_string(), "greedy".to_string()]),
341 /// problemset_name: None,
342 /// };
343 ///
344 /// match x.get(api_key, api_secret) {
345 /// Ok(CFResult::CFProblemset(p)) => {
346 /// // your code here
347 /// },
348 /// _ => {
349 /// panic!("API request failed");
350 /// }
351 /// }
352 /// ```
353 Problems {
354 /// Optional Vec of tags to search for (eg. "dp").
355 tags: Option<Vec<String>>,
356 /// Optional custom problemset's short name, like `acmsguru`.
357 problemset_name: Option<String>,
358 },
359 /// Struct for sending `problemset.recentStatus` requests to the Codeforces
360 /// API.
361 ///
362 /// Returns recent submissions.
363 ///
364 /// If correctly parsed, the response object will be of type
365 /// [`responses::CFResult::CFSubmissionVec`].
366 ///
367 /// More details for the `problemset.recentStatus` command can be found
368 /// [here](https://codeforces.com/apiHelp/methods#problemset.recentStatus).
369 ///
370 /// # Examples
371 ///
372 /// ```
373 /// # use codeforces_api::requests::*;
374 /// # use codeforces_api::responses::*;
375 /// # let api_key = codeforces_api::TEST_API_KEY;
376 /// # let api_secret = codeforces_api::TEST_API_SECRET;
377 /// let x = CFProblemsetCommand::RecentStatus {
378 /// count: 10,
379 /// problemset_name: None,
380 /// };
381 ///
382 /// match x.get(api_key, api_secret) {
383 /// Ok(CFResult::CFSubmissionVec(v)) => {
384 /// // your code here
385 /// },
386 /// _ => {
387 /// panic!("API request failed");
388 /// }
389 /// }
390 /// ```
391 RecentStatus {
392 /// Number of submissions to return. Can be up to 1000.
393 count: i64,
394 /// Optional custom problemset's short name, like `acmsguru`.
395 problemset_name: Option<String>,
396 },
397}
398
399/// Wrapper enum for all API methods of form `user.<method>`.
400///
401/// More details for the user command can be found
402/// [here](https://codeforces.com/apiHelp/methods#user.blogEntries).
403#[derive(Debug, Clone, PartialEq, Eq)]
404pub enum CFUserCommand {
405 /// Struct for sending `user.blogEntries` requests to the Codeforces
406 /// API.
407 ///
408 /// Returns a list with all of a specified user's blog entries.
409 ///
410 /// If correctly parsed, the response object will be of type
411 /// [`responses::CFResult::CFBlogEntryVec`].
412 ///
413 /// More details for the `user.blogEntries` command can be found
414 /// [here](https://codeforces.com/apiHelp/methods#user.blogEntries).
415 ///
416 /// # Examples
417 ///
418 /// ```
419 /// # use codeforces_api::requests::*;
420 /// # use codeforces_api::responses::*;
421 /// # let api_key = codeforces_api::TEST_API_KEY;
422 /// # let api_secret = codeforces_api::TEST_API_SECRET;
423 /// let x = CFUserCommand::BlogEntries {
424 /// handle: "thud".to_string(),
425 /// };
426 ///
427 /// match x.get(api_key, api_secret) {
428 /// Ok(CFResult::CFBlogEntryVec(v)) => {
429 /// // your code here
430 /// },
431 /// _ => {
432 /// panic!("API request failed");
433 /// }
434 /// }
435 /// ```
436 BlogEntries {
437 /// Codeforces handle of the user for which to fetch blog entries.
438 handle: String,
439 },
440 /// Struct for sending `user.friends` requests to the Codeforces API.
441 ///
442 /// Returns authorized user's friends (ie. the friends of the user who owns
443 /// the API keys in use).
444 ///
445 /// If correctly parsed, the response object will be of type
446 /// [`responses::CFResult::CFFriends`].
447 ///
448 /// More details for the `user.friends` command can be found
449 /// [here](https://codeforces.com/apiHelp/methods#user.friends).
450 ///
451 /// # Examples
452 ///
453 /// ```
454 /// # use codeforces_api::requests::*;
455 /// # use codeforces_api::responses::*;
456 /// # let api_key = codeforces_api::TEST_API_KEY;
457 /// # let api_secret = codeforces_api::TEST_API_SECRET;
458 /// let x = CFUserCommand::Friends {
459 /// only_online: None,
460 /// };
461 ///
462 /// match x.get(api_key, api_secret) {
463 /// Ok(CFResult::CFFriends(v)) => {
464 /// // your code here
465 /// },
466 /// _ => {
467 /// panic!("API request failed");
468 /// }
469 /// }
470 /// ```
471 Friends {
472 /// If `Some(true)`, then only online friends are returned. Otherwise
473 /// all friends are returned.
474 only_online: Option<bool>,
475 },
476 /// Struct for sending `user.info` requests to the Codeforces API.
477 ///
478 /// Returns information about one or several users.
479 ///
480 /// If correctly parsed, the response object will be of type
481 /// [`responses::CFResult::CFUserVec`].
482 ///
483 /// More details for the `user.info` command can be found
484 /// [here](https://codeforces.com/apiHelp/methods#user.info).
485 ///
486 /// # Examples
487 ///
488 /// ```
489 /// # use codeforces_api::requests::*;
490 /// # use codeforces_api::responses::*;
491 /// # let api_key = codeforces_api::TEST_API_KEY;
492 /// # let api_secret = codeforces_api::TEST_API_SECRET;
493 /// let x = CFUserCommand::Info {
494 /// handles: vec!["thud".to_string()],
495 /// };
496 ///
497 /// match x.get(api_key, api_secret) {
498 /// Ok(CFResult::CFUserVec(v)) => {
499 /// // your code here
500 /// },
501 /// _ => {
502 /// panic!("API request failed");
503 /// }
504 /// }
505 /// ```
506 Info {
507 /// Vec of handles for which to get info for. Codeforces will return an
508 /// error if this is empty.
509 handles: Vec<String>,
510 },
511 /// Struct for sending `user.ratedList` requests to the Codeforces API.
512 ///
513 /// Returns the list of users who have participated in at least one rated
514 /// contest.
515 ///
516 /// If correctly parsed, the response object will be of type
517 /// [`responses::CFResult::CFUserVec`].
518 ///
519 /// More details for the `user.ratedList` command can be found
520 /// [here](https://codeforces.com/apiHelp/methods#user.ratedList).
521 ///
522 /// # Examples
523 ///
524 /// ```
525 /// # use codeforces_api::requests::*;
526 /// # use codeforces_api::responses::*;
527 /// # let api_key = codeforces_api::TEST_API_KEY;
528 /// # let api_secret = codeforces_api::TEST_API_SECRET;
529 /// let x = CFUserCommand::RatedList {
530 /// active_only: Some(true),
531 /// };
532 ///
533 /// match x.get(api_key, api_secret) {
534 /// Ok(CFResult::CFUserVec(v)) => {
535 /// // your code here
536 /// },
537 /// _ => {
538 /// panic!("API request failed");
539 /// }
540 /// }
541 /// ```
542 RatedList {
543 /// If `Some(true)`, then only users who have participated in a rated
544 /// contest during the last month are returned.
545 active_only: Option<bool>,
546 },
547 /// Struct for sending `user.rating` requests to the Codeforces API.
548 ///
549 /// Returns the rating history of a specified user.
550 ///
551 /// If correctly parsed, the response object will be of type
552 /// [`responses::CFResult::CFRatingChangeVec`].
553 ///
554 /// More details for the `user.rating` command can be found
555 /// [here](https://codeforces.com/apiHelp/methods#user.rating).
556 ///
557 /// # Examples
558 ///
559 /// ```
560 /// # use codeforces_api::requests::*;
561 /// # use codeforces_api::responses::*;
562 /// # let api_key = codeforces_api::TEST_API_KEY;
563 /// # let api_secret = codeforces_api::TEST_API_SECRET;
564 /// let x = CFUserCommand::Rating {
565 /// handle: "thud".to_string(),
566 /// };
567 ///
568 /// match x.get(api_key, api_secret) {
569 /// Ok(CFResult::CFRatingChangeVec(v)) => {
570 /// // your code here
571 /// },
572 /// _ => {
573 /// panic!("API request failed");
574 /// }
575 /// }
576 /// ```
577 Rating {
578 /// Codeforces handle of user for which to fetch rating changes for.
579 handle: String,
580 },
581 /// Struct for sending `user.status` requests to the Codeforces API.
582 ///
583 /// Returns the submissions of a specified user.
584 ///
585 /// If correctly parsed, the response object will be of type
586 /// [`responses::CFResult::CFSubmissionVec`].
587 ///
588 /// More details for the `user.status` command can be found
589 /// [here](https://codeforces.com/apiHelp/methods#user.status).
590 ///
591 /// # Examples
592 ///
593 /// ```
594 /// # use codeforces_api::requests::*;
595 /// # use codeforces_api::responses::*;
596 /// # let api_key = codeforces_api::TEST_API_KEY;
597 /// # let api_secret = codeforces_api::TEST_API_SECRET;
598 /// let x = CFUserCommand::Status {
599 /// handle: "thud".to_string(),
600 /// from: Some(1),
601 /// count: Some(3),
602 /// };
603 ///
604 /// match x.get(api_key, api_secret) {
605 /// Ok(CFResult::CFSubmissionVec(v)) => {
606 /// // your code here
607 /// },
608 /// _ => {
609 /// panic!("API request failed");
610 /// }
611 /// }
612 /// ```
613 Status {
614 /// Codeforces handle of user for which to fetch submissions.
615 handle: String,
616 /// Optional 1-based index of the first submission to return (most recent first).
617 from: Option<i64>,
618 /// Optional number of submissions to return.
619 count: Option<i64>,
620 },
621}
622
623/// Struct for sending `recentActions` requests to the Codeforces API.
624///
625/// Returns recent actions.
626///
627/// If correctly parsed, the response object will be of type
628/// [`responses::CFResult::CFRecentActionVec`].
629///
630/// More details for the `recentActions` command can be found
631/// [here](https://codeforces.com/apiHelp/methods#recentActions).
632///
633/// # Examples
634///
635/// ```
636/// # use codeforces_api::requests::*;
637/// # use codeforces_api::responses::*;
638/// # let api_key = codeforces_api::TEST_API_KEY;
639/// # let api_secret = codeforces_api::TEST_API_SECRET;
640/// let x = CFRecentActionsCommand {
641/// max_count: 3,
642/// };
643///
644/// match x.get(api_key, api_secret) {
645/// Ok(CFResult::CFRecentActionVec(v)) => {
646/// // your code here
647/// },
648/// _ => {
649/// panic!("API request failed");
650/// }
651/// }
652/// ```
653#[derive(Debug, Clone, PartialEq, Eq)]
654pub struct CFRecentActionsCommand {
655 /// Number of recent actions to return. Can be up to 100.
656 pub max_count: i64,
657}
658
659/// Converts CFAPIRequestable object into a Codeforces API url. Currently, only
660/// authenticated interaction is implemented, though in the future, this could
661/// be extended to not require it (ie. no API keys required).
662fn as_codeforces_api_url<T: CFAPIRequestable + std::fmt::Debug>(
663 command: &T,
664 api_key: &str,
665 api_secret: &str,
666) -> String {
667 // generate random number to be used as nonce in url.
668 let mut rng = rand::thread_rng();
669 let rand: String = (0..6)
670 .map(|_| rng.gen_range::<u8, _>(0..=9).to_string())
671 .collect();
672 // get current UNIX time to be used in url.
673 let ctime = SystemTime::now()
674 .duration_since(SystemTime::UNIX_EPOCH)
675 .unwrap()
676 .as_secs();
677 // get command specific query params from method.
678 let mut params = command.query_params();
679 // add non-specific query params.
680 params.push(("apiKey", api_key.to_string()));
681 params.push(("time", ctime.to_string()));
682 // Codeforces requires that the query params be sorted in lexicographical
683 // order.
684 params.sort();
685 // construct url by concatenating query params to API_STUB.
686 let mut url = String::from(API_STUB);
687 url += command.method_name();
688 url += "?";
689 // construct secondary String which will be hashed for checksum.
690 let mut to_hash = String::new();
691 to_hash += &rand;
692 to_hash += "/";
693 to_hash += command.method_name();
694 to_hash += "?";
695 for (key, val) in params {
696 url += &key;
697 to_hash += &key;
698 url += "=";
699 to_hash += "=";
700 url += &val;
701 to_hash += &val;
702 url += "&";
703 to_hash += "&";
704 }
705 to_hash.pop();
706 to_hash += "#";
707 to_hash += &api_secret.to_string();
708 // hash to_hash then add to end of the url.
709 let mut hasher = Sha512::new();
710 hasher.update(&to_hash);
711 let api_sig = hasher.finalize();
712 url += "apiSig=";
713 url += &rand;
714 url += &hex::encode(&api_sig);
715 url
716}
717
718/// Takes any CFAPIRequestable object and sends it as an API request to the
719/// Codeforces servers. Made possible by `as_codeforces_url()` function.
720fn send_codeforces_api_req<T: CFAPIRequestable + std::fmt::Debug>(
721 req: &T,
722 api_key: &str,
723 api_secret: &str,
724) -> Result<responses::CFResult, Error> {
725 // convert request object into a url String.
726 let url = as_codeforces_api_url(req, api_key, api_secret);
727 match get_url(&url) {
728 // if fetch was successful, then parse the JSON into a `CFResponse`.
729 Ok(res) => match res.json::<responses::CFResponse>() {
730 // if parse was successful, then check Codeforces response code.
731 Ok(json) => match json.status {
732 // if response is `Ok`, then return `CFResult` object.
733 responses::CFResponseStatus::Ok => Ok(json.result.unwrap()),
734 // if response is `Failed`, then return `Error::CodeforcesApi`,
735 // with the returned comment as its String param.
736 responses::CFResponseStatus::Failed => {
737 Err(Error::CodeforcesApi(json.comment.unwrap()))
738 }
739 },
740 // if parse failed, then wrap reqwest parsing error with custom.
741 Err(e) => Err(Error::Parse(e)),
742 },
743 // if fetch failed, then wrap reqwest error with custom Http.
744 Err(e) => Err(Error::Http(e)),
745 }
746}
747
748/// Analogous to `send_codeforces_api_req()`, only don't bother parsing.
749/// Returns a JSON String or an `Error::Http`.
750fn send_codeforces_api_req_raw<T: CFAPIRequestable + std::fmt::Debug>(
751 req: &T,
752 api_key: &str,
753 api_secret: &str,
754) -> Result<String, Error> {
755 let url = as_codeforces_api_url(req, api_key, api_secret);
756 get_url_raw(&url)
757}
758
759/// Simple blocking request to url using [`reqwest::blocking::get`]. This is
760/// not very efficient for rapidly making lots of requests since the function
761/// creates and destroys a new [`reqwest::Client`] with every request.
762fn get_url(url: &str) -> Result<reqwest::blocking::Response, reqwest::Error> {
763 reqwest::blocking::get(url)
764}
765
766/// Analogous to `get_url()`, but immediately returns just the text content of
767/// the request.
768fn get_url_raw(url: &str) -> Result<String, Error> {
769 match get_url(url) {
770 Ok(res) => match res.text() {
771 Ok(text) => Ok(text),
772 Err(e) => Err(Error::Http(e)),
773 },
774 Err(e) => Err(Error::Http(e)),
775 }
776}
777
778/// Trait implemented by any type which can be sent as a request to the
779/// Codeforces API.
780///
781/// Exposes functions which allow a type to be converted into a URL and fetched
782/// from the server.
783pub trait CFAPIRequestable {
784 /// Method which returns a Vec of pairs (key, val) which will be mapped
785 /// onto URL query parameters. Used internally and not much use for most
786 /// people.
787 fn query_params(&self) -> Vec<(&'static str, String)>;
788 /// Method which returns a str slice of the method name (eg. "user.info").
789 /// Used internally and not much use for most
790 /// people.
791 fn method_name(&self) -> &'static str;
792 /// Fetch response from Codeforces servers.
793 ///
794 /// # Examples
795 ///
796 /// ```
797 /// # use codeforces_api::requests::*;
798 /// # use codeforces_api::responses::*;
799 /// # let api_key = codeforces_api::TEST_API_KEY;
800 /// # let api_secret = codeforces_api::TEST_API_SECRET;
801 /// let x = CFUserCommand::Status {
802 /// handle: "thud".to_string(),
803 /// from: Some(1),
804 /// count: Some(3),
805 /// };
806 ///
807 /// match x.get(api_key, api_secret) {
808 /// Ok(CFResult::CFSubmissionVec(v)) => {
809 /// // your code here
810 /// },
811 /// _ => {
812 /// panic!("API request failed");
813 /// }
814 /// }
815 /// ```
816 fn get(
817 &self,
818 api_key: &str,
819 api_secret: &str,
820 ) -> Result<responses::CFResult, Error>;
821 /// Fetch raw JSON response from Codeforces servers.
822 ///
823 /// # Examples
824 ///
825 /// ```
826 /// # use codeforces_api::requests::*;
827 /// # use codeforces_api::responses::*;
828 /// # let api_key = codeforces_api::TEST_API_KEY;
829 /// # let api_secret = codeforces_api::TEST_API_SECRET;
830 /// let x = CFUserCommand::Status {
831 /// handle: "thud".to_string(),
832 /// from: Some(1),
833 /// count: Some(3),
834 /// };
835 ///
836 /// match x.get_raw(api_key, api_secret) {
837 /// Ok(s) => {
838 /// assert!(s.starts_with("{\"status\":\"OK\""));
839 /// // your code here
840 /// },
841 /// _ => {
842 /// panic!("raw API request failed");
843 /// }
844 /// }
845 /// ```
846 fn get_raw(&self, api_key: &str, api_secret: &str)
847 -> Result<String, Error>;
848}
849
850impl CFAPIRequestable for CFBlogEntryCommand {
851 fn query_params(&self) -> Vec<(&'static str, String)> {
852 let mut res = vec![];
853 match self {
854 CFBlogEntryCommand::Comments { blog_entry_id } => {
855 res.push(("blogEntryId", blog_entry_id.to_string()));
856 }
857 CFBlogEntryCommand::View { blog_entry_id } => {
858 res.push(("blogEntryId", blog_entry_id.to_string()));
859 }
860 }
861 res
862 }
863
864 fn method_name(&self) -> &'static str {
865 match self {
866 CFBlogEntryCommand::Comments { blog_entry_id: _ } => {
867 "blogEntry.comments"
868 }
869 CFBlogEntryCommand::View { blog_entry_id: _ } => "blogEntry.view",
870 }
871 }
872
873 fn get(
874 &self,
875 api_key: &str,
876 api_secret: &str,
877 ) -> Result<responses::CFResult, Error> {
878 send_codeforces_api_req(self, api_key, api_secret)
879 }
880
881 fn get_raw(
882 &self,
883 api_key: &str,
884 api_secret: &str,
885 ) -> Result<String, Error> {
886 send_codeforces_api_req_raw(self, api_key, api_secret)
887 }
888}
889
890impl CFAPIRequestable for CFContestCommand {
891 fn query_params(&self) -> Vec<(&'static str, String)> {
892 let mut res = vec![];
893 match self {
894 CFContestCommand::Hacks { contest_id } => {
895 res.push(("contestId", contest_id.to_string()));
896 }
897 CFContestCommand::List { gym } => {
898 if let Some(b) = gym {
899 res.push((
900 "gym",
901 if *b {
902 "true".to_string()
903 } else {
904 "false".to_string()
905 },
906 ));
907 }
908 }
909 CFContestCommand::RatingChanges { contest_id } => {
910 res.push(("contestId", contest_id.to_string()));
911 }
912 CFContestCommand::Standings {
913 contest_id,
914 from,
915 count,
916 handles,
917 room,
918 show_unofficial,
919 } => {
920 res.push(("contestId", contest_id.to_string()));
921 if let Some(i) = from {
922 res.push(("from", i.to_string()));
923 }
924 if let Some(i) = count {
925 res.push(("count", i.to_string()));
926 }
927 if let Some(v) = handles {
928 res.push(("handles", v.join(";")));
929 }
930 if let Some(i) = room {
931 res.push(("room", i.to_string()));
932 }
933 if let Some(b) = show_unofficial {
934 res.push((
935 "showUnofficial",
936 if *b {
937 "true".to_string()
938 } else {
939 "false".to_string()
940 },
941 ));
942 }
943 }
944 CFContestCommand::Status {
945 contest_id,
946 handle,
947 from,
948 count,
949 } => {
950 res.push(("contestId", contest_id.to_string()));
951 if let Some(s) = handle {
952 res.push(("handle", s.to_string()));
953 }
954 if let Some(i) = from {
955 res.push(("from", i.to_string()));
956 }
957 if let Some(i) = count {
958 res.push(("count", i.to_string()));
959 }
960 }
961 }
962 res
963 }
964
965 fn method_name(&self) -> &'static str {
966 match self {
967 CFContestCommand::Hacks { .. } => "contest.hacks",
968 CFContestCommand::List { .. } => "contest.list",
969 CFContestCommand::RatingChanges { .. } => "contest.ratingChanges",
970 CFContestCommand::Standings { .. } => "contest.standings",
971 CFContestCommand::Status { .. } => "contest.status",
972 }
973 }
974
975 fn get(
976 &self,
977 api_key: &str,
978 api_secret: &str,
979 ) -> Result<responses::CFResult, Error> {
980 send_codeforces_api_req(self, api_key, api_secret)
981 }
982
983 fn get_raw(
984 &self,
985 api_key: &str,
986 api_secret: &str,
987 ) -> Result<String, Error> {
988 send_codeforces_api_req_raw(self, api_key, api_secret)
989 }
990}
991
992impl CFAPIRequestable for CFProblemsetCommand {
993 fn query_params(&self) -> Vec<(&'static str, String)> {
994 let mut res = vec![];
995 match self {
996 CFProblemsetCommand::Problems {
997 tags,
998 problemset_name,
999 } => {
1000 if let Some(v) = tags {
1001 res.push(("tags", v.join(";")));
1002 }
1003 if let Some(s) = problemset_name {
1004 res.push(("problemsetName", s.to_string()));
1005 }
1006 }
1007 CFProblemsetCommand::RecentStatus {
1008 count,
1009 problemset_name,
1010 } => {
1011 res.push(("count", count.to_string()));
1012 if let Some(s) = problemset_name {
1013 res.push(("problemsetName", s.to_string()));
1014 }
1015 }
1016 }
1017 res
1018 }
1019
1020 fn method_name(&self) -> &'static str {
1021 match self {
1022 CFProblemsetCommand::Problems { .. } => "problemset.problems",
1023 CFProblemsetCommand::RecentStatus { .. } => {
1024 "problemset.recentStatus"
1025 }
1026 }
1027 }
1028
1029 fn get(
1030 &self,
1031 api_key: &str,
1032 api_secret: &str,
1033 ) -> Result<responses::CFResult, Error> {
1034 send_codeforces_api_req(self, api_key, api_secret)
1035 }
1036
1037 fn get_raw(
1038 &self,
1039 api_key: &str,
1040 api_secret: &str,
1041 ) -> Result<String, Error> {
1042 send_codeforces_api_req_raw(self, api_key, api_secret)
1043 }
1044}
1045
1046impl CFAPIRequestable for CFRecentActionsCommand {
1047 fn query_params(&self) -> Vec<(&'static str, String)> {
1048 let mut res = vec![];
1049 res.push(("maxCount", self.max_count.to_string()));
1050 res
1051 }
1052
1053 fn method_name(&self) -> &'static str {
1054 "recentActions"
1055 }
1056
1057 fn get(
1058 &self,
1059 api_key: &str,
1060 api_secret: &str,
1061 ) -> Result<responses::CFResult, Error> {
1062 send_codeforces_api_req(self, api_key, api_secret)
1063 }
1064
1065 fn get_raw(
1066 &self,
1067 api_key: &str,
1068 api_secret: &str,
1069 ) -> Result<String, Error> {
1070 send_codeforces_api_req_raw(self, api_key, api_secret)
1071 }
1072}
1073
1074impl CFAPIRequestable for CFUserCommand {
1075 fn query_params(&self) -> Vec<(&'static str, String)> {
1076 let mut res = vec![];
1077 match self {
1078 CFUserCommand::BlogEntries { handle } => {
1079 res.push(("handle", handle.to_string()));
1080 }
1081 CFUserCommand::Friends { only_online } => {
1082 if let Some(b) = only_online {
1083 res.push((
1084 "onlyOnline",
1085 if *b {
1086 "true".to_string()
1087 } else {
1088 "false".to_string()
1089 },
1090 ));
1091 }
1092 }
1093 CFUserCommand::Info { handles } => {
1094 res.push(("handles", handles.join(";")));
1095 }
1096 CFUserCommand::RatedList { active_only } => {
1097 if let Some(b) = active_only {
1098 res.push((
1099 "activeOnly",
1100 if *b {
1101 "true".to_string()
1102 } else {
1103 "false".to_string()
1104 },
1105 ));
1106 }
1107 }
1108 CFUserCommand::Rating { handle } => {
1109 res.push(("handle", handle.to_string()));
1110 }
1111 CFUserCommand::Status {
1112 handle,
1113 from,
1114 count,
1115 } => {
1116 res.push(("handle", handle.to_string()));
1117 if let Some(i) = from {
1118 res.push(("from", i.to_string()));
1119 }
1120 if let Some(i) = count {
1121 res.push(("count", i.to_string()));
1122 }
1123 }
1124 }
1125 res
1126 }
1127
1128 fn method_name(&self) -> &'static str {
1129 match self {
1130 CFUserCommand::BlogEntries { .. } => "user.blogEntries",
1131 CFUserCommand::Friends { .. } => "user.friends",
1132 CFUserCommand::Info { .. } => "user.info",
1133 CFUserCommand::RatedList { .. } => "user.ratedList",
1134 CFUserCommand::Rating { .. } => "user.rating",
1135 CFUserCommand::Status { .. } => "user.status",
1136 }
1137 }
1138
1139 fn get(
1140 &self,
1141 api_key: &str,
1142 api_secret: &str,
1143 ) -> Result<responses::CFResult, Error> {
1144 send_codeforces_api_req(self, api_key, api_secret)
1145 }
1146
1147 fn get_raw(
1148 &self,
1149 api_key: &str,
1150 api_secret: &str,
1151 ) -> Result<String, Error> {
1152 send_codeforces_api_req_raw(self, api_key, api_secret)
1153 }
1154}
1155
1156/// Extra utility function which webscrapes problem pages to get input testcases
1157/// to a given problem.
1158///
1159/// Used internally to provide
1160/// [`problem.fetch_testcases()`](responses::CFProblem::fetch_testcases).
1161#[cfg(feature = "use_testcase_fetcher")]
1162pub fn fetch_testcases_for_problem(
1163 contest_id: &i64,
1164 problem_index: &str,
1165) -> Result<Vec<String>, Error> {
1166 // construct problem url.
1167 let url = "https://codeforces.com/contest/".to_string()
1168 + &contest_id.to_string()
1169 + "/problem/"
1170 + &problem_index.to_string();
1171 match get_url(&url) {
1172 // if fetch was successful, then read response.
1173 Ok(res) => {
1174 let document = Document::from_read(res).unwrap();
1175 // older problems use <br> instead of text \n chars in the
1176 // testcases. These are replaced by a regex for consistency.
1177 lazy_static! {
1178 static ref RE: Regex = Regex::new(r"(<br>|<br/>)").unwrap();
1179 }
1180 let testcases: Vec<String> = document
1181 .find(Descendant(Class("input"), Name("pre")))
1182 .map(|e| e.inner_html())
1183 .map(|e| RE.replace_all(&e, "\n").into())
1184 .collect();
1185 if testcases.is_empty() {
1186 Err(Error::Testcases(
1187 "No testcase input found for this \
1188 problem.",
1189 ))
1190 } else {
1191 Ok(testcases)
1192 }
1193 }
1194 // if fetch unsuccessful, then wrap `reqwest::Error` in custom Error.
1195 Err(e) => Err(Error::Http(e)),
1196 }
1197}
1198
1199#[cfg(feature = "use_testcase_fetcher")]
1200impl responses::CFProblem {
1201 /// Extra method which allows a user to fetch testcases directly from a
1202 /// [`CFProblem`](super::responses::CFProblem).
1203 ///
1204 /// Returns Vec of Strings where each String is a separate input testcase
1205 /// for the problem. Currently, the 'expected output' provided by
1206 /// Codeforces is not returned. However, in future this could be
1207 /// implemented relatively easily.
1208 ///
1209 /// Uses [`fetch_testcases_for_problem`] under the hood.
1210 pub fn fetch_testcases(&mut self) -> Result<Vec<String>, Error> {
1211 if self.contest_id.is_none() {
1212 return Err(Error::Testcases(
1213 "problem.contest_id field is \
1214 required.",
1215 ));
1216 }
1217 if self.index.is_none() {
1218 return Err(Error::Testcases("problem.index field is required."));
1219 }
1220 let testcases = fetch_testcases_for_problem(
1221 &self.contest_id.unwrap(),
1222 &self.index.as_ref().unwrap(),
1223 );
1224 // if getting testcases was successful, then set self.input_testcases.
1225 if let Ok(ref v) = testcases {
1226 self.input_testcases = Some(v.to_vec());
1227 }
1228 testcases
1229 }
1230}