1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#![allow(unused_parens)]
use cervine::Cow;
use compact_str::format_compact;
use compact_str::CompactString;
use compact_str::ToCompactString;
use itertools::Itertools;
pub use reqwest::Error;
use serde::de::DeserializeOwned;
mod model;
use model::FindResult;
pub use model::{Movie, MovieSearchResult, TV, TVExternalIds, TVSearchResult};
#[cfg(test)]
mod integration_tests;
const BASE_URL: &str = "https://api.themoviedb.org/3";
#[derive(Debug, Clone)]
pub struct Client {
http: reqwest::Client,
api_key: String,
language: CompactString
}
#[inline]
fn compact_str_url(k: &str, v: &str) -> CompactString {
format_compact!("{k}={v}")
}
impl Client {
pub fn new(api_key: String) -> Self {
Self::with_language(api_key, "en".into())
}
pub fn with_language(api_key: String, language: CompactString) -> Self {
Self{
http: reqwest::Client::new(),
api_key,
language
}
}
#[inline]
async fn get<T: DeserializeOwned>(&self, path: &str, args: &[(&'static str, Cow<'_, CompactString, str>)]) -> Result<T, Error> {
let url = format!(
"{}{}?api_key={}&language={}&{}",
BASE_URL,
path,
self.api_key,
self.language,
args.iter().map(|(k, v)| compact_str_url(k, v)).join("&")
);
self.http.get(url).send().await?.json().await
}
#[inline]
pub async fn movie_search(&self, title: &str, year: Option<u16>) -> Result<MovieSearchResult, Error> {
let mut args = Vec::with_capacity(3);
args.push(("query", Cow::Borrowed(title)));
if let Some(year) = year {
args.push(("year", Cow::Owned(year.to_compact_string())));
}
args.push(("append_to_response", Cow::Borrowed("images")));
self.get("/search/movie", &args).await
}
#[inline]
pub async fn movie_by_id(&self, id: u32, include_videos: bool, include_credits: bool) -> Result<Movie, Error> {
let args = match (include_videos, include_credits) {
(false, false) => None,
(true, false) => Some(("append_to_response", Cow::Borrowed("videos"))),
(false, true) => Some(("append_to_response", Cow::Borrowed("credits"))),
(true, true) => Some(("append_to_response", Cow::Borrowed("videos,credits")))
};
self.get(&format_compact!("/movie/{}", id), args.as_ref().map(core::slice::from_ref).unwrap_or_default()).await
}
#[inline]
pub async fn movie_by_imdb_id(&self, id: u32) -> Result<Movie, Error> {
let result: FindResult = self.get(&format_compact!("/find/tt{:07}", id), &[
("external_source", Cow::Borrowed("imdb_id")),
("append_to_response", Cow::Borrowed("images"))
]).await?;
self.movie_by_id(result.movie_results[0].id, false, false).await
}
#[inline]
pub async fn tv_search(&self, title: &str, year: Option<u16>) -> Result<TVSearchResult, Error> {
let mut args = Vec::with_capacity(3);
args.push(("query", Cow::Borrowed(title)));
if let Some(year) = year {
args.push(("year", Cow::Owned(year.to_compact_string())));
}
args.push(("append_to_response", Cow::Borrowed("images")));
self.get("/search/tv", &args).await
}
#[inline]
pub async fn tv_by_id(&self, id: u32, include_videos: bool, include_credits: bool) -> Result<TV, Error> {
let args = match (include_videos, include_credits) {
(false, false) => None,
(true, false) => Some(("append_to_response", Cow::Borrowed("videos"))),
(false, true) => Some(("append_to_response", Cow::Borrowed("credits"))),
(true, true) => Some(("append_to_response", Cow::Borrowed("videos,credits")))
};
self.get(&format_compact!("/tv/{}", id), args.as_ref().map(core::slice::from_ref).unwrap_or_default()).await
}
#[inline]
pub async fn tv_by_imdb_id(&self, id: u32) -> Result<TV, Error> {
let result: FindResult = self.get(&format_compact!("/find/tt{}", id), &[
("external_source", Cow::Borrowed("imdb_id")),
("append_to_response", Cow::Borrowed("images"))
]).await?;
self.tv_by_id(result.tv_results[0].id, false, false).await
}
pub async fn tv_by_tvdb_id(&self, id: u32) -> Result<TV, Error> {
let result: FindResult = self.get(&format_compact!("/find/{}", id), &[
("external_source", Cow::Borrowed("tvdb_id")),
("append_to_response", Cow::Borrowed("images"))
]).await?;
self.tv_by_id(result.tv_results[0].id, false, false).await
}
#[inline]
pub async fn tv_external_ids(&self, id: u32) -> Result<TVExternalIds, Error> {
self.get(&format_compact!("/tv/{}/external_ids", id), &[]).await
}
}