#![allow(unused_parens)]
#![warn(clippy::future_not_send)]
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")
}
#[inline]
pub fn with_language(api_key: String, language: &str) -> Self {
Self{
http: reqwest::Client::new(),
api_key,
language: language.into()
}
}
#[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")))
};
let path = format_compact!("/movie/{}", id);
self.get(&path, 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 path = format_compact!("/find/tt{:07}", id);
let result: FindResult = self.get(&path, &[
("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")))
};
let path = format_compact!("/tv/{}", id);
self.get(&path, 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 path = format_compact!("/find/tt{:07}", id);
let result: FindResult = self.get(&path, &[
("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
}
#[inline]
pub async fn tv_by_tvdb_id(&self, id: u32) -> Result<TV, Error> {
let path = format_compact!("/find/{}", id);
let result: FindResult = self.get(&path, &[
("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> {
let path = format_compact!("/tv/{}/external_ids", id);
self.get(&path, &[]).await
}
}