subspedia_rs/
lib.rs

1//! This crate is a simple library for [subspedia](https://www.subspedia.tv/) based on api the
2//! provided by site
3
4extern crate hyper;
5extern crate hyper_tls;
6extern crate tokio;
7extern crate futures;
8#[macro_use]
9extern crate serde_derive;
10extern crate failure;
11#[macro_use]
12extern crate failure_derive;
13extern crate serde;
14extern crate serde_json;
15
16use futures::{Future, Stream};
17use std::sync::{Arc, Mutex};
18use std::borrow::Cow;
19
20/// An enumeration of possible error which can occur during http requests and the parsing of json
21/// returned by the api
22#[derive(Debug, Fail)]
23pub enum FetchError {
24    #[fail(display = "HTTP error: {}", _0)]
25    Http(hyper::Error),
26    #[fail(display = "JSON parsing error: {}", _0)]
27    Json(serde_json::Error),
28    #[fail(display = "{}", _0)]
29    NotFound(String),
30}
31
32impl From<hyper::Error> for FetchError {
33    fn from(err: hyper::Error) -> FetchError {
34        FetchError::Http(err)
35    }
36}
37
38impl From<serde_json::Error> for FetchError {
39    fn from(err: serde_json::Error) -> FetchError {
40        FetchError::Json(err)
41    }
42}
43
44/// Trait that requests have to implement.
45pub trait Request {
46    type Response: serde::de::DeserializeOwned + std::fmt::Debug + std::marker::Send;
47
48    /// Return api url based on type of response that are you looking for.
49    fn url(&self) -> Cow<'static, str>;
50}
51
52/// Struct for store the television series in translation.
53#[derive(Deserialize, Debug)]
54pub struct SerieTraduzione {
55    id_serie: usize,
56    nome_serie: String,
57    link_serie: String,
58    id_thetvdb: usize,
59    num_stagione: usize,
60    num_episodio: usize,
61    stato: String,
62}
63
64/// Struct to perform the request for the series that are in translation.
65pub struct ReqSerieTraduzione;
66
67impl Request for ReqSerieTraduzione {
68    type Response = SerieTraduzione;
69
70    fn url(&self) -> Cow<'static, str> {
71        Cow::Borrowed("https://www.subspedia.tv/API/serie_traduzione")
72    }
73}
74
75/// Struct for store the television series available on site.
76#[derive(Deserialize, Debug, Clone)]
77pub struct Serie {
78    id_serie: usize,
79    pub nome_serie: String,
80    link_serie: String,
81    id_thetvdb: usize,
82    stato: String,
83    anno: usize,
84}
85
86/// Struct to perform the request for the all series available on site.
87pub struct ReqElencoSerie;
88
89impl Request for ReqElencoSerie {
90    type Response = Serie;
91
92    fn url(&self) -> Cow<'static, str> {
93        Cow::Borrowed("https://www.subspedia.tv/API/elenco_serie")
94    }
95}
96
97/// Struct for store the subtitles.
98#[derive(Deserialize, Debug)]
99pub struct Sottotitolo {
100    id_serie: usize,
101    nome_serie: String,
102    ep_titolo: String,
103    num_stagione: usize,
104    num_episodio: usize,
105    immagine: String,
106    link_sottotitoli: String,
107    link_serie: String,
108    link_file: String,
109    descrizione: String,
110    id_thetvdb: usize,
111    data_uscita: String,
112    grazie: usize,
113}
114
115/// Struct to perform the request for the last subtitles translated.
116pub struct ReqUltimiSottotitoli;
117
118impl Request for ReqUltimiSottotitoli {
119    type Response = Sottotitolo;
120
121    fn url(&self) -> Cow<'static, str> {
122        Cow::Borrowed("https://www.subspedia.tv/API/ultimi_sottotitoli")
123    }
124}
125
126/// Struct to perform the subtitle request for a series.
127pub struct ReqSottotitoliSerie {
128    id: usize,
129}
130
131impl ReqSottotitoliSerie {
132    /// Create a new ReqSottotitoliSerie with the given series id.
133    pub fn new(id: usize) -> ReqSottotitoliSerie {
134        ReqSottotitoliSerie { id }
135    }
136}
137
138impl Request for ReqSottotitoliSerie {
139    type Response = Sottotitolo;
140
141    fn url(&self) -> Cow<'static, str> {
142        Cow::Owned(format!("https://www.subspedia.tv/API/sottotitoli_serie?serie={}", self.id))
143    }
144}
145
146///Makes a request based on given type
147///
148/// # Errors
149///
150/// Returns error if something gone wrong during http requests and parsing json.
151///
152/// # Exemple
153///
154///```
155///extern crate subspedia;
156///
157///use subspedia::ReqSerieTraduzione;
158///
159///fn main() {
160///    println!("{:#?}", subspedia::get(ReqSerieTraduzione).unwrap());
161///}
162/// ```
163pub fn get<R: 'static + Request>(req: &R) -> Result<Vec<R::Response>, FetchError>
164{
165    let url = req.url().parse().unwrap();
166    let result = Arc::new(Mutex::new(Vec::new()));
167
168    let tmp = Arc::clone(&result);
169
170    tokio::run(futures::lazy(move || {
171        fetch_json::<R::Response>(url)
172            // use the parsed vector
173            .map(move |mut serie| {
174                tmp.lock().unwrap().append(&mut serie);
175            })
176            // if there was an error print it
177            .map_err(|e| eprintln!("{}", e))
178    }));
179
180    Ok(Arc::try_unwrap(result).unwrap().into_inner().unwrap())
181}
182
183///Search serie based on a given name
184///
185/// # Errors
186///
187/// Returns error if something gone wrong during http requests, parsing json or if a series with that
188/// name isn't found
189///
190/// # Exemple
191///
192///```
193///extern crate subspedia;
194///
195///use subspedia::{FetchError, search_by_name};
196///
197///fn main() -> Result<(), FetchError> {
198///    println!("{:#?}", search_by_name("serie name")?);
199///    Ok(())
200///}
201/// ```
202pub fn search_by_name(name: &str) -> Result<Vec<Serie>, FetchError> {
203    let result = get(&ReqElencoSerie)?
204        .iter()
205        .filter(|s| s.nome_serie
206            .to_lowercase()
207            .as_str()
208            .contains(name.to_lowercase().as_str())
209        )
210        .collect::<Vec<_>>()
211        .iter()
212        .map(|s| (**s).clone())
213        .collect::<Vec<_>>();
214
215    if !result.is_empty() {
216        Ok(result)
217    } else {
218        Err(FetchError::NotFound(format!("Series with name {} not found", name)))
219    }
220}
221
222///Search the series based on a given id
223///
224/// # Errors
225///
226/// Returns error if something gone wrong during http requests, parsing json or if a series with that
227/// id isn't found
228///
229/// # Exemple
230///
231///```
232/// extern crate subspedia;
233///
234/// use subspedia::{FetchError, search_by_id};
235///
236/// fn main() -> Result<(), FetchError> {
237///    println!("{:#?}", search_by_id(500)?);
238///    Ok(())
239/// }
240/// ```
241pub fn search_by_id(id: usize) -> Result<Serie, FetchError> {
242    match get(&ReqElencoSerie)?
243        .iter()
244        .filter(|s| s.id_serie == id)
245        .collect::<Vec<_>>()
246        .pop() {
247        Some(s) => Ok(s.clone()),
248        None => Err(FetchError::NotFound(format!("Series with id {} not found.", id)))
249    }
250}
251
252fn fetch_json<T>(url: hyper::Uri) -> impl Future<Item=Vec<T>, Error=FetchError>
253    where T: serde::de::DeserializeOwned + std::fmt::Debug
254{
255    let https = hyper_tls::HttpsConnector::new(4).unwrap();
256    let client = hyper::Client::builder()
257        .build::<_, hyper::Body>(https);
258
259    client
260        // Fetch the url...
261        .get(url)
262        // And then, if we get a response back...
263        .and_then(|res| {
264            // asynchronously concatenate chunks of the body
265            res.into_body().concat2()
266        })
267        .from_err::<FetchError>()
268        // use the body after concatenation
269        .and_then(|body| {
270            // try to parse as json with serde_json
271            let serie = serde_json::from_slice(&body)?;
272            Ok(serie)
273        })
274}