You've already forked flix
Initial commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# OS Files
|
||||||
|
*~
|
||||||
|
._*
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
/target
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
hard_tabs = true
|
||||||
|
wrap_comments = true
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"vadimcn.vscode-lldb",
|
||||||
|
"barbosshack.crates-io",
|
||||||
|
"usernamehw.errorlens",
|
||||||
|
"tamasfe.even-better-toml",
|
||||||
|
"rust-lang.rust-analyzer",
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
// VSCode
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"editor.insertSpaces": false,
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/target": true,
|
||||||
|
"**/Cargo.lock": true,
|
||||||
|
},
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimFinalNewlines": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/.git/**": true,
|
||||||
|
"**/target/**": true,
|
||||||
|
},
|
||||||
|
// Extensions
|
||||||
|
"crates.listPreReleases": true,
|
||||||
|
"evenBetterToml.formatter.alignComments": true,
|
||||||
|
"evenBetterToml.formatter.alignEntries": false,
|
||||||
|
"evenBetterToml.formatter.allowedBlankLines": 1,
|
||||||
|
"evenBetterToml.formatter.arrayAutoExpand": true,
|
||||||
|
"evenBetterToml.formatter.arrayTrailingComma": true,
|
||||||
|
"evenBetterToml.formatter.columnWidth": 80,
|
||||||
|
"evenBetterToml.formatter.reorderKeys": true,
|
||||||
|
"evenBetterToml.formatter.trailingNewline": true,
|
||||||
|
"rust-analyzer.imports.granularity.enforce": true,
|
||||||
|
"rust-analyzer.imports.granularity.group": "module",
|
||||||
|
"rust-analyzer.imports.group.enable": true,
|
||||||
|
"rust-analyzer.imports.merge.glob": false,
|
||||||
|
"rust-analyzer.imports.preferNoStd": true,
|
||||||
|
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
How to Contribute
|
||||||
|
=================
|
||||||
|
|
||||||
|
We'd love to accept your patches and contributions to this project.
|
||||||
|
We just need you to follow the Contributor License Agreement outlined
|
||||||
|
in the latest v0.0.x of https://github.com/Skrunix/license
|
||||||
Generated
+1743
File diff suppressed because it is too large
Load Diff
+50
@@ -0,0 +1,50 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["crates/*"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
authors = []
|
||||||
|
edition = "2024"
|
||||||
|
license-file = "LICENSE.md"
|
||||||
|
rust-version = "1.85.0"
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
arithmetic_overflow = "forbid"
|
||||||
|
missing_docs = "forbid"
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
arithmetic_side_effects = "forbid"
|
||||||
|
as_conversions = "forbid"
|
||||||
|
checked_conversions = "forbid"
|
||||||
|
default_union_representation = "forbid"
|
||||||
|
expect_used = "forbid"
|
||||||
|
indexing_slicing = "forbid"
|
||||||
|
integer_division = "forbid"
|
||||||
|
integer_division_remainder_used = "forbid"
|
||||||
|
transmute_undefined_repr = "forbid"
|
||||||
|
unchecked_duration_subtraction = "forbid"
|
||||||
|
unwrap_used = "forbid"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = "fat"
|
||||||
|
opt-level = 3
|
||||||
|
overflow-checks = true
|
||||||
|
strip = "debuginfo"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
flix = { path = "crates/flix", version = "=0.0.1", default-features = false }
|
||||||
|
flix-tmdb = { path = "crates/tmdb", version = "=0.0.1", default-features = false }
|
||||||
|
|
||||||
|
anyhow = { version = "^1", default-features = false }
|
||||||
|
chrono = { version = "^0.4", default-features = false }
|
||||||
|
clap = { version = "^4", default-features = false, features = ["std"] }
|
||||||
|
home = { version = "^0.5", default-features = false }
|
||||||
|
reqwest = { version = "^0.12", default-features = false }
|
||||||
|
serde = { version = "^1", default-features = false }
|
||||||
|
thiserror = { version = "^2", default-features = false }
|
||||||
|
tokio = { version = "^1", default-features = false }
|
||||||
|
toml = { version = "^0.8", default-features = false }
|
||||||
|
url = { version = "^2", default-features = false }
|
||||||
|
url-macro = { version = "^0.2", default-features = false }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Skrunix Software License
|
||||||
|
========================
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software is
|
||||||
|
outlined in the latest v0.0.x of https://github.com/Skrunix/license
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Flix
|
||||||
|
|
||||||
|
Libraries and tools for dealing with media metadata
|
||||||
|
|
||||||
|
## Various builds
|
||||||
|
|
||||||
|
- build: `cargo hack --feature-powerset build`
|
||||||
|
- clippy: `cargo hack --feature-powerset clippy -- -D warnings`
|
||||||
|
- fmt: `cargo fmt --check`
|
||||||
|
- docs: `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features`
|
||||||
|
- install: `cargo install --path crates/cli`
|
||||||
|
- publish: `cargo publish --dry-run -p flix-tmdb`
|
||||||
|
- publish: `cargo publish --dry-run -p flix`
|
||||||
|
- publish: `cargo publish --dry-run -p flix-cli`
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
[package]
|
||||||
|
name = "flix-cli"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
description = "CLI for interacting with flix media"
|
||||||
|
repository = "https://github.com/QuantumShade/flix"
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license-file.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
doc = false
|
||||||
|
name = "flix"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
arithmetic_overflow = "forbid"
|
||||||
|
unsafe_code = "forbid"
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
arithmetic_side_effects = "deny"
|
||||||
|
as_conversions = "deny"
|
||||||
|
checked_conversions = "deny"
|
||||||
|
default_union_representation = "deny"
|
||||||
|
expect_used = "deny"
|
||||||
|
indexing_slicing = "deny"
|
||||||
|
integer_division = "deny"
|
||||||
|
integer_division_remainder_used = "deny"
|
||||||
|
transmute_undefined_repr = "deny"
|
||||||
|
unchecked_duration_subtraction = "deny"
|
||||||
|
unwrap_used = "deny"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
flix = { workspace = true, features = ["tmdb"] }
|
||||||
|
flix-tmdb = { workspace = true }
|
||||||
|
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
clap = { workspace = true, features = [
|
||||||
|
"derive",
|
||||||
|
"color",
|
||||||
|
"error-context",
|
||||||
|
"help",
|
||||||
|
"suggestions",
|
||||||
|
"usage",
|
||||||
|
] }
|
||||||
|
home = { workspace = true }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
tokio = { workspace = true, features = ["rt", "fs", "macros"] }
|
||||||
|
toml = { workspace = true, features = ["display", "parse"] }
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# flix-cli
|
||||||
|
|
||||||
|
[](https://crates.io/crates/flix-cli)
|
||||||
|
|
||||||
|
CLI for interacting with flix media
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
pub mod tmdb;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Use a custom config file [default: ~/.flix]
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
pub fn config_path(&self) -> PathBuf {
|
||||||
|
match self.config.as_ref() {
|
||||||
|
Some(path) => match path.strip_prefix("~/") {
|
||||||
|
Ok(path) => expect_home_dir().join(path),
|
||||||
|
Err(_) => path.to_owned(),
|
||||||
|
},
|
||||||
|
None => expect_home_dir().join(".flix"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(&self) -> &Command {
|
||||||
|
&self.command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Print a flix manifest
|
||||||
|
Print {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: BackendCommand,
|
||||||
|
},
|
||||||
|
/// Write a flix manifest if the destination does not exist
|
||||||
|
Write {
|
||||||
|
/// Overwrite the destination
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
force: bool,
|
||||||
|
|
||||||
|
/// Change the destination
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
output: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: BackendCommand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum BackendCommand {
|
||||||
|
/// Use the TMDB backend
|
||||||
|
Tmdb {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: tmdb::Command,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_home_dir() -> PathBuf {
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
home::home_dir().expect("you do not have a home directory")
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Process a TMDB collection
|
||||||
|
Collection {
|
||||||
|
#[arg(value_name = "TMDB_ID")]
|
||||||
|
id: i32,
|
||||||
|
},
|
||||||
|
/// Process a TMDB movie
|
||||||
|
Movie {
|
||||||
|
#[arg(value_name = "TMDB_ID")]
|
||||||
|
id: i32,
|
||||||
|
},
|
||||||
|
/// Process a TMDB show
|
||||||
|
Show {
|
||||||
|
#[arg(value_name = "TMDB_ID")]
|
||||||
|
id: i32,
|
||||||
|
},
|
||||||
|
/// Process a TMDB season
|
||||||
|
Season {
|
||||||
|
#[arg(value_name = "TMDB_ID")]
|
||||||
|
id: i32,
|
||||||
|
#[arg(value_name = "SEASON_NUM")]
|
||||||
|
season: i32,
|
||||||
|
},
|
||||||
|
/// Process a TMDB episode
|
||||||
|
Episode {
|
||||||
|
#[arg(value_name = "TMDB_ID")]
|
||||||
|
id: i32,
|
||||||
|
#[arg(value_name = "SEASON_NUM")]
|
||||||
|
season: i32,
|
||||||
|
#[arg(value_name = "EPISODE_NUM")]
|
||||||
|
episode: i32,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
tmdb: TmdbConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct TmdbConfig {
|
||||||
|
bearer_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn tmdb(&self) -> &TmdbConfig {
|
||||||
|
&self.tmdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TmdbConfig {
|
||||||
|
pub fn bearer_token(&self) -> &str {
|
||||||
|
&self.bearer_token
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use flix_tmdb::Client;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
use cli::{BackendCommand, Cli, Command};
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
use config::Config;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
mod run;
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let config = fs::read_to_string(cli.config_path())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not read config: {:?}", cli.config_path()))?;
|
||||||
|
let config: Config = toml::from_str(config.as_str())
|
||||||
|
.with_context(|| format!("could not parse config: {:?}", cli.config_path()))?;
|
||||||
|
|
||||||
|
let client = Client::new(config.tmdb().bearer_token().to_owned());
|
||||||
|
|
||||||
|
match cli.command() {
|
||||||
|
Command::Print { command } => match command {
|
||||||
|
BackendCommand::Tmdb { command } => {
|
||||||
|
let object = run::tmdb::TmdbObject::fetch(&client, command).await?;
|
||||||
|
println!("{}", object.serialize().context("failed to serialize")?)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Command::Write {
|
||||||
|
force,
|
||||||
|
output,
|
||||||
|
command,
|
||||||
|
} => match command {
|
||||||
|
BackendCommand::Tmdb { command } => {
|
||||||
|
let object = run::tmdb::TmdbObject::fetch(&client, command).await?;
|
||||||
|
let output = output
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.as_path())
|
||||||
|
.unwrap_or(object.default_filename());
|
||||||
|
|
||||||
|
let mut file = if *force {
|
||||||
|
fs::File::create(output).await
|
||||||
|
} else {
|
||||||
|
fs::File::create_new(output).await
|
||||||
|
}
|
||||||
|
.with_context(|| format!("could not create file at path {}", output.display()))?;
|
||||||
|
file.write_all(
|
||||||
|
object
|
||||||
|
.serialize()
|
||||||
|
.context("failed to serialize tmdb object")?
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not write to file at path {}", output.display()))?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod tmdb;
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use flix::model::{
|
||||||
|
Collection, Episode, GenericCollection, GenericEpisode, GenericMovie, GenericSeason,
|
||||||
|
GenericShow, Movie, Season, Show, TmdbCollection, TmdbMovie, TmdbShow,
|
||||||
|
};
|
||||||
|
use flix_tmdb::Client;
|
||||||
|
use flix_tmdb::model::{
|
||||||
|
Collection as TCollection, Episode as TEpisode, Movie as TMovie, Season as TSeason,
|
||||||
|
Show as TShow,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
|
use crate::cli::tmdb::Command;
|
||||||
|
|
||||||
|
pub enum TmdbObject {
|
||||||
|
Collection(TCollection),
|
||||||
|
Movie(TMovie),
|
||||||
|
Show(TShow),
|
||||||
|
Season(TSeason),
|
||||||
|
Episode(TEpisode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TmdbObject {
|
||||||
|
pub fn default_filename(&self) -> &'static Path {
|
||||||
|
Path::new(match self {
|
||||||
|
TmdbObject::Collection(_) => "collection.toml",
|
||||||
|
TmdbObject::Movie(_) => "movie.toml",
|
||||||
|
TmdbObject::Show(_) => "show.toml",
|
||||||
|
TmdbObject::Season(_) => "season.toml",
|
||||||
|
TmdbObject::Episode(_) => "episode.toml",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(self) -> Result<String> {
|
||||||
|
Ok(match self {
|
||||||
|
TmdbObject::Collection(tmdb) => toml::to_string(&Collection {
|
||||||
|
collection: GenericCollection {
|
||||||
|
name: tmdb.name,
|
||||||
|
overview: tmdb.overview,
|
||||||
|
},
|
||||||
|
tmdb: Some(TmdbCollection { id: tmdb.id }),
|
||||||
|
})?,
|
||||||
|
TmdbObject::Movie(tmdb) => toml::to_string(&Movie {
|
||||||
|
movie: GenericMovie {
|
||||||
|
title: tmdb.title,
|
||||||
|
overview: tmdb.overview,
|
||||||
|
release_date: tmdb.release_date,
|
||||||
|
},
|
||||||
|
tmdb: Some(TmdbMovie {
|
||||||
|
id: tmdb.id,
|
||||||
|
collection: tmdb.collection.map(|c| c.id),
|
||||||
|
genres: tmdb.genres.iter().map(|g| g.id).collect(),
|
||||||
|
}),
|
||||||
|
})?,
|
||||||
|
TmdbObject::Show(tmdb) => toml::to_string(&Show {
|
||||||
|
show: GenericShow {
|
||||||
|
name: tmdb.name,
|
||||||
|
overview: tmdb.overview,
|
||||||
|
air_date: tmdb.first_air_date,
|
||||||
|
},
|
||||||
|
tmdb: Some(TmdbShow {
|
||||||
|
id: tmdb.id,
|
||||||
|
genres: tmdb.genres.iter().map(|g| g.id).collect(),
|
||||||
|
}),
|
||||||
|
})?,
|
||||||
|
TmdbObject::Season(tmdb) => toml::to_string(&Season {
|
||||||
|
season: GenericSeason {
|
||||||
|
number: tmdb.season_number,
|
||||||
|
name: tmdb.name,
|
||||||
|
overview: tmdb.overview,
|
||||||
|
air_date: tmdb.air_date,
|
||||||
|
},
|
||||||
|
})?,
|
||||||
|
TmdbObject::Episode(tmdb) => toml::to_string(&Episode {
|
||||||
|
episode: GenericEpisode {
|
||||||
|
number: tmdb.episode_number,
|
||||||
|
name: tmdb.name,
|
||||||
|
overview: tmdb.overview,
|
||||||
|
air_date: tmdb.air_date,
|
||||||
|
},
|
||||||
|
})?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TmdbObject {
|
||||||
|
pub async fn fetch(client: &Client, command: &Command) -> Result<Self> {
|
||||||
|
Ok(match *command {
|
||||||
|
Command::Collection { id } => Self::Collection(
|
||||||
|
client
|
||||||
|
.collections()
|
||||||
|
.get_details(id, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not get collection details for '{id}'"))?,
|
||||||
|
),
|
||||||
|
Command::Movie { id } => Self::Movie(
|
||||||
|
client
|
||||||
|
.movies()
|
||||||
|
.get_details(id, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not get movie details for '{id}'"))?,
|
||||||
|
),
|
||||||
|
Command::Show { id } => Self::Show(
|
||||||
|
client
|
||||||
|
.shows()
|
||||||
|
.get_details(id, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not get show details for '{id}'"))?,
|
||||||
|
),
|
||||||
|
Command::Season { id, season } => Self::Season(
|
||||||
|
client
|
||||||
|
.seasons()
|
||||||
|
.get_details(id, season, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("could not get show details for '{id}' S{season}"))?,
|
||||||
|
),
|
||||||
|
Command::Episode {
|
||||||
|
id,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
} => Self::Episode(
|
||||||
|
client
|
||||||
|
.episodes()
|
||||||
|
.get_details(id, season, episode, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
format!("could not get show details for '{id}' S{season}E{episode}")
|
||||||
|
})?,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "flix"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
description = "Types for storing persistent data about media"
|
||||||
|
repository = "https://github.com/QuantumShade/flix"
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license-file.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
tmdb = ["dep:flix-tmdb"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
flix-tmdb = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
serde = { workspace = true, features = ["std", "derive"] }
|
||||||
|
thiserror = { workspace = true }
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# flix
|
||||||
|
|
||||||
|
[](https://crates.io/crates/flix)
|
||||||
|
|
||||||
|
A library providing types for storing persistent data about media
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//! flix provides types for storing persistent data about media
|
||||||
|
|
||||||
|
/// flix types
|
||||||
|
pub mod model;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
use flix_tmdb::model::CollectionId;
|
||||||
|
|
||||||
|
/// A Collection container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Collection {
|
||||||
|
/// Generic collection data
|
||||||
|
pub collection: GenericCollection,
|
||||||
|
|
||||||
|
/// TMDB collection data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
pub tmdb: Option<TmdbCollection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic collection data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericCollection {
|
||||||
|
/// The collection's name
|
||||||
|
pub name: String,
|
||||||
|
/// The collection's overview
|
||||||
|
pub overview: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The TMDB collection data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct TmdbCollection {
|
||||||
|
/// The collection's TMDB ID
|
||||||
|
pub id: CollectionId,
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
/// An Episode container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Episode {
|
||||||
|
/// The generic episode data
|
||||||
|
pub episode: GenericEpisode,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic episode data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericEpisode {
|
||||||
|
/// The episode's number
|
||||||
|
pub 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,13 @@
|
|||||||
|
mod collection;
|
||||||
|
mod episode;
|
||||||
|
mod movie;
|
||||||
|
mod season;
|
||||||
|
mod show;
|
||||||
|
mod verse;
|
||||||
|
|
||||||
|
pub use collection::*;
|
||||||
|
pub use episode::*;
|
||||||
|
pub use movie::*;
|
||||||
|
pub use season::*;
|
||||||
|
pub use show::*;
|
||||||
|
pub use verse::*;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
use flix_tmdb::model::{CollectionId, MovieGenreId, MovieId};
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
/// A Movie container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Movie {
|
||||||
|
/// The generic movie data
|
||||||
|
pub movie: GenericMovie,
|
||||||
|
|
||||||
|
/// The TMDB movie data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
pub tmdb: Option<TmdbMovie>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic movie data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericMovie {
|
||||||
|
/// The movie's title
|
||||||
|
pub title: String,
|
||||||
|
/// The movie's overview
|
||||||
|
pub overview: String,
|
||||||
|
/// The movie's release date
|
||||||
|
pub release_date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The TMDB movie data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct TmdbMovie {
|
||||||
|
/// The movie's TMDB ID
|
||||||
|
pub id: MovieId,
|
||||||
|
/// The movie's collection's TMDB ID
|
||||||
|
pub collection: Option<CollectionId>,
|
||||||
|
/// The list of genre TMDB IDs that the movie is associated with
|
||||||
|
pub genres: Vec<MovieGenreId>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
/// A Season container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Season {
|
||||||
|
/// The generic season data
|
||||||
|
pub season: GenericSeason,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic season data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericSeason {
|
||||||
|
/// The season's number
|
||||||
|
pub 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,
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
use flix_tmdb::model::{ShowGenreId, ShowId};
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
|
/// A Show container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Show {
|
||||||
|
/// The generic show data
|
||||||
|
pub show: GenericShow,
|
||||||
|
|
||||||
|
/// The TMDB show data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
pub tmdb: Option<TmdbShow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic show data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericShow {
|
||||||
|
/// The show's name
|
||||||
|
pub name: String,
|
||||||
|
/// The show's overview
|
||||||
|
pub overview: String,
|
||||||
|
/// The show's air date
|
||||||
|
pub air_date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The TMDB show data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct TmdbShow {
|
||||||
|
/// The show's TMDB ID
|
||||||
|
pub id: ShowId,
|
||||||
|
/// The list of genre TMDB IDs that the movie is associated with
|
||||||
|
pub genres: Vec<ShowGenreId>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
use flix_tmdb::model::{MovieId, ShowId};
|
||||||
|
|
||||||
|
/// A Verse container
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Verse {
|
||||||
|
/// The generic verse data
|
||||||
|
pub verse: GenericVerse,
|
||||||
|
|
||||||
|
/// The TMDB verse data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
pub tmdb: Option<TmdbVerse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The generic verse data
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct GenericVerse {
|
||||||
|
/// The verse's name
|
||||||
|
pub name: String,
|
||||||
|
/// The verse's overview
|
||||||
|
pub overview: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The TMDB verse data
|
||||||
|
#[cfg(feature = "tmdb")]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct TmdbVerse {
|
||||||
|
/// The list of movie TMDB IDs in the verse
|
||||||
|
pub movies: Vec<MovieId>,
|
||||||
|
/// The list of show TMDB IDs in the verse
|
||||||
|
pub shows: Vec<ShowId>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "flix-tmdb"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
description = "Clients and models for fetching data from TMDB"
|
||||||
|
repository = "https://github.com/QuantumShade/flix"
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license-file.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
url-macro = { workspace = true }
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# flix
|
||||||
|
|
||||||
|
[](https://crates.io/crates/flix)
|
||||||
|
|
||||||
|
A library providing clients and models for fetching data from TMDB
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
Reference in New Issue
Block a user