use std::fmt::{Display, Formatter}; use chrono::{DateTime, Utc}; use clap::ValueEnum; use derivative::Derivative; use serde::{Deserialize, Serialize}; use serde_json::{json, Number, Value}; use strum::EnumIter; use crate::serde_enum_from; use super::{ radarr_models::IndexerTestResult, servarr_models::{ DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, RootFolder, SecurityConfig, Tag, Update, }, EnumDisplayStyle, HorizontallyScrollableText, Serdeable, }; #[cfg(test)] #[path = "sonarr_models_tests.rs"] mod sonarr_models_tests; #[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AddSeriesBody { pub tvdb_id: i64, pub title: String, pub monitored: bool, pub root_folder_path: String, pub quality_profile_id: i64, pub language_profile_id: i64, pub series_type: String, pub season_folder: bool, pub tags: Vec, pub add_options: AddSeriesOptions, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AddSeriesSearchResult { #[serde(deserialize_with = "super::from_i64")] pub tvdb_id: i64, pub title: HorizontallyScrollableText, pub status: Option, pub ended: bool, pub overview: Option, pub genres: Vec, #[serde(deserialize_with = "super::from_i64")] pub year: i64, pub network: Option, #[serde(deserialize_with = "super::from_i64")] pub runtime: i64, pub ratings: Option, pub statistics: Option, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AddSeriesSearchResultStatistics { #[serde(deserialize_with = "super::from_i64")] pub season_count: i64, } #[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AddSeriesOptions { pub monitor: String, pub search_for_cutoff_unmet_episodes: bool, pub search_for_missing_episodes: bool, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct BlocklistItem { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub series_id: i64, #[serde(skip_serializing_if = "Option::is_none")] pub series_title: Option, pub episode_ids: Vec, pub source_title: String, pub language: Language, pub quality: QualityWrapper, pub date: DateTime, pub protocol: String, pub indexer: String, pub message: String, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct BlocklistResponse { pub records: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub struct DeleteSeriesParams { pub id: i64, pub delete_series_files: bool, pub add_list_exclusion: bool, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DownloadRecord { pub title: String, pub status: DownloadStatus, #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_id: i64, #[serde(deserialize_with = "super::from_f64")] pub size: f64, #[serde(deserialize_with = "super::from_f64")] pub sizeleft: f64, pub output_path: Option, #[serde(default)] pub indexer: String, pub download_client: String, } impl Eq for DownloadRecord {} #[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter)] #[serde(rename_all = "camelCase")] pub enum DownloadStatus { #[default] Unknown, Queued, Paused, Downloading, Completed, Failed, Warning, Delay, DownloadClientUnavailable, Fallback, } impl Display for DownloadStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let download_status = match self { DownloadStatus::Unknown => "unknown", DownloadStatus::Queued => "queued", DownloadStatus::Paused => "paused", DownloadStatus::Downloading => "downloading", DownloadStatus::Completed => "completed", DownloadStatus::Failed => "failed", DownloadStatus::Warning => "warning", DownloadStatus::Delay => "delay", DownloadStatus::DownloadClientUnavailable => "downloadClientUnavailable", DownloadStatus::Fallback => "fallback", }; write!(f, "{download_status}") } } impl<'a> EnumDisplayStyle<'a> for DownloadStatus { fn to_display_str(self) -> &'a str { match self { DownloadStatus::Unknown => "Unknown", DownloadStatus::Queued => "Queued", DownloadStatus::Paused => "Paused", DownloadStatus::Downloading => "Downloading", DownloadStatus::Completed => "Completed", DownloadStatus::Failed => "Failed", DownloadStatus::Warning => "Warning", DownloadStatus::Delay => "Delay", DownloadStatus::DownloadClientUnavailable => "Download Client Unavailable", DownloadStatus::Fallback => "Fallback", } } } #[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct DownloadsResponse { pub records: Vec, } #[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct EditSeriesParams { pub series_id: i64, pub monitored: Option, pub use_season_folders: Option, pub quality_profile_id: Option, pub language_profile_id: Option, pub series_type: Option, pub root_folder_path: Option, pub tags: Option>, pub clear_tags: bool, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Episode { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub series_id: i64, #[serde(deserialize_with = "super::from_i64")] pub tvdb_id: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_file_id: i64, #[serde(deserialize_with = "super::from_i64")] pub season_number: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_number: i64, pub title: String, pub air_date_utc: Option>, pub overview: Option, pub has_file: bool, pub monitored: bool, pub episode_file: Option, } impl Display for Episode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.title) } } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct EpisodeFile { #[serde(deserialize_with = "super::from_i64")] pub id: i64, pub relative_path: String, pub path: String, #[serde(deserialize_with = "super::from_i64")] pub size: i64, pub language: Language, pub quality: QualityWrapper, pub date_added: DateTime, pub media_info: Option, } #[derive(Default, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IndexerSettings { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub minimum_age: i64, #[serde(deserialize_with = "super::from_i64")] pub retention: i64, #[serde(deserialize_with = "super::from_i64")] pub maximum_size: i64, #[serde(deserialize_with = "super::from_i64")] pub rss_sync_interval: i64, } #[derive(Serialize, Deserialize, Derivative, Debug, Clone, PartialEq, Eq)] #[derivative(Default)] #[serde(rename_all = "camelCase")] pub struct MediaInfo { #[serde(deserialize_with = "super::from_i64")] pub audio_bitrate: i64, #[derivative(Default(value = "Number::from(0)"))] pub audio_channels: Number, pub audio_codec: Option, pub audio_languages: Option, #[serde(deserialize_with = "super::from_i64")] pub audio_stream_count: i64, #[serde(deserialize_with = "super::from_i64")] pub video_bit_depth: i64, #[serde(deserialize_with = "super::from_i64")] pub video_bitrate: i64, pub video_codec: String, #[derivative(Default(value = "Number::from(0)"))] pub video_fps: Number, pub resolution: String, pub run_time: String, pub scan_type: String, pub subtitles: Option, } #[derive(Derivative, Serialize, Deserialize, Debug, Clone, PartialEq)] #[derivative(Default)] pub struct Rating { #[serde(deserialize_with = "super::from_i64")] pub votes: i64, #[serde(deserialize_with = "super::from_f64")] pub value: f64, } impl Eq for Rating {} #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Season { pub title: Option, #[serde(deserialize_with = "super::from_i64")] pub season_number: i64, pub monitored: bool, pub statistics: SeasonStatistics, } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SeasonStatistics { pub next_airing: Option>, pub previous_airing: Option>, #[serde(deserialize_with = "super::from_i64")] pub episode_file_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub total_episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub size_on_disk: i64, #[serde(deserialize_with = "super::from_f64")] pub percent_of_episodes: f64, } impl Eq for SeasonStatistics {} #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Series { #[serde(deserialize_with = "super::from_i64")] pub id: i64, #[serde(deserialize_with = "super::from_i64")] pub tvdb_id: i64, pub title: HorizontallyScrollableText, #[serde(deserialize_with = "super::from_i64")] pub quality_profile_id: i64, #[serde(deserialize_with = "super::from_i64")] pub language_profile_id: i64, #[serde(deserialize_with = "super::from_i64")] pub runtime: i64, #[serde(deserialize_with = "super::from_i64")] pub year: i64, pub monitored: bool, pub series_type: SeriesType, pub path: String, pub genres: Vec, pub tags: Vec, pub ratings: Rating, pub ended: bool, pub status: SeriesStatus, pub overview: Option, pub network: Option, pub season_folder: bool, pub certification: Option, pub statistics: Option, pub seasons: Option>, } #[derive( Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, )] #[serde(rename_all = "camelCase")] pub enum SeriesMonitor { #[default] All, Unknown, Future, Missing, Existing, FirstSeason, LastSeason, LatestSeason, Pilot, Recent, MonitorSpecials, UnmonitorSpecials, None, Skip, } impl Display for SeriesMonitor { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let series_monitor = match self { SeriesMonitor::Unknown => "unknown", SeriesMonitor::All => "all", SeriesMonitor::Future => "future", SeriesMonitor::Missing => "missing", SeriesMonitor::Existing => "existing", SeriesMonitor::FirstSeason => "firstSeason", SeriesMonitor::LastSeason => "lastSeason", SeriesMonitor::LatestSeason => "latestSeason", SeriesMonitor::Pilot => "pilot", SeriesMonitor::Recent => "recent", SeriesMonitor::MonitorSpecials => "monitorSpecials", SeriesMonitor::UnmonitorSpecials => "unmonitorSpecials", SeriesMonitor::None => "none", SeriesMonitor::Skip => "skip", }; write!(f, "{series_monitor}") } } impl<'a> EnumDisplayStyle<'a> for SeriesMonitor { fn to_display_str(self) -> &'a str { match self { SeriesMonitor::Unknown => "Unknown", SeriesMonitor::All => "All Episodes", SeriesMonitor::Future => "Future Episodes", SeriesMonitor::Missing => "Missing Episodes", SeriesMonitor::Existing => "Existing Episodes", SeriesMonitor::FirstSeason => "Only First Season", SeriesMonitor::LastSeason => "Only Last Season", SeriesMonitor::LatestSeason => "Only Latest Season", SeriesMonitor::Pilot => "Pilot Episode", SeriesMonitor::Recent => "Recent Episodes", SeriesMonitor::MonitorSpecials => "Only Specials", SeriesMonitor::UnmonitorSpecials => "Not Specials", SeriesMonitor::None => "None", SeriesMonitor::Skip => "Skip", } } } #[derive( Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, )] #[serde(rename_all = "camelCase")] pub enum SeriesType { #[default] Standard, Daily, Anime, } impl Display for SeriesType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let series_type = match self { SeriesType::Standard => "standard", SeriesType::Daily => "daily", SeriesType::Anime => "anime", }; write!(f, "{series_type}") } } impl<'a> EnumDisplayStyle<'a> for SeriesType { fn to_display_str(self) -> &'a str { match self { SeriesType::Standard => "Standard", SeriesType::Daily => "Daily", SeriesType::Anime => "Anime", } } } #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SeriesStatistics { #[serde(deserialize_with = "super::from_i64")] pub season_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_file_count: i64, #[serde(deserialize_with = "super::from_i64")] pub episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub total_episode_count: i64, #[serde(deserialize_with = "super::from_i64")] pub size_on_disk: i64, #[serde(deserialize_with = "super::from_f64")] pub percent_of_episodes: f64, } impl Eq for SeriesStatistics {} #[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter)] #[serde(rename_all = "camelCase")] pub enum SeriesStatus { #[default] Continuing, Ended, Upcoming, Deleted, } impl Display for SeriesStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let series_status = match self { SeriesStatus::Continuing => "continuing", SeriesStatus::Ended => "ended", SeriesStatus::Upcoming => "upcoming", SeriesStatus::Deleted => "deleted", }; write!(f, "{series_status}") } } impl<'a> EnumDisplayStyle<'a> for SeriesStatus { fn to_display_str(self) -> &'a str { match self { SeriesStatus::Continuing => "Continuing", SeriesStatus::Ended => "Ended", SeriesStatus::Upcoming => "Upcoming", SeriesStatus::Deleted => "Deleted", } } } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryWrapper { pub records: Vec, } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryData { pub dropped_path: Option, pub imported_path: Option, pub indexer: Option, pub release_group: Option, pub series_match_type: Option, pub nzb_info_url: Option, pub download_client_name: Option, pub age: Option, pub published_date: Option>, pub message: Option, pub reason: Option, pub source_path: Option, pub source_relative_path: Option, pub path: Option, pub relative_path: Option, } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum SonarrHistoryEventType { #[default] Unknown, Grabbed, SeriesFolderImported, DownloadFolderImported, DownloadFailed, EpisodeFileDeleted, EpisodeFileRenamed, DownloadIgnored, } impl Display for SonarrHistoryEventType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let event_type = match self { SonarrHistoryEventType::Unknown => "unknown", SonarrHistoryEventType::Grabbed => "grabbed", SonarrHistoryEventType::SeriesFolderImported => "seriesFolderImported", SonarrHistoryEventType::DownloadFolderImported => "downloadFolderImported", SonarrHistoryEventType::DownloadFailed => "downloadFailed", SonarrHistoryEventType::EpisodeFileDeleted => "episodeFileDeleted", SonarrHistoryEventType::EpisodeFileRenamed => "episodeFileRenamed", SonarrHistoryEventType::DownloadIgnored => "downloadIgnored", }; write!(f, "{event_type}") } } impl<'a> EnumDisplayStyle<'a> for SonarrHistoryEventType { fn to_display_str(self) -> &'a str { match self { SonarrHistoryEventType::Unknown => "Unknown", SonarrHistoryEventType::Grabbed => "Grabbed", SonarrHistoryEventType::SeriesFolderImported => "Series Folder Imported", SonarrHistoryEventType::DownloadFolderImported => "Download Folder Imported", SonarrHistoryEventType::DownloadFailed => "Download Failed", SonarrHistoryEventType::EpisodeFileDeleted => "Episode File Deleted", SonarrHistoryEventType::EpisodeFileRenamed => "Episode File Renamed", SonarrHistoryEventType::DownloadIgnored => "Download Ignored", } } } #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrHistoryItem { #[serde(deserialize_with = "super::from_i64")] pub id: i64, pub source_title: HorizontallyScrollableText, #[serde(deserialize_with = "super::from_i64")] pub episode_id: i64, pub quality: QualityWrapper, pub language: Language, pub date: DateTime, pub event_type: SonarrHistoryEventType, pub data: SonarrHistoryData, } #[derive(Default, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct SonarrCommandBody { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub series_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub season_number: Option, #[serde(skip_serializing_if = "Option::is_none")] pub episode_ids: Option>, } #[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[serde(default)] pub struct SonarrRelease { pub guid: String, pub protocol: String, #[serde(deserialize_with = "super::from_i64")] pub age: i64, pub title: HorizontallyScrollableText, pub indexer: String, #[serde(deserialize_with = "super::from_i64")] pub indexer_id: i64, #[serde(deserialize_with = "super::from_i64")] pub size: i64, pub rejected: bool, pub rejections: Option>, pub seeders: Option, pub leechers: Option, pub languages: Option>, pub quality: QualityWrapper, pub full_season: bool, } #[derive(Default, Serialize, Debug, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct SonarrReleaseDownloadBody { pub guid: String, pub indexer_id: i64, #[serde(skip_serializing_if = "Option::is_none")] pub series_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub episode_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub season_number: Option, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SonarrTask { pub name: String, pub task_name: SonarrTaskName, #[serde(deserialize_with = "super::from_i64")] pub interval: i64, pub last_execution: DateTime, pub next_execution: DateTime, } #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Copy, ValueEnum)] #[serde(rename_all = "PascalCase")] pub enum SonarrTaskName { #[default] ApplicationUpdateCheck, Backup, CheckHealth, CleanUpRecycleBin, Housekeeping, ImportListSync, MessagingCleanup, RefreshMonitoredDownloads, RefreshSeries, RssSync, UpdateSceneMapping, } impl Display for SonarrTaskName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let task_name = serde_json::to_string(&self) .expect("Unable to serialize task name") .replace('"', ""); write!(f, "{task_name}") } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum SonarrSerdeable { AddSeriesSearchResults(Vec), BlocklistResponse(BlocklistResponse), DownloadsResponse(DownloadsResponse), DiskSpaces(Vec), Episode(Episode), Episodes(Vec), EpisodeFiles(Vec), HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), IndexerTestResults(Vec), LanguageProfiles(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), Releases(Vec), RootFolders(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), Series(Series), SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), Tag(Tag), Tags(Vec), Tasks(Vec), Updates(Vec), Value(Value), } impl From for Serdeable { fn from(value: SonarrSerdeable) -> Serdeable { Serdeable::Sonarr(value) } } impl From<()> for SonarrSerdeable { fn from(_: ()) -> Self { SonarrSerdeable::Value(json!({})) } } serde_enum_from!( SonarrSerdeable { AddSeriesSearchResults(Vec), BlocklistResponse(BlocklistResponse), DownloadsResponse(DownloadsResponse), DiskSpaces(Vec), Episode(Episode), Episodes(Vec), EpisodeFiles(Vec), HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), IndexerTestResults(Vec), LanguageProfiles(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), Releases(Vec), RootFolders(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), Series(Series), SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), Tag(Tag), Tags(Vec), Tasks(Vec), Updates(Vec), Value(Value), } ); #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SystemStatus { pub version: String, pub start_time: DateTime, }