You've already forked flix
Initial commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Collection, CollectionId};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Collections API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the details of the collection refered to by ID
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<CollectionId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Collection, Error> {
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/collection/{}", id.into()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Episode, ShowId};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Episodes API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the details of the episode refered to by ID, season number and episode number
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<ShowId>,
|
||||
season: impl Into<i32>,
|
||||
episode: impl Into<i32>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Episode, Error> {
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!(
|
||||
"/3/tv/{}/season/{}/episode/{}",
|
||||
id.into(),
|
||||
season.into(),
|
||||
episode.into()
|
||||
),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{MovieGenre, ShowGenre};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Genre API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the list of all valid movie genres
|
||||
pub async fn get_movie_genres(&self, language: Option<&str>) -> Result<Vec<MovieGenre>, Error> {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Genres {
|
||||
genres: Vec<MovieGenre>,
|
||||
}
|
||||
|
||||
let genres: Genres = self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(&self.config, "/3/genre/movie/list", language)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(genres.genres)
|
||||
}
|
||||
|
||||
/// Fetch the list of all valid show genres
|
||||
pub async fn get_tv_genres(&self, language: Option<&str>) -> Result<Vec<ShowGenre>, Error> {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Genres {
|
||||
genres: Vec<ShowGenre>,
|
||||
}
|
||||
|
||||
let genres: Genres = self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(&self.config, "/3/genre/tv/list", language)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(genres.genres)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use reqwest::Request;
|
||||
use reqwest::header;
|
||||
|
||||
use crate::Config;
|
||||
|
||||
/// Collections API
|
||||
pub mod collections;
|
||||
/// Episodes API
|
||||
pub mod episodes;
|
||||
/// Genres API
|
||||
pub mod genres;
|
||||
/// Movies API
|
||||
pub mod movies;
|
||||
/// Seasons API
|
||||
pub mod seasons;
|
||||
/// Shows API
|
||||
pub mod shows;
|
||||
|
||||
/// A generic error wrapping Url and Reqwest errors
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Url error wrapper
|
||||
#[error("url parse error: {0}")]
|
||||
Url(#[from] url::ParseError),
|
||||
/// Reqwest error wrapper
|
||||
#[error("reqwest error: {0}")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
}
|
||||
|
||||
fn make_request(config: &Config, path: &str, language: Option<&str>) -> Result<Request, Error> {
|
||||
let url = config.base_url.join(path)?;
|
||||
|
||||
let mut builder = config.client.get(url).header(
|
||||
header::AUTHORIZATION,
|
||||
format!("Bearer {}", config.bearer_token),
|
||||
);
|
||||
if let Some(ref user_agent) = config.user_agent {
|
||||
builder = builder.header(header::USER_AGENT, user_agent);
|
||||
}
|
||||
if let Some(language) = language {
|
||||
builder = builder.query(&[("language", language)]);
|
||||
}
|
||||
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Movie, MovieId};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Movies API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the details of the movie refered to by ID
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<MovieId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Movie, Error> {
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/movie/{}", id.into()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Season, ShowId};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Seasons API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the details of the season refered to by ID and season number
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<ShowId>,
|
||||
season: impl Into<i32>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Season, Error> {
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/tv/{}/season/{}", id.into(), season.into()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Show, ShowId};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Shows API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the details of the show refered to by ID
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<ShowId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Show, Error> {
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/tv/{}", id.into()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{Config, api};
|
||||
|
||||
/// The primary client that references all other clients
|
||||
pub struct Client {
|
||||
genres: api::genres::Client,
|
||||
collections: api::collections::Client,
|
||||
movies: api::movies::Client,
|
||||
shows: api::shows::Client,
|
||||
seasons: api::seasons::Client,
|
||||
episodes: api::episodes::Client,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client from a default configuration using the bearer token
|
||||
pub fn new(bearer_token: String) -> Self {
|
||||
Self::new_with_config(Config::new(bearer_token))
|
||||
}
|
||||
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new_with_config(config: Config) -> Self {
|
||||
let config = Rc::new(config);
|
||||
Self {
|
||||
genres: api::genres::Client::new(config.clone()),
|
||||
collections: api::collections::Client::new(config.clone()),
|
||||
movies: api::movies::Client::new(config.clone()),
|
||||
shows: api::shows::Client::new(config.clone()),
|
||||
seasons: api::seasons::Client::new(config.clone()),
|
||||
episodes: api::episodes::Client::new(config.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Access the Genres API
|
||||
pub fn genres(&self) -> &api::genres::Client {
|
||||
&self.genres
|
||||
}
|
||||
|
||||
/// Access the Collections API
|
||||
pub fn collections(&self) -> &api::collections::Client {
|
||||
&self.collections
|
||||
}
|
||||
|
||||
/// Access the Movies API
|
||||
pub fn movies(&self) -> &api::movies::Client {
|
||||
&self.movies
|
||||
}
|
||||
|
||||
/// Access the Shows API
|
||||
pub fn shows(&self) -> &api::shows::Client {
|
||||
&self.shows
|
||||
}
|
||||
|
||||
/// Access the Seasons API
|
||||
pub fn seasons(&self) -> &api::seasons::Client {
|
||||
&self.seasons
|
||||
}
|
||||
|
||||
/// Access the Episodes API
|
||||
pub fn episodes(&self) -> &api::episodes::Client {
|
||||
&self.episodes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use url::Url;
|
||||
use url_macro::url;
|
||||
|
||||
/// The client configuration
|
||||
pub struct Config {
|
||||
/// The base URL of the API
|
||||
pub base_url: Url,
|
||||
/// The reqwest client that is used for every request
|
||||
pub client: reqwest::Client,
|
||||
/// The bearer token for readonly access to the API
|
||||
pub bearer_token: String,
|
||||
/// An optional user agent string to provide to the API
|
||||
pub user_agent: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Create a new configuration using the provided bearer token
|
||||
pub fn new(bearer_token: String) -> Self {
|
||||
Self {
|
||||
base_url: url!("https://api.themoviedb.org"),
|
||||
client: reqwest::Client::new(),
|
||||
bearer_token,
|
||||
user_agent: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//! flix-tmdb provides clients and models for fetching data from TMDB
|
||||
|
||||
/// TMDB API clients
|
||||
pub mod api;
|
||||
/// Deserializable types from the TMDB API
|
||||
pub mod model;
|
||||
|
||||
mod client;
|
||||
pub use client::Client;
|
||||
|
||||
mod config;
|
||||
pub use config::Config;
|
||||
@@ -0,0 +1,22 @@
|
||||
use super::{CollectionId, MovieId};
|
||||
|
||||
/// A deserialized Collection from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Collection {
|
||||
/// The collection's TMDB ID
|
||||
pub id: CollectionId,
|
||||
/// The collection's name
|
||||
pub name: String,
|
||||
/// The collection's overview
|
||||
pub overview: String,
|
||||
/// The list of movies that are part of this collection
|
||||
#[serde(rename = "parts")]
|
||||
pub movies: Vec<Item>,
|
||||
}
|
||||
|
||||
/// A deserialized collection item from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Item {
|
||||
/// The movie's TMDB ID
|
||||
pub id: MovieId,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
/// A deserialized Episode from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Episode {
|
||||
/// The episode's number
|
||||
pub episode_number: i32,
|
||||
/// The episode's name
|
||||
pub name: String,
|
||||
/// The episode's overview
|
||||
pub overview: String,
|
||||
/// The episode's air date
|
||||
pub air_date: NaiveDate,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use super::id::{MovieGenreId, ShowGenreId};
|
||||
|
||||
/// A deserialized movie Genre from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct MovieGenre {
|
||||
/// The genre's TMDB ID
|
||||
pub id: MovieGenreId,
|
||||
/// The genre's name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// A deserialized show Genre from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ShowGenre {
|
||||
/// The genre's TMDB ID
|
||||
pub id: ShowGenreId,
|
||||
/// The genre's name
|
||||
pub name: String,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// The TMDB ID type of a movie genre
|
||||
pub type MovieGenreId = TmdbId<MovieGenre>;
|
||||
/// The TMDB ID type of a show genre
|
||||
pub type ShowGenreId = TmdbId<ShowGenre>;
|
||||
/// The TMDB ID type of a collection
|
||||
pub type CollectionId = TmdbId<Collection>;
|
||||
/// The TMDB ID type of a movie
|
||||
pub type MovieId = TmdbId<Movie>;
|
||||
/// The TMDB ID type of a show
|
||||
pub type ShowId = TmdbId<Show>;
|
||||
|
||||
pub enum MovieGenre {}
|
||||
pub enum ShowGenre {}
|
||||
pub enum Collection {}
|
||||
pub enum Movie {}
|
||||
pub enum Show {}
|
||||
|
||||
type Inner = i32;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
#[repr(transparent)]
|
||||
pub struct TmdbId<T> {
|
||||
inner: Inner,
|
||||
#[serde(skip_serializing, default)]
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TmdbId<T> {
|
||||
pub fn inner(&self) -> Inner {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Inner> for TmdbId<T> {
|
||||
fn from(value: Inner) -> Self {
|
||||
Self {
|
||||
inner: value,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<TmdbId<T>> for Inner {
|
||||
fn from(value: TmdbId<T>) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for TmdbId<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for TmdbId<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TmdbId<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for TmdbId<T> {}
|
||||
|
||||
impl<T> PartialEq for TmdbId<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for TmdbId<T> {}
|
||||
@@ -0,0 +1,17 @@
|
||||
mod collection;
|
||||
mod episode;
|
||||
mod genre;
|
||||
mod id;
|
||||
mod movie;
|
||||
mod season;
|
||||
mod show;
|
||||
|
||||
pub use collection::*;
|
||||
pub use episode::*;
|
||||
pub use genre::*;
|
||||
pub use movie::*;
|
||||
pub use season::*;
|
||||
pub use serde::*;
|
||||
pub use show::*;
|
||||
|
||||
pub use id::{CollectionId, MovieGenreId, MovieId, ShowGenreId, ShowId};
|
||||
@@ -0,0 +1,53 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::{CollectionId, MovieGenre, MovieId};
|
||||
|
||||
/// A deserialized Movie from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Movie {
|
||||
/// The movie's TMDB ID
|
||||
pub id: MovieId,
|
||||
/// The movie's collection, if it exists
|
||||
#[serde(rename = "belongs_to_collection")]
|
||||
pub collection: Option<InCollection>,
|
||||
/// The movie's title
|
||||
pub title: String,
|
||||
/// The movie's overview
|
||||
pub overview: String,
|
||||
/// The list of genres the movie belongs to
|
||||
pub genres: Vec<MovieGenre>,
|
||||
/// The movie's release date
|
||||
pub release_date: NaiveDate,
|
||||
/// The movie's status
|
||||
pub status: MovieStatus,
|
||||
}
|
||||
|
||||
/// A deserialized movie's collection from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct InCollection {
|
||||
/// The collection's TMDB ID
|
||||
pub id: CollectionId,
|
||||
}
|
||||
|
||||
/// A deserialized movie status from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub enum MovieStatus {
|
||||
/// The movie was cancelled
|
||||
#[serde(rename = "Canceled")]
|
||||
Canceled,
|
||||
/// The movie is in production
|
||||
#[serde(rename = "In Production")]
|
||||
InProduction,
|
||||
/// The movie is planned
|
||||
#[serde(rename = "Planned")]
|
||||
Planned,
|
||||
/// The movie is in post production
|
||||
#[serde(rename = "Post Production")]
|
||||
PostProduction,
|
||||
/// The movie is released
|
||||
#[serde(rename = "Released")]
|
||||
Released,
|
||||
/// The movie is rumored
|
||||
#[serde(rename = "Rumored")]
|
||||
Rumored,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::Episode;
|
||||
|
||||
/// A deserialized Season from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Season {
|
||||
/// The season's number
|
||||
pub season_number: i32,
|
||||
/// The season's name
|
||||
pub name: String,
|
||||
/// The season's overview
|
||||
pub overview: String,
|
||||
/// The season's air date
|
||||
pub air_date: NaiveDate,
|
||||
/// The list of episodes in this season
|
||||
pub episodes: Vec<Episode>,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::{ShowGenre, ShowId};
|
||||
|
||||
/// A deserialized Show from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct Show {
|
||||
/// The show's TMDB ID
|
||||
pub id: ShowId,
|
||||
/// The show's name
|
||||
pub name: String,
|
||||
/// The show's overview
|
||||
pub overview: String,
|
||||
/// The list of genres this show belongs to
|
||||
pub genres: Vec<ShowGenre>,
|
||||
/// The show's first air date
|
||||
pub first_air_date: NaiveDate,
|
||||
/// The show's last air date
|
||||
pub last_air_date: NaiveDate,
|
||||
/// The number of seasons in this show
|
||||
pub number_of_seasons: i32,
|
||||
/// The show's status
|
||||
pub status: ShowStatus,
|
||||
}
|
||||
|
||||
/// A deserialized show Status from the TMDB API
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub enum ShowStatus {
|
||||
/// The show is returning
|
||||
#[serde(rename = "Returning Series")]
|
||||
Returning,
|
||||
/// The show is planned
|
||||
#[serde(rename = "Planned")]
|
||||
Planned,
|
||||
/// The show is in procuction
|
||||
#[serde(rename = "In Production")]
|
||||
InProduction,
|
||||
/// The show has ended
|
||||
#[serde(rename = "Ended")]
|
||||
Ended,
|
||||
/// The show is canceled
|
||||
#[serde(rename = "Canceled")]
|
||||
Canceled,
|
||||
/// The show only released a pilot
|
||||
#[serde(rename = "Pilot")]
|
||||
Pilot,
|
||||
}
|
||||
Reference in New Issue
Block a user