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}