rarbg_api/
lib.rs

1#![crate_name = "rarbg_api"]
2extern crate chrono;
3extern crate reqwest;
4extern crate serde;
5extern crate serde_json;
6
7use std::thread::sleep;
8use std::time::Duration;
9
10use reqwest::{Client, Error as ReqwestError, RequestBuilder, Response};
11use serde_json::Error as SerdeJsonError;
12
13use crate::api_parameters::ApiParameters;
14use crate::error::Error;
15use crate::mode::Mode;
16use crate::token::Token;
17use crate::torrents::Torrents;
18
19pub mod api_parameters;
20pub mod api_parameters_builder;
21pub mod category;
22pub mod episode_info;
23pub mod error;
24pub mod format;
25pub mod limit;
26pub mod mode;
27pub mod sort_by;
28pub mod token;
29pub mod torrent;
30pub mod torrents;
31
32/* The API has a 1req/2s limit. We take three extra seconds just to be sure. */
33const REQUEST_TIME_LIMIT: u64 = 5;
34const USER_AGENT: &str =
35    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:73.0) Gecko/20100101 Firefox/73.0";
36const ENDPOINT: &str = "https://torrentapi.org/pubapi_v2.php";
37
38#[derive(Debug)]
39pub struct RarBgApi {
40    app_id: String,
41    token: Token,
42}
43
44impl RarBgApi {
45    /// Return the name of your app.
46    ///
47    /// # Example
48    ///
49    /// ```no_run
50    /// use rarbg_api::RarBgApi;
51    ///
52    /// #[tokio::main]
53    /// async fn main() {
54    ///     let api = RarBgApi::new("RustExample").await;
55    ///     let app_id = api.app_id();
56    /// }
57    /// ```
58    pub fn app_id(&self) -> &str {
59        self.app_id.as_str()
60    }
61
62    /// Return the token associate to your app.
63    ///
64    /// # Example
65    ///
66    /// ```no_run
67    /// use rarbg_api::RarBgApi;
68    /// use rarbg_api::token::Token;
69    ///
70    /// #[tokio::main]
71    /// async fn main() {
72    ///     let api = RarBgApi::new("RustExample").await;
73    ///     let token: &Token = api.token();
74    /// }
75    /// ```
76    pub fn token(&self) -> &Token {
77        &self.token
78    }
79
80    /// Create a new RARBG client.
81    ///
82    /// # Arguments
83    ///
84    /// * `app_id` - A string slice that holds the name of your app.
85    ///
86    /// # Example
87    ///
88    /// ```no_run
89    /// use rarbg_api::RarBgApi;
90    /// use rarbg_api::token::Token;
91    ///
92    /// #[tokio::main]
93    /// async fn main() {
94    ///     let api = RarBgApi::new("RustExample").await;
95    /// }
96    /// ```
97    pub async fn new(app_id: &str) -> Self {
98        RarBgApi {
99            token: Token::new(app_id).await,
100            app_id: app_id.to_string(),
101        }
102    }
103
104    async fn request(
105        &mut self,
106        search_value: Option<&[(&str, &str)]>,
107        mode: Mode,
108        parameters: Option<&ApiParameters>,
109    ) -> Result<Torrents, Error> {
110        if !self.token.is_valid() {
111            self.token = Token::new(self.app_id()).await;
112        }
113        sleep(Duration::new(REQUEST_TIME_LIMIT, 0));
114
115        let client: Client = Client::builder().user_agent(USER_AGENT).build().unwrap();
116
117        let mut request: RequestBuilder = client
118            .get(ENDPOINT)
119            .query(&[("mode", mode.as_str())])
120            .query(&[("token", self.token().value())])
121            .query(&[("app_id", self.app_id())]);
122
123        if search_value.is_some() {
124            request = request.query(search_value.unwrap());
125        }
126
127        if parameters.is_some() {
128            let pm = parameters.unwrap();
129            request = request
130                .query(&[("ranked", *pm.ranked() as isize)])
131                .query(&[("sort", &pm.sort_by().as_str())])
132                .query(&[("limit", pm.limit().as_str())])
133                .query(&[("format", pm.format().as_str())]);
134
135            if pm.minimum_seeders().is_some() {
136                request = request.query(&[("min_seeders", pm.minimum_seeders().unwrap())]);
137            }
138
139            if pm.minimum_leechers().is_some() {
140                request = request.query(&[("min_leechers", pm.minimum_leechers().unwrap())]);
141            }
142
143            if pm.categories().is_some() {
144                let categories = pm.categories().unwrap();
145                let stringified_categories: Vec<&str> =
146                    categories.iter().map(|c| c.as_str()).collect();
147                let joined_categories: String = stringified_categories.join(";");
148                request = request.query(&[("category", joined_categories)]);
149            }
150        }
151        let response: Result<Response, ReqwestError> = request.send().await;
152
153        let content = match response {
154            Ok(res) => res.text().await,
155            Err(reason) => panic!("{}", reason),
156        };
157
158        let text = match content {
159            Ok(text) => text,
160            Err(reason) => panic!("{}", reason),
161        };
162
163        let torrents: Result<Torrents, SerdeJsonError> = serde_json::from_str(text.as_str());
164        match torrents {
165            Ok(torrents) => Ok(torrents),
166            Err(reason1) => {
167                let api_error: Result<Error, SerdeJsonError> = serde_json::from_str(text.as_str());
168                match api_error {
169                    Ok(api_error) => Err(api_error),
170                    Err(reason2) => panic!("First reason: {}. Second reason: {}", reason1, reason2),
171                }
172            }
173        }
174    }
175
176    /// List the torrents available depending on parameters given.
177    ///
178    /// # Example
179    ///
180    /// ```no_run
181    /// use rarbg_api::RarBgApi;
182    /// use rarbg_api::token::Token;
183    ///
184    /// #[tokio::main]
185    /// async fn main() {
186    ///     let mut api = RarBgApi::new("RustExample").await;
187    ///     // It will get the 25 last ranked torrents
188    ///     let result = api.list(None).await;
189    /// }
190    /// ```
191    pub async fn list(&mut self, parameters: Option<&ApiParameters>) -> Result<Torrents, Error> {
192        self.request(None, Mode::List, parameters).await
193    }
194
195    /// Search torrents by its name with some or no parameters.
196    ///
197    /// # Example
198    ///
199    /// ```no_run
200    /// use rarbg_api::RarBgApi;
201    /// use rarbg_api::token::Token;
202    ///
203    /// #[tokio::main]
204    /// async fn main() {
205    ///     let mut api = RarBgApi::new("RustExample").await;
206    ///     let result = api.search("Rick and Morty", None).await;
207    /// }
208    /// ```
209    pub async fn search(
210        &mut self,
211        value: &str,
212        parameters: Option<&ApiParameters>,
213    ) -> Result<Torrents, Error> {
214        self.request(Some(&[("search_string", value)]), Mode::Search, parameters)
215            .await
216    }
217
218    /// Search torrents by its IMDB id with some or no parameters.
219    ///
220    /// # Example
221    ///
222    /// ```no_run
223    /// use rarbg_api::RarBgApi;
224    /// use rarbg_api::token::Token;
225    ///
226    /// #[tokio::main]
227    /// async fn main() {
228    ///     let mut api = RarBgApi::new("RustExample").await;
229    ///     // tt2861424 is Rick and Morty
230    ///     let result = api.search_by_imdb("tt2861424", None).await;
231    /// }
232    /// ```
233    pub async fn search_by_imdb(
234        &mut self,
235        value: &str,
236        parameters: Option<&ApiParameters>,
237    ) -> Result<Torrents, Error> {
238        self.request(Some(&[("search_imdb", value)]), Mode::Search, parameters)
239            .await
240    }
241
242    /// Search torrents by its TVDB id with some or no parameters.
243    ///
244    /// # Example
245    ///
246    /// ```no_run
247    /// use rarbg_api::RarBgApi;
248    /// use rarbg_api::token::Token;
249    ///
250    /// #[tokio::main]
251    /// async fn main() {
252    ///     let mut api = RarBgApi::new("RustExample").await;
253    ///     // 275274 is Rick and Morty
254    ///     let result = api.search_by_tvdb("275274", None).await;
255    /// }
256    /// ```
257    pub async fn search_by_tvdb(
258        &mut self,
259        value: &str,
260        parameters: Option<&ApiParameters>,
261    ) -> Result<Torrents, Error> {
262        self.request(Some(&[("search_tvdb", value)]), Mode::Search, parameters)
263            .await
264    }
265
266    /// Search torrents by its TMDB id with some or no parameters.
267    ///
268    /// # Example
269    ///
270    /// ```no_run
271    /// use rarbg_api::RarBgApi;
272    /// use rarbg_api::token::Token;
273    ///
274    /// #[tokio::main]
275    /// async fn main() {
276    ///     let mut api = RarBgApi::new("RustExample").await;
277    ///     // 60625 is Rick and Morty
278    ///     let result = api.search_by_tmdb("60625", None).await;
279    /// }
280    /// ```
281    pub async fn search_by_tmdb(
282        &mut self,
283        value: &str,
284        parameters: Option<&ApiParameters>,
285    ) -> Result<Torrents, Error> {
286        self.request(Some(&[("search_tmdb", value)]), Mode::Search, parameters)
287            .await
288    }
289}