tmdb_async/
lib.rs

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