pub mod trakt;
pub use trakt::TraktProvider;
use async_trait::async_trait;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProviderError {
#[error("request failed: {0}")]
RequestError(#[from] reqwest::Error),
#[error("not authenticated")]
NotAuthenticated,
#[error("access denied")]
AccessDenied,
#[error("invalid response: {0}")]
InvalidResponse(String),
#[error("token expired")]
TokenExpired,
}
#[derive(Debug, Clone)]
pub struct TokenResponse {
pub access_token: String,
pub refresh_token: String,
pub expires_in: u64,
pub created_at: u64,
}
#[derive(Debug, Clone)]
pub struct DiscoveryItem {
pub id: u64,
pub provider_id: u64,
pub title: String,
pub year: Option<u16>,
pub media_type: String,
pub overview: Option<String>,
pub rating: Option<f64>,
pub released: Option<String>,
pub is_released: bool,
}
#[async_trait]
pub trait ContentProvider: Send + Sync {
fn name(&self) -> &str;
fn is_authenticated(&self) -> bool;
async fn trending_movies(&self, limit: usize) -> Result<Vec<DiscoveryItem>, ProviderError>;
async fn trending_shows(&self, limit: usize) -> Result<Vec<DiscoveryItem>, ProviderError>;
async fn watchlist(&self) -> Result<Vec<DiscoveryItem>, ProviderError>;
async fn calendar(&self, days: u32) -> Result<Vec<DiscoveryItem>, ProviderError>;
async fn recommendations(&self) -> Result<Vec<DiscoveryItem>, ProviderError>;
fn get_authorize_url(&self) -> String;
async fn exchange_code(
&self,
code: &str,
client_secret: &str,
) -> Result<TokenResponse, ProviderError>;
async fn refresh_token(
&self,
refresh_token: &str,
client_secret: &str,
) -> Result<TokenResponse, ProviderError>;
}
pub fn create_provider(
name: &str,
client_id: String,
access_token: Option<String>,
) -> Option<Box<dyn ContentProvider>> {
match name {
"trakt" => Some(Box::new(if let Some(token) = access_token {
TraktProvider::authenticated(client_id, token)
} else {
TraktProvider::new(client_id)
})),
_ => None,
}
}