From 77a36cb8683c4b2b58d65666a12832cec47db40c Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:18:26 +0200 Subject: [PATCH 01/23] Refactoring Move errors to a seperate file Replace &String with &str in imgur.rs and jellyfin.rs Change how Content struct is created --- src/config.rs | 33 +----------- src/error.rs | 65 +++++++++++++++++++++++ src/main.rs | 1 + src/services/imgur.rs | 47 +++-------------- src/services/jellyfin.rs | 111 ++++++++++++++++++++++----------------- 5 files changed, 138 insertions(+), 119 deletions(-) create mode 100644 src/error.rs diff --git a/src/config.rs b/src/config.rs index b1ee432..8828635 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::error::ConfigError; use crate::services::jellyfin::MediaType; use colored::Colorize; use std::env; @@ -26,38 +27,6 @@ pub struct Images { pub imgur: bool, } -#[derive(Debug)] -pub enum ConfigError { - MissingConfig(String), - Io(String), - Json(String), - VarError(String), -} - -impl From<&'static str> for ConfigError { - fn from(value: &'static str) -> Self { - Self::MissingConfig(value.to_string()) - } -} - -impl From for ConfigError { - fn from(value: std::io::Error) -> Self { - Self::Io(format!("Unable to open file: {}", value)) - } -} - -impl From for ConfigError { - fn from(value: serde_json::Error) -> Self { - Self::Json(format!("Unable to parse config: {}", value)) - } -} - -impl From for ConfigError { - fn from(value: env::VarError) -> Self { - Self::VarError(format!("Unable to get environment variables: {}", value)) - } -} - pub fn get_config_path() -> Result { if cfg!(not(windows)) { let user = env::var("USER")?; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..4ea581f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,65 @@ +use std::env; + +#[derive(Debug)] +pub enum ConfigError { + MissingConfig(String), + Io(String), + Json(String), + VarError(String), +} + +impl From<&'static str> for ConfigError { + fn from(value: &'static str) -> Self { + Self::MissingConfig(value.to_string()) + } +} + +impl From for ConfigError { + fn from(value: std::io::Error) -> Self { + Self::Io(format!("Unable to open file: {}", value)) + } +} + +impl From for ConfigError { + fn from(value: serde_json::Error) -> Self { + Self::Json(format!("Unable to parse config: {}", value)) + } +} + +impl From for ConfigError { + fn from(value: env::VarError) -> Self { + Self::VarError(format!("Unable to get environment variables: {}", value)) + } +} + +#[derive(Debug)] +pub enum ImgurError { + Reqwest(String), + Io(String), + Json(String), + VarError(String), +} + +impl From for ImgurError { + fn from(value: reqwest::Error) -> Self { + Self::Reqwest(format!("Error uploading image: {}", value)) + } +} + +impl From for ImgurError { + fn from(value: std::io::Error) -> Self { + Self::Io(format!("Unable to open file: {}", value)) + } +} + +impl From for ImgurError { + fn from(value: serde_json::Error) -> Self { + Self::Json(format!("Unable to parse urls: {}", value)) + } +} + +impl From for ImgurError { + fn from(value: env::VarError) -> Self { + Self::VarError(format!("Unable to get environment variables: {}", value)) + } +} diff --git a/src/main.rs b/src/main.rs index fd76346..43a77e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ pub use crate::services::imgur::*; pub use crate::services::jellyfin::*; pub mod config; pub use crate::config::*; +pub mod error; use clap::Parser; use colored::Colorize; use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient}; diff --git a/src/services/imgur.rs b/src/services/imgur.rs index 4ca5067..a7635ef 100644 --- a/src/services/imgur.rs +++ b/src/services/imgur.rs @@ -1,3 +1,4 @@ +use crate::error::ImgurError; use serde_json::Value; use std::env; use std::io::Write; @@ -17,38 +18,6 @@ pub struct Imgur { pub url: String, } -#[derive(Debug)] -pub enum ImgurError { - Reqwest(String), - Io(String), - Json(String), - VarError(String), -} - -impl From for ImgurError { - fn from(value: reqwest::Error) -> Self { - Self::Reqwest(format!("Error uploading image: {}", value)) - } -} - -impl From for ImgurError { - fn from(value: std::io::Error) -> Self { - Self::Io(format!("Unable to open file: {}", value)) - } -} - -impl From for ImgurError { - fn from(value: serde_json::Error) -> Self { - Self::Json(format!("Unable to parse urls: {}", value)) - } -} - -impl From for ImgurError { - fn from(value: env::VarError) -> Self { - Self::VarError(format!("Unable to get environment variables: {}", value)) - } -} - pub fn get_urls_path() -> Result { if cfg!(not(windows)) { let user = env::var("USER")?; @@ -67,9 +36,9 @@ pub fn get_urls_path() -> Result { impl Imgur { pub async fn get( - image_url: &String, - item_id: &String, - client_id: &String, + image_url: &str, + item_id: &str, + client_id: &str, image_urls_file: Option, ) -> Result { let file = image_urls_file.unwrap_or_else(|| get_urls_path().unwrap()); @@ -102,9 +71,9 @@ impl Imgur { async fn write_file( file: String, - image_url: &String, - item_id: &String, - client_id: &String, + image_url: &str, + item_id: &str, + client_id: &str, json: &mut Value, ) -> Result { let mut new_data = serde_json::Map::new(); @@ -118,7 +87,7 @@ impl Imgur { Ok(imgur_url) } - async fn upload(image_url: &String, client_id: &String) -> Result { + async fn upload(image_url: &str, client_id: &str) -> Result { let img = reqwest::get(image_url).await?.bytes().await?; let client = reqwest::Client::new(); let response = client diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 7e8ef73..5bf1d5f 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -18,8 +18,8 @@ pub struct Content { impl Content { pub async fn get( url: &str, - api_key: &String, - username: &String, + api_key: &str, + username: &str, enable_images: &bool, ) -> Result { let sessions: Vec = serde_json::from_str( @@ -51,29 +51,24 @@ impl Content { let now_playing_item = &session["NowPlayingItem"]; - let external_services = ExternalServices::get(now_playing_item).await; + let mut content = Content::watching(now_playing_item).await; - let main = Content::watching(now_playing_item).await; + content.external_services = ExternalServices::get(now_playing_item).await; + + content.endtime = Content::time_left(now_playing_item, &session).await; let mut image_url: String = "".to_string(); if enable_images == &true { - image_url = Content::image(url, main[3].clone()).await; + image_url = Content::image(url, content.item_id.clone()).await; } + content.image_url = image_url; - return Ok(Self { - media_type: main[0].clone().into(), - details: main[1].clone(), - state_message: main[2].clone(), - endtime: Content::time_left(now_playing_item, &session).await, - image_url, - item_id: main[3].clone(), - external_services, - }); + return Ok(content); } Ok(Self::default()) } - async fn watching(now_playing_item: &Value) -> Vec { + async fn watching(now_playing_item: &Value) -> Self { /* FIXME: Update this explanation/remove it. @@ -87,11 +82,11 @@ impl Content { Then we send it off as a Vec with the external urls and the end timer to the main loop. */ let name = now_playing_item["Name"].as_str().unwrap(); - let item_type: String; + let media_type: MediaType; let item_id: String; let mut genres = "".to_string(); if now_playing_item["Type"].as_str().unwrap() == "Episode" { - item_type = "episode".to_owned(); + media_type = MediaType::Episode; let series_name = now_playing_item["SeriesName"].as_str().unwrap().to_string(); item_id = now_playing_item["SeriesId"].as_str().unwrap().to_string(); @@ -105,9 +100,15 @@ impl Content { msg += &(" ".to_string() + name); - vec![item_type, series_name, msg, item_id] + Self { + media_type, + details: series_name, + state_message: msg, + item_id, + ..Default::default() + } } else if now_playing_item["Type"].as_str().unwrap() == "Movie" { - item_type = "movie".to_owned(); + media_type = MediaType::Movie; item_id = now_playing_item["Id"].as_str().unwrap().to_string(); match now_playing_item.get("Genres") { None => (), @@ -123,43 +124,57 @@ impl Content { } }; - vec![item_type, name.to_string(), genres, item_id] + Self { + media_type, + details: name.into(), + state_message: genres, + item_id, + ..Default::default() + } } else if now_playing_item["Type"].as_str().unwrap() == "Audio" { - item_type = "music".to_owned(); + media_type = MediaType::Music; item_id = now_playing_item["AlbumId"].as_str().unwrap().to_string(); let artist = now_playing_item["AlbumArtist"].as_str().unwrap(); - match now_playing_item.get("Genres") { - None => (), + let msg = match now_playing_item.get("Genres") { + None => format!("By {}", artist), genre_array => { - genres.push_str(" - "); - genres += &genre_array - .unwrap() - .as_array() - .unwrap() - .iter() - .map(|x| x.as_str().unwrap().to_string()) - .collect::>() - .join(", "); + format!( + "{} - {}", + artist, + &genre_array + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + .join(", ") + ) } }; - let msg = format!("By {}{}", artist, genres); - - vec![item_type, name.to_string(), msg, item_id] + Self { + media_type, + details: name.into(), + state_message: msg, + item_id, + ..Default::default() + } } else if now_playing_item["Type"].as_str().unwrap() == "TvChannel" { - item_type = "livetv".to_owned(); + media_type = MediaType::LiveTv; item_id = now_playing_item["Id"].as_str().unwrap().to_string(); let msg = "Live TV".to_string(); - vec![item_type, name.to_string(), msg, item_id] + Self { + media_type, + details: name.into(), + state_message: msg, + item_id, + ..Default::default() + } } else { - // Return 4 empty strings to make vector equal length - vec![ - "".to_string(), - "".to_string(), - "".to_string(), - "".to_string(), - ] + // Return empty struct + Self::default() } } @@ -216,8 +231,8 @@ impl ExternalServices { i.get("Url").and_then(Value::as_str), ) { external_services.push(Self { - name: name.to_string(), - url: url.to_string(), + name: name.into(), + url: url.into(), }); if external_services.len() == 2 { break; @@ -259,14 +274,14 @@ impl Default for MediaType { impl MediaType { pub fn is_none(&self) -> bool { - if self == &MediaType::None { + if self == &Self::None { return true; } false } pub fn equal_to(&self, value: String) -> bool { - self == &MediaType::from(value) + self == &Self::from(value) } } From 06ac3b77fdb037dfa6774ba5156db4946a654263 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:37:08 +0200 Subject: [PATCH 02/23] builder pattern ish.. --- Cargo.toml | 2 +- src/services/jellyfin.rs | 137 +++++++++++++++++++++++---------------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90612ec..be12825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jellyfin-rpc" -version = "0.11.2" +version = "0.12.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 5bf1d5f..43becba 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -4,6 +4,63 @@ use serde_json::Value; TODO: Comments */ +#[derive(Default, Clone)] +struct ContentBuilder { + media_type: MediaType, + details: String, + state_message: String, + endtime: Option, + image_url: String, + item_id: String, + external_services: Vec +} + +impl ContentBuilder { + fn new() -> Self { + Self::default() + } + + fn media_type(&mut self, media_type: MediaType) { + self.media_type = media_type; + } + + fn details(&mut self, details: String) { + self.details = details; + } + + fn state_message(&mut self, state_message: String) { + self.state_message = state_message; + } + + fn endtime(&mut self, endtime: Option) { + self.endtime = endtime; + } + + fn image_url(&mut self, image_url: String) { + self.image_url = image_url; + } + + fn item_id(&mut self, item_id: String) { + self.item_id = item_id; + } + + fn external_services(&mut self, external_services: Vec) { + self.external_services = external_services; + } + + pub fn build(self) -> Content { + Content { + media_type: self.media_type, + details: self.details, + state_message: self.state_message, + endtime: self.endtime, + image_url: self.image_url, + item_id: self.item_id, + external_services: self.external_services, + } + } +} + #[derive(Default)] pub struct Content { pub media_type: MediaType, @@ -49,26 +106,26 @@ impl Content { continue; } - let now_playing_item = &session["NowPlayingItem"]; - - let mut content = Content::watching(now_playing_item).await; + let mut content = ContentBuilder::new(); - content.external_services = ExternalServices::get(now_playing_item).await; + let now_playing_item = &session["NowPlayingItem"]; - content.endtime = Content::time_left(now_playing_item, &session).await; + Content::watching(&mut content, now_playing_item).await; let mut image_url: String = "".to_string(); if enable_images == &true { image_url = Content::image(url, content.item_id.clone()).await; } - content.image_url = image_url; + content.external_services(ExternalServices::get(now_playing_item).await); + content.endtime(Content::time_left(now_playing_item, &session).await); + content.image_url(image_url); - return Ok(content); + return Ok(content.build()); } Ok(Self::default()) } - async fn watching(now_playing_item: &Value) -> Self { + async fn watching(content: &mut ContentBuilder, now_playing_item: &Value) { /* FIXME: Update this explanation/remove it. @@ -82,14 +139,8 @@ impl Content { Then we send it off as a Vec with the external urls and the end timer to the main loop. */ let name = now_playing_item["Name"].as_str().unwrap(); - let media_type: MediaType; - let item_id: String; let mut genres = "".to_string(); if now_playing_item["Type"].as_str().unwrap() == "Episode" { - media_type = MediaType::Episode; - let series_name = now_playing_item["SeriesName"].as_str().unwrap().to_string(); - item_id = now_playing_item["SeriesId"].as_str().unwrap().to_string(); - let season = now_playing_item["ParentIndexNumber"].to_string(); let first_episode_number = now_playing_item["IndexNumber"].to_string(); let mut msg = "S".to_owned() + &season + "E" + &first_episode_number; @@ -99,17 +150,11 @@ impl Content { } msg += &(" ".to_string() + name); - - Self { - media_type, - details: series_name, - state_message: msg, - item_id, - ..Default::default() - } + content.media_type(MediaType::Episode); + content.details(now_playing_item["SeriesName"].as_str().unwrap().to_string()); + content.state_message(msg); + content.item_id(now_playing_item["SeriesId"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "Movie" { - media_type = MediaType::Movie; - item_id = now_playing_item["Id"].as_str().unwrap().to_string(); match now_playing_item.get("Genres") { None => (), genre_array => { @@ -124,16 +169,11 @@ impl Content { } }; - Self { - media_type, - details: name.into(), - state_message: genres, - item_id, - ..Default::default() - } + content.media_type(MediaType::Movie); + content.details(name.into()); + content.state_message(genres); + content.item_id(now_playing_item["Id"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "Audio" { - media_type = MediaType::Music; - item_id = now_playing_item["AlbumId"].as_str().unwrap().to_string(); let artist = now_playing_item["AlbumArtist"].as_str().unwrap(); let msg = match now_playing_item.get("Genres") { None => format!("By {}", artist), @@ -153,28 +193,15 @@ impl Content { } }; - Self { - media_type, - details: name.into(), - state_message: msg, - item_id, - ..Default::default() - } + content.media_type(MediaType::Music); + content.details(name.into()); + content.state_message(msg); + content.item_id(now_playing_item["AlbumId"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "TvChannel" { - media_type = MediaType::LiveTv; - item_id = now_playing_item["Id"].as_str().unwrap().to_string(); - let msg = "Live TV".to_string(); - - Self { - media_type, - details: name.into(), - state_message: msg, - item_id, - ..Default::default() - } - } else { - // Return empty struct - Self::default() + content.media_type(MediaType::LiveTv); + content.details(name.into()); + content.state_message("Live TV".into()); + content.item_id(now_playing_item["Id"].as_str().unwrap().to_string()); } } @@ -209,7 +236,7 @@ impl Content { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ExternalServices { pub name: String, pub url: String, From 46ee12e365427c1afb7336518dc75c0ed6c3bbb5 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:35:03 +0200 Subject: [PATCH 03/23] Builder pattern part 2 --- src/config.rs | 168 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 109 insertions(+), 59 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8828635..d011bb6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,79 @@ use std::env; TODO: Comments */ +#[derive(Default)] +struct ConfigBuilder { + url: String, + api_key: String, + username: String, + blacklist: Blacklist, + rpc_client_id: String, + imgur_client_id: String, + images: Images, +} + +impl ConfigBuilder { + fn new() -> Self { + Self::default() + } + + fn url(&mut self, url: String) { + self.url = url; + } + + fn api_key(&mut self, api_key: String) { + self.api_key = api_key; + } + + fn username(&mut self, username: String) { + self.username = username; + } + + fn blacklist(&mut self, types: Vec, libraries: Vec) { + self.blacklist = Blacklist { types, libraries }; + } + + fn rpc_client_id(&mut self, rpc_client_id: String) { + self.rpc_client_id = rpc_client_id; + } + + fn imgur_client_id(&mut self, imgur_client_id: String) { + self.imgur_client_id = imgur_client_id; + } + + fn images(&mut self, enabled: bool, imgur: bool) { + self.images = Images { enabled, imgur }; + } + + fn build(self) -> Result { + match ( + self.url.is_empty(), + self.api_key.is_empty(), + self.username.is_empty(), + self.rpc_client_id.is_empty(), + (self.images.imgur, self.imgur_client_id.is_empty()), + ) { + (true, _, _, _, _) => Err(ConfigError::from("Jellyfin URL is empty!")), + (_, true, _, _, _) => Err(ConfigError::from("Jellyfin API key is empty!")), + (_, _, true, _, _) => Err(ConfigError::from("Jellyfin Username is empty!")), + (_, _, _, true, _) => Err(ConfigError::from("Discord Application ID is empty!")), + (_, _, _, _, (true, true)) => Err(ConfigError::from( + "Imgur Client ID is empty but Imgur images are enabled!", + )), + (false, false, false, false, _) => Ok(Config { + url: self.url, + api_key: self.api_key, + username: self.username, + blacklist: self.blacklist, + rpc_client_id: self.rpc_client_id, + imgur_client_id: self.imgur_client_id, + images: self.images + }, + ), + } + } +} + pub struct Config { pub url: String, pub api_key: String, @@ -17,11 +90,13 @@ pub struct Config { pub images: Images, } +#[derive(Default)] pub struct Blacklist { pub types: Vec, pub libraries: Vec, } +#[derive(Default)] pub struct Images { pub enabled: bool, pub imgur: bool, @@ -45,6 +120,7 @@ pub fn get_config_path() -> Result { impl Config { pub fn load_config(path: String) -> Result { + let mut config = ConfigBuilder::new(); let data = std::fs::read_to_string(path)?; let res: serde_json::Value = serde_json::from_str(&data)?; @@ -53,9 +129,9 @@ impl Config { let imgur: serde_json::Value = res["Imgur"].clone(); let images: serde_json::Value = res["Images"].clone(); - let url = jellyfin["URL"].as_str().unwrap_or("").to_string(); - let api_key = jellyfin["API_KEY"].as_str().unwrap_or("").to_string(); - let username = jellyfin["USERNAME"].as_str().unwrap_or("").to_string(); + config.url(jellyfin["URL"].as_str().unwrap_or("").to_string()); + config.api_key(jellyfin["API_KEY"].as_str().unwrap_or("").to_string()); + config.username(jellyfin["USERNAME"].as_str().unwrap_or("").to_string()); let mut type_blacklist: Vec = vec![MediaType::None]; if !Option::is_none(&jellyfin["TYPE_BLACKLIST"].get(0)) { type_blacklist.pop(); @@ -90,65 +166,39 @@ impl Config { ) }); } - let rpc_client_id = discord["APPLICATION_ID"] + config.blacklist(type_blacklist, library_blacklist); + config.rpc_client_id(discord["APPLICATION_ID"] .as_str() .unwrap_or("1053747938519679018") - .to_string(); - - let imgur_client_id = imgur["CLIENT_ID"].as_str().unwrap_or("").to_string(); - - let enable_images = images["ENABLE_IMAGES"].as_bool().unwrap_or_else(|| { - eprintln!( - "{}\n{} {} {} {}", - "ENABLE_IMAGES has to be a bool...".red().bold(), - "EXAMPLE:".bold(), - "true".bright_green().bold(), - "not".bold(), - "'true'".red().bold() - ); - std::process::exit(2) - }); - let imgur_images = images["IMGUR_IMAGES"].as_bool().unwrap_or_else(|| { - eprintln!( - "{}\n{} {} {} {}", - "IMGUR_IMAGES has to be a bool...".red().bold(), - "EXAMPLE:".bold(), - "true".bright_green().bold(), - "not".bold(), - "'true'".red().bold() - ); - std::process::exit(2) - }); + .to_string()); - match ( - url.is_empty(), - api_key.is_empty(), - username.is_empty(), - rpc_client_id.is_empty(), - (imgur_images, imgur_client_id.is_empty()), - ) { - (true, _, _, _, _) => Err(ConfigError::from("Jellyfin URL is empty!")), - (_, true, _, _, _) => Err(ConfigError::from("Jellyfin API key is empty!")), - (_, _, true, _, _) => Err(ConfigError::from("Jellyfin Username is empty!")), - (_, _, _, true, _) => Err(ConfigError::from("Discord Application ID is empty!")), - (_, _, _, _, (true, true)) => Err(ConfigError::from( - "Imgur Client ID is empty but Imgur images are enabled!", - )), - (false, false, false, false, _) => Ok(Config { - url, - api_key, - username, - blacklist: Blacklist { - types: type_blacklist, - libraries: library_blacklist, - }, - rpc_client_id, - imgur_client_id, - images: Images { - enabled: enable_images, - imgur: imgur_images, - }, + config.imgur_client_id(imgur["CLIENT_ID"].as_str().unwrap_or("").to_string()); + + config.images( + images["ENABLE_IMAGES"].as_bool().unwrap_or_else(|| { + eprintln!( + "{}\n{} {} {} {}", + "ENABLE_IMAGES has to be a bool...".red().bold(), + "EXAMPLE:".bold(), + "true".bright_green().bold(), + "not".bold(), + "'true'".red().bold() + ); + std::process::exit(2) }), - } + images["IMGUR_IMAGES"].as_bool().unwrap_or_else(|| { + eprintln!( + "{}\n{} {} {} {}", + "IMGUR_IMAGES has to be a bool...".red().bold(), + "EXAMPLE:".bold(), + "true".bright_green().bold(), + "not".bold(), + "'true'".red().bold() + ); + std::process::exit(2) + }) + ); + + config.build() } } From 414e1748c4e01ea489aaa6adf191676c05a0770b Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sun, 18 Jun 2023 18:34:23 +0200 Subject: [PATCH 04/23] Make code more readable Removed Option::is_none(&object) in favor of object.is_none() --- src/config.rs | 7 +++++-- src/services/jellyfin.rs | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index d011bb6..56eae76 100644 --- a/src/config.rs +++ b/src/config.rs @@ -133,7 +133,7 @@ impl Config { config.api_key(jellyfin["API_KEY"].as_str().unwrap_or("").to_string()); config.username(jellyfin["USERNAME"].as_str().unwrap_or("").to_string()); let mut type_blacklist: Vec = vec![MediaType::None]; - if !Option::is_none(&jellyfin["TYPE_BLACKLIST"].get(0)) { + if jellyfin["TYPE_BLACKLIST"].get(0).is_some() { type_blacklist.pop(); jellyfin["TYPE_BLACKLIST"] .as_array() @@ -152,7 +152,7 @@ impl Config { }); } let mut library_blacklist: Vec = vec!["".to_string()]; - if !Option::is_none(&jellyfin["LIBRARY_BLACKLIST"].get(0)) { + if jellyfin["LIBRARY_BLACKLIST"].get(0).is_some() { library_blacklist.pop(); jellyfin["LIBRARY_BLACKLIST"] .as_array() @@ -166,6 +166,9 @@ impl Config { ) }); } + + + config.blacklist(type_blacklist, library_blacklist); config.rpc_client_id(discord["APPLICATION_ID"] .as_str() diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 43becba..d4fc2c1 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -96,13 +96,13 @@ impl Content { ) }); for session in sessions { - if Option::is_none(&session.get("UserName")) { + if session.get("UserName").is_none() { continue; } if session["UserName"].as_str().unwrap() != username { continue; } - if Option::is_none(&session.get("NowPlayingItem")) { + if session.get("NowPlayingItem").is_none() { continue; } @@ -145,7 +145,7 @@ impl Content { let first_episode_number = now_playing_item["IndexNumber"].to_string(); let mut msg = "S".to_owned() + &season + "E" + &first_episode_number; - if !Option::is_none(&now_playing_item.get("IndexNumberEnd")) { + if now_playing_item.get("IndexNumberEnd").is_none() { msg += &("-".to_string() + &now_playing_item["IndexNumberEnd"].to_string()); } From dff92b375c70322b1cdf81f1e4b999a9fdaedf82 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:06:28 +0200 Subject: [PATCH 05/23] Use entire config in function --- src/config.rs | 9 ++++++++- src/main.rs | 5 +---- src/services/jellyfin.rs | 19 +++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index 56eae76..5e402ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ struct ConfigBuilder { api_key: String, username: String, blacklist: Blacklist, + music: String, rpc_client_id: String, imgur_client_id: String, images: Images, @@ -39,6 +40,10 @@ impl ConfigBuilder { self.blacklist = Blacklist { types, libraries }; } + fn music(&mut self, music: String) { + self.music = music + } + fn rpc_client_id(&mut self, rpc_client_id: String) { self.rpc_client_id = rpc_client_id; } @@ -71,6 +76,7 @@ impl ConfigBuilder { api_key: self.api_key, username: self.username, blacklist: self.blacklist, + music: self.music, rpc_client_id: self.rpc_client_id, imgur_client_id: self.imgur_client_id, images: self.images @@ -85,6 +91,7 @@ pub struct Config { pub api_key: String, pub username: String, pub blacklist: Blacklist, + pub music: String, pub rpc_client_id: String, pub imgur_client_id: String, pub images: Images, @@ -167,7 +174,7 @@ impl Config { }); } - + config.music(jellyfin["Music"].as_str().unwrap_or("genres").to_string()); config.blacklist(type_blacklist, library_blacklist); config.rpc_client_id(discord["APPLICATION_ID"] diff --git a/src/main.rs b/src/main.rs index 43a77e8..44c3f09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,10 +115,7 @@ async fn main() -> Result<(), Box> { // Start loop loop { let mut content = Content::get( - &config.url, - &config.api_key, - &config.username, - &config.images.enabled, + &config ) .await?; diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index d4fc2c1..4ce5ab3 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -1,5 +1,7 @@ use serde_json::Value; +use crate::config::Config; + /* TODO: Comments */ @@ -74,16 +76,13 @@ pub struct Content { impl Content { pub async fn get( - url: &str, - api_key: &str, - username: &str, - enable_images: &bool, + config: &Config ) -> Result { let sessions: Vec = serde_json::from_str( &reqwest::get(format!( "{}/Sessions?api_key={}", - url.trim_end_matches('/'), - api_key + config.url.trim_end_matches('/'), + config.api_key )) .await? .text() @@ -92,14 +91,14 @@ impl Content { .unwrap_or_else(|_| { panic!( "Can't unwrap URL, check if JELLYFIN_URL is correct. Current URL: {}", - url + config.url ) }); for session in sessions { if session.get("UserName").is_none() { continue; } - if session["UserName"].as_str().unwrap() != username { + if session["UserName"].as_str().unwrap() != config.username { continue; } if session.get("NowPlayingItem").is_none() { @@ -113,8 +112,8 @@ impl Content { Content::watching(&mut content, now_playing_item).await; let mut image_url: String = "".to_string(); - if enable_images == &true { - image_url = Content::image(url, content.item_id.clone()).await; + if config.images.enabled == true { + image_url = Content::image(&config.url, content.item_id.clone()).await; } content.external_services(ExternalServices::get(now_playing_item).await); content.endtime(Content::time_left(now_playing_item, &session).await); From 14f8d3bd390111be41832e88d363bea977386add Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:45:08 +0200 Subject: [PATCH 06/23] Experimental custom music presence --- src/config.rs | 2 +- src/services/jellyfin.rs | 57 ++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5e402ea..e5c1d91 100644 --- a/src/config.rs +++ b/src/config.rs @@ -174,7 +174,7 @@ impl Config { }); } - config.music(jellyfin["Music"].as_str().unwrap_or("genres").to_string()); + config.music(jellyfin["Music"]["Display"].as_str().unwrap_or("genres").to_string()); config.blacklist(type_blacklist, library_blacklist); config.rpc_client_id(discord["APPLICATION_ID"] diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 4ce5ab3..8e0eb8f 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -109,7 +109,7 @@ impl Content { let now_playing_item = &session["NowPlayingItem"]; - Content::watching(&mut content, now_playing_item).await; + Content::watching(&mut content, now_playing_item, config).await; let mut image_url: String = "".to_string(); if config.images.enabled == true { @@ -124,7 +124,7 @@ impl Content { Ok(Self::default()) } - async fn watching(content: &mut ContentBuilder, now_playing_item: &Value) { + async fn watching(content: &mut ContentBuilder, now_playing_item: &Value, config: &Config) { /* FIXME: Update this explanation/remove it. @@ -173,28 +173,45 @@ impl Content { content.state_message(genres); content.item_id(now_playing_item["Id"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "Audio" { - let artist = now_playing_item["AlbumArtist"].as_str().unwrap(); - let msg = match now_playing_item.get("Genres") { - None => format!("By {}", artist), - genre_array => { - format!( - "{} - {}", - artist, - &genre_array - .unwrap() - .as_array() - .unwrap() - .iter() - .map(|x| x.as_str().unwrap().to_string()) - .collect::>() - .join(", ") - ) + let artist = now_playing_item["AlbumArtist"].as_str().unwrap().to_string(); + let mut state = format!("By {} - ", artist); + config.music.split(',').for_each(|mut x| { + x = x.trim(); + let old_state = state.clone(); + match x { + "genres" => match now_playing_item.get("Genres") { + None => (), + genre_array => { + state.push_str( + &genre_array + .unwrap() + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap().to_string()) + .collect::>() + .join(", ") + ) + } + }, + "album" => state.push_str(now_playing_item["Album"].as_str().unwrap_or("")), + "year" => { + let mut year = now_playing_item["ProductionYear"].as_u64().unwrap_or(0).to_string(); + if year == "0" { + year = String::from(""); + } + state.push_str(&year) + }, + _ => state = format!("By {}", artist), } - }; + if state != old_state { + state.push(' ') + } + }); content.media_type(MediaType::Music); content.details(name.into()); - content.state_message(msg); + content.state_message(state); content.item_id(now_playing_item["AlbumId"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "TvChannel" { content.media_type(MediaType::LiveTv); From 26c2fc20e6f8d472eb052c696a3b4463a60f375b Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:12:10 +0200 Subject: [PATCH 07/23] Added support for multiple usernames, fixing #23 Made username field accept both an Array and a String making it possible to give the script multiple usernames. --- src/config.rs | 21 +++++++++++++++++---- src/services/jellyfin.rs | 4 +++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index e5c1d91..7262673 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,7 +11,7 @@ use std::env; struct ConfigBuilder { url: String, api_key: String, - username: String, + username: Vec, blacklist: Blacklist, music: String, rpc_client_id: String, @@ -32,7 +32,7 @@ impl ConfigBuilder { self.api_key = api_key; } - fn username(&mut self, username: String) { + fn username(&mut self, username: Vec) { self.username = username; } @@ -89,7 +89,7 @@ impl ConfigBuilder { pub struct Config { pub url: String, pub api_key: String, - pub username: String, + pub username: Vec, pub blacklist: Blacklist, pub music: String, pub rpc_client_id: String, @@ -138,7 +138,20 @@ impl Config { config.url(jellyfin["URL"].as_str().unwrap_or("").to_string()); config.api_key(jellyfin["API_KEY"].as_str().unwrap_or("").to_string()); - config.username(jellyfin["USERNAME"].as_str().unwrap_or("").to_string()); + if jellyfin["USERNAME"].as_str().is_some() { + config.username(vec![ + jellyfin["USERNAME"].as_str().unwrap_or("").to_string() + ]); + } else { + let mut usernames: Vec = Vec::new(); + jellyfin["USERNAME"].as_array() + .unwrap() + .iter() + .for_each(|username| + usernames.push(username.as_str().unwrap().to_string()) + ); + config.username(usernames) + } let mut type_blacklist: Vec = vec![MediaType::None]; if jellyfin["TYPE_BLACKLIST"].get(0).is_some() { type_blacklist.pop(); diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 8e0eb8f..99fbb0f 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -98,9 +98,11 @@ impl Content { if session.get("UserName").is_none() { continue; } - if session["UserName"].as_str().unwrap() != config.username { + + if config.username.iter().all(|username| session["UserName"].as_str().unwrap() != username) { continue; } + if session.get("NowPlayingItem").is_none() { continue; } From 61a2e24d1a58c7843248db4521d542ddeb4d9820 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:47:03 +0200 Subject: [PATCH 08/23] Added update checker Moved config.rs and error.rs to new core dir Created mod.rs and updates.rs in core dir Changed imports to match new directory structure --- src/{ => core}/config.rs | 2 +- src/{ => core}/error.rs | 0 src/core/mod.rs | 3 +++ src/core/updates.rs | 31 +++++++++++++++++++++++++++++++ src/main.rs | 9 +++++---- src/services/imgur.rs | 2 +- src/services/jellyfin.rs | 2 +- 7 files changed, 42 insertions(+), 7 deletions(-) rename src/{ => core}/config.rs (99%) rename src/{ => core}/error.rs (100%) create mode 100644 src/core/mod.rs create mode 100644 src/core/updates.rs diff --git a/src/config.rs b/src/core/config.rs similarity index 99% rename from src/config.rs rename to src/core/config.rs index 7262673..3bf8ab8 100644 --- a/src/config.rs +++ b/src/core/config.rs @@ -1,4 +1,4 @@ -use crate::error::ConfigError; +use crate::core::error::ConfigError; use crate::services::jellyfin::MediaType; use colored::Colorize; use std::env; diff --git a/src/error.rs b/src/core/error.rs similarity index 100% rename from src/error.rs rename to src/core/error.rs diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..9168893 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod error; +pub mod updates; \ No newline at end of file diff --git a/src/core/updates.rs b/src/core/updates.rs new file mode 100644 index 0000000..81ddaff --- /dev/null +++ b/src/core/updates.rs @@ -0,0 +1,31 @@ +use colored::Colorize; + +use crate::VERSION; +pub async fn checker() { + let current = VERSION.unwrap_or("0.0.0").to_string(); + let latest = get_latest_github() + .await + .unwrap_or( + current.clone() + ); + if latest != current { + eprintln!("{} (Current: v{}, Latest: v{})\n{}\n{}\n{}", + "You are not running the latest version of Jellyfin-RPC".red().bold(), + current, + latest, + "A newer version can be found at".red().bold(), + "https://github.com/Radiicall/jellyfin-rpc/releases/latest".green().bold(), + "This can be safely ignored if you are running a prerelease version".bold()); + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} + +async fn get_latest_github() -> Result { + let url = reqwest::get("https://github.com/Radiicall/jellyfin-rpc/releases/latest") + .await? + .url() + .as_str() + .trim_start_matches("https://github.com/Radiicall/jellyfin-rpc/releases/tag/") + .to_string(); + Ok(url) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 44c3f09..2368818 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ pub mod services; +use crate::core::updates; pub use crate::services::imgur::*; pub use crate::services::jellyfin::*; -pub mod config; -pub use crate::config::*; -pub mod error; +pub mod core; +pub use crate::core::config::*; use clap::Parser; use colored::Colorize; use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient}; @@ -32,6 +32,7 @@ struct Args { #[tokio::main] async fn main() -> Result<(), Box> { + updates::checker().await; let args = Args::parse(); let config_path = args.config.unwrap_or_else(|| { get_config_path().unwrap_or_else(|err| { @@ -39,7 +40,7 @@ async fn main() -> Result<(), Box> { std::process::exit(1) }) }); - + std::fs::create_dir_all(std::path::Path::new(&config_path).parent().unwrap()).ok(); let config = Config::load_config(config_path.clone()).unwrap_or_else(|e| { diff --git a/src/services/imgur.rs b/src/services/imgur.rs index a7635ef..b1409fb 100644 --- a/src/services/imgur.rs +++ b/src/services/imgur.rs @@ -1,4 +1,4 @@ -use crate::error::ImgurError; +use crate::core::error::ImgurError; use serde_json::Value; use std::env; use std::io::Write; diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 99fbb0f..d56ac78 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -1,6 +1,6 @@ use serde_json::Value; -use crate::config::Config; +use crate::core::config::Config; /* TODO: Comments From 1fa9013e4c53fc250e2414f435c43a02c3d2f3f2 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:10:59 +0200 Subject: [PATCH 09/23] cargo clippy --- src/services/jellyfin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index d56ac78..7eb041a 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -114,7 +114,7 @@ impl Content { Content::watching(&mut content, now_playing_item, config).await; let mut image_url: String = "".to_string(); - if config.images.enabled == true { + if config.images.enabled { image_url = Content::image(&config.url, content.item_id.clone()).await; } content.external_services(ExternalServices::get(now_playing_item).await); From 78afa5f982b5af73fc652eff1471b246a4d5a9a7 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:56:20 +0200 Subject: [PATCH 10/23] Add trailing space --- src/core/mod.rs | 2 +- src/core/updates.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/mod.rs b/src/core/mod.rs index 9168893..00ccafc 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,3 @@ pub mod config; pub mod error; -pub mod updates; \ No newline at end of file +pub mod updates; diff --git a/src/core/updates.rs b/src/core/updates.rs index 81ddaff..7c3ec4d 100644 --- a/src/core/updates.rs +++ b/src/core/updates.rs @@ -28,4 +28,4 @@ async fn get_latest_github() -> Result { .trim_start_matches("https://github.com/Radiicall/jellyfin-rpc/releases/tag/") .to_string(); Ok(url) -} \ No newline at end of file +} From 6e1c8d337766414207cca6f06ffb18517d89a168 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:56:49 +0200 Subject: [PATCH 11/23] Collect usernames in a saner way --- src/core/config.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index 3bf8ab8..6053223 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -143,14 +143,13 @@ impl Config { jellyfin["USERNAME"].as_str().unwrap_or("").to_string() ]); } else { - let mut usernames: Vec = Vec::new(); - jellyfin["USERNAME"].as_array() - .unwrap() - .iter() - .for_each(|username| - usernames.push(username.as_str().unwrap().to_string()) - ); - config.username(usernames) + config.username( + jellyfin["USERNAME"].as_array() + .unwrap() + .iter() + .map(|username| username.as_str().unwrap().to_string()) + .collect::>() + ); } let mut type_blacklist: Vec = vec![MediaType::None]; if jellyfin["TYPE_BLACKLIST"].get(0).is_some() { From e9d068b36f2198d39639e9845ae2be678401865d Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:17:35 +0200 Subject: [PATCH 12/23] Add new struct music Move old music to music.display. Add music.separator. Made music.display accept proper arrays and comma seperated arrays in strings. --- src/core/config.rs | 55 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index 6053223..42ccede 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -13,7 +13,7 @@ struct ConfigBuilder { api_key: String, username: Vec, blacklist: Blacklist, - music: String, + music: Music, rpc_client_id: String, imgur_client_id: String, images: Images, @@ -40,8 +40,12 @@ impl ConfigBuilder { self.blacklist = Blacklist { types, libraries }; } - fn music(&mut self, music: String) { - self.music = music + fn music_display(&mut self, display: Vec) { + self.music.display = display + } + + fn music_seperator(&mut self, separator: Option) { + self.music.separator = separator } fn rpc_client_id(&mut self, rpc_client_id: String) { @@ -76,7 +80,10 @@ impl ConfigBuilder { api_key: self.api_key, username: self.username, blacklist: self.blacklist, - music: self.music, + music: Music { + display: self.music.display, + separator: self.music.separator + }, rpc_client_id: self.rpc_client_id, imgur_client_id: self.imgur_client_id, images: self.images @@ -91,7 +98,7 @@ pub struct Config { pub api_key: String, pub username: Vec, pub blacklist: Blacklist, - pub music: String, + pub music: Music, pub rpc_client_id: String, pub imgur_client_id: String, pub images: Images, @@ -109,6 +116,12 @@ pub struct Images { pub imgur: bool, } +#[derive(Default)] +pub struct Music { + pub display: Vec, + pub separator: Option +} + pub fn get_config_path() -> Result { if cfg!(not(windows)) { let user = env::var("USER")?; @@ -132,13 +145,15 @@ impl Config { let res: serde_json::Value = serde_json::from_str(&data)?; let jellyfin: serde_json::Value = res["Jellyfin"].clone(); + let music: serde_json::Value = jellyfin["Music"].clone(); + let discord: serde_json::Value = res["Discord"].clone(); let imgur: serde_json::Value = res["Imgur"].clone(); let images: serde_json::Value = res["Images"].clone(); config.url(jellyfin["URL"].as_str().unwrap_or("").to_string()); config.api_key(jellyfin["API_KEY"].as_str().unwrap_or("").to_string()); - if jellyfin["USERNAME"].as_str().is_some() { + if jellyfin["USERNAME"].is_string() { config.username(vec![ jellyfin["USERNAME"].as_str().unwrap_or("").to_string() ]); @@ -186,7 +201,33 @@ impl Config { }); } - config.music(jellyfin["Music"]["Display"].as_str().unwrap_or("genres").to_string()); + if music["DISPLAY"].is_string() { + config.music_display( + music["DISPLAY"] + .as_str() + .unwrap() + .split(',') + .map(|username| + username.trim().to_string() + ) + .collect::>() + ) + } else if music["Display"].is_array() { + config.music_display( + music["DISPLAY"] + .as_array() + .unwrap() + .iter() + .map(|username| + username.as_str().unwrap().trim().to_string() + ) + .collect::>() + ) + } else { + config.music_display(vec![String::from("genres")]) + } + + config.music_seperator(music["SEPARATOR"].as_str().unwrap_or("-").chars().next()); config.blacklist(type_blacklist, library_blacklist); config.rpc_client_id(discord["APPLICATION_ID"] From 75919ce19c9c6888aaff5cb03c7170f146fbec97 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:18:42 +0200 Subject: [PATCH 13/23] Update jellyfin music handling Changed x to data for better readability. Add in new separator option --- src/services/jellyfin.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 7eb041a..5ca0dcb 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -177,10 +177,12 @@ impl Content { } else if now_playing_item["Type"].as_str().unwrap() == "Audio" { let artist = now_playing_item["AlbumArtist"].as_str().unwrap().to_string(); let mut state = format!("By {} - ", artist); - config.music.split(',').for_each(|mut x| { - x = x.trim(); + let mut index = 0; + config.music.display.iter().for_each(|data| { + index += 1; + let data = data.as_str(); let old_state = state.clone(); - match x { + match data { "genres" => match now_playing_item.get("Genres") { None => (), genre_array => { @@ -206,9 +208,14 @@ impl Content { }, _ => state = format!("By {}", artist), } - if state != old_state { - state.push(' ') + if state != old_state && config.music.display.len() != index { + if config.music.separator.is_some() { + state.push_str(&format!(" {} ", config.music.separator.unwrap())) + } else { + state.push(' ') + } } + }); content.media_type(MediaType::Music); From 1411ac1627baec3c52d04f9ddca662e1c8cf9f6b Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:21:51 +0200 Subject: [PATCH 14/23] Make config lowercase This breaks previous configs so they will have to be updated. Basically having some things uppercase and others lowercase got annoying so now its all lowercase --- src/core/config.rs | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index 42ccede..365b7eb 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -144,22 +144,22 @@ impl Config { let data = std::fs::read_to_string(path)?; let res: serde_json::Value = serde_json::from_str(&data)?; - let jellyfin: serde_json::Value = res["Jellyfin"].clone(); - let music: serde_json::Value = jellyfin["Music"].clone(); + let jellyfin: serde_json::Value = res["jellyfin"].clone(); + let music: serde_json::Value = jellyfin["music"].clone(); - let discord: serde_json::Value = res["Discord"].clone(); - let imgur: serde_json::Value = res["Imgur"].clone(); - let images: serde_json::Value = res["Images"].clone(); + let discord: serde_json::Value = res["discord"].clone(); + let imgur: serde_json::Value = res["imgur"].clone(); + let images: serde_json::Value = res["images"].clone(); - config.url(jellyfin["URL"].as_str().unwrap_or("").to_string()); - config.api_key(jellyfin["API_KEY"].as_str().unwrap_or("").to_string()); - if jellyfin["USERNAME"].is_string() { + config.url(jellyfin["url"].as_str().unwrap_or("").to_string()); + config.api_key(jellyfin["api_key"].as_str().unwrap_or("").to_string()); + if jellyfin["username"].is_string() { config.username(vec![ - jellyfin["USERNAME"].as_str().unwrap_or("").to_string() + jellyfin["username"].as_str().unwrap_or("").to_string() ]); } else { config.username( - jellyfin["USERNAME"].as_array() + jellyfin["username"].as_array() .unwrap() .iter() .map(|username| username.as_str().unwrap().to_string()) @@ -167,9 +167,9 @@ impl Config { ); } let mut type_blacklist: Vec = vec![MediaType::None]; - if jellyfin["TYPE_BLACKLIST"].get(0).is_some() { + if jellyfin["type_blacklist"].get(0).is_some() { type_blacklist.pop(); - jellyfin["TYPE_BLACKLIST"] + jellyfin["type_blacklist"] .as_array() .unwrap() .iter() @@ -186,9 +186,9 @@ impl Config { }); } let mut library_blacklist: Vec = vec!["".to_string()]; - if jellyfin["LIBRARY_BLACKLIST"].get(0).is_some() { + if jellyfin["library_blacklist"].get(0).is_some() { library_blacklist.pop(); - jellyfin["LIBRARY_BLACKLIST"] + jellyfin["library_blacklist"] .as_array() .unwrap() .iter() @@ -201,9 +201,9 @@ impl Config { }); } - if music["DISPLAY"].is_string() { + if music["display"].is_string() { config.music_display( - music["DISPLAY"] + music["display"] .as_str() .unwrap() .split(',') @@ -212,9 +212,9 @@ impl Config { ) .collect::>() ) - } else if music["Display"].is_array() { + } else if music["display"].is_array() { config.music_display( - music["DISPLAY"] + music["display"] .as_array() .unwrap() .iter() @@ -227,21 +227,21 @@ impl Config { config.music_display(vec![String::from("genres")]) } - config.music_seperator(music["SEPARATOR"].as_str().unwrap_or("-").chars().next()); + config.music_seperator(music["separator"].as_str().unwrap_or("-").chars().next()); config.blacklist(type_blacklist, library_blacklist); - config.rpc_client_id(discord["APPLICATION_ID"] + config.rpc_client_id(discord["application_id"] .as_str() .unwrap_or("1053747938519679018") .to_string()); - config.imgur_client_id(imgur["CLIENT_ID"].as_str().unwrap_or("").to_string()); + config.imgur_client_id(imgur["client_id"].as_str().unwrap_or("").to_string()); config.images( - images["ENABLE_IMAGES"].as_bool().unwrap_or_else(|| { + images["enable_images"].as_bool().unwrap_or_else(|| { eprintln!( "{}\n{} {} {} {}", - "ENABLE_IMAGES has to be a bool...".red().bold(), + "enable_images has to be a bool...".red().bold(), "EXAMPLE:".bold(), "true".bright_green().bold(), "not".bold(), @@ -249,10 +249,10 @@ impl Config { ); std::process::exit(2) }), - images["IMGUR_IMAGES"].as_bool().unwrap_or_else(|| { + images["imgur_images"].as_bool().unwrap_or_else(|| { eprintln!( "{}\n{} {} {} {}", - "IMGUR_IMAGES has to be a bool...".red().bold(), + "imgur_images has to be a bool...".red().bold(), "EXAMPLE:".bold(), "true".bright_green().bold(), "not".bold(), From f16a0c80284fcfadeb0078cbed10ba55964c58cb Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:41:27 +0200 Subject: [PATCH 15/23] Renamed installers --- README.md | 4 ++-- scripts/{Auto-install-macos.sh => install-macos.sh} | 0 scripts/{Auto-Install-win.bat => install-win.bat} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename scripts/{Auto-install-macos.sh => install-macos.sh} (100%) rename scripts/{Auto-Install-win.bat => install-win.bat} (100%) diff --git a/README.md b/README.md index 9efa3a8..abb56b2 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Terminal Output: ## Setup - Installers - - Windows - - macOS + - Windows + - macOS Make a `main.json` file with the following items in `$XDG_CONFIG_HOME/jellyfin-rpc` on Linux/macOS and `%APPDATA%\jellyfin-rpc\main.json` on Windows. diff --git a/scripts/Auto-install-macos.sh b/scripts/install-macos.sh similarity index 100% rename from scripts/Auto-install-macos.sh rename to scripts/install-macos.sh diff --git a/scripts/Auto-Install-win.bat b/scripts/install-win.bat similarity index 100% rename from scripts/Auto-Install-win.bat rename to scripts/install-win.bat From afe0bda787d509356cdc94e3bbfd96485d31274d Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Thu, 22 Jun 2023 07:44:59 +0200 Subject: [PATCH 16/23] Update installers to work with newest version --- scripts/install-macos.sh | 26 +++++++++++++------------- scripts/install-win.bat | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh index 23b56f4..0f0cb62 100755 --- a/scripts/install-macos.sh +++ b/scripts/install-macos.sh @@ -95,38 +95,38 @@ fi configFileContents="" configFileContents+="$(cat < main.json -echo "Jellyfin": { >> main.json -echo "URL": "%JELLYFIN_URL%", >> main.json -echo "API_KEY": "%JELLYFIN_API_KEY%", >> main.json -echo "USERNAME": "%JELLYFIN_USERNAME%" >> main.json +echo "jellyfin": { >> main.json +echo "url": "%JELLYFIN_URL%", >> main.json +echo "api_key": "%JELLYFIN_API_KEY%", >> main.json +echo "username": "%JELLYFIN_USERNAME%" >> main.json echo }, >> main.json -echo "Discord": { >> main.json -echo "APPLICATION_ID": "%DISCORD_APPLICATION_ID%" >> main.json +echo "discord": { >> main.json +echo "application_id": "%DISCORD_APPLICATION_ID%" >> main.json echo }, >> main.json -echo "Imgur": { >> main.json -echo "CLIENT_ID": "%IMGUR_CLIENT_ID%" >> main.json +echo "imgur": { >> main.json +echo "client_id": "%IMGUR_CLIENT_ID%" >> main.json echo }, >> main.json -echo "Images": { >> main.json -echo "ENABLE_IMAGES": %IMAGES_ENABLE_IMAGES%, >> main.json -echo "IMGUR_IMAGES": %IMAGES_IMGUR_IMAGES% >> main.json +echo "images": { >> main.json +echo "enable_images": %IMAGES_ENABLE_IMAGES%, >> main.json +echo "imgur_images": %IMAGES_IMGUR_IMAGES% >> main.json echo } >> main.json echo } >> main.json From 20e0600dd980a50761a7267a9ab0891f3b3d3c85 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:36:41 +0200 Subject: [PATCH 17/23] Change msg to state --- src/services/jellyfin.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 5ca0dcb..6a7cb1c 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -144,16 +144,16 @@ impl Content { if now_playing_item["Type"].as_str().unwrap() == "Episode" { let season = now_playing_item["ParentIndexNumber"].to_string(); let first_episode_number = now_playing_item["IndexNumber"].to_string(); - let mut msg = "S".to_owned() + &season + "E" + &first_episode_number; + let mut state = "S".to_owned() + &season + "E" + &first_episode_number; if now_playing_item.get("IndexNumberEnd").is_none() { - msg += &("-".to_string() + &now_playing_item["IndexNumberEnd"].to_string()); + state += &("-".to_string() + &now_playing_item["IndexNumberEnd"].to_string()); } - msg += &(" ".to_string() + name); + state += &(" ".to_string() + name); content.media_type(MediaType::Episode); content.details(now_playing_item["SeriesName"].as_str().unwrap().to_string()); - content.state_message(msg); + content.state_message(state); content.item_id(now_playing_item["SeriesId"].as_str().unwrap().to_string()); } else if now_playing_item["Type"].as_str().unwrap() == "Movie" { match now_playing_item.get("Genres") { @@ -192,7 +192,7 @@ impl Content { .as_array() .unwrap() .iter() - .map(|x| x.as_str().unwrap().to_string()) + .map(|genre| genre.as_str().unwrap().to_string()) .collect::>() .join(", ") ) @@ -208,6 +208,7 @@ impl Content { }, _ => state = format!("By {}", artist), } + if state != old_state && config.music.display.len() != index { if config.music.separator.is_some() { state.push_str(&format!(" {} ", config.music.separator.unwrap())) @@ -215,7 +216,6 @@ impl Content { state.push(' ') } } - }); content.media_type(MediaType::Music); @@ -331,10 +331,6 @@ impl MediaType { } false } - - pub fn equal_to(&self, value: String) -> bool { - self == &Self::from(value) - } } impl From<&'static str> for MediaType { From 1d320d28789b0d924ad6ccb2dbe72cda30faac8b Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:37:43 +0200 Subject: [PATCH 18/23] Change is_none to is_some Fixes a bug where episodes show up as E1-null instead of E1 --- src/services/jellyfin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 6a7cb1c..7d68298 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -146,7 +146,7 @@ impl Content { let first_episode_number = now_playing_item["IndexNumber"].to_string(); let mut state = "S".to_owned() + &season + "E" + &first_episode_number; - if now_playing_item.get("IndexNumberEnd").is_none() { + if now_playing_item.get("IndexNumberEnd").is_some() { state += &("-".to_string() + &now_playing_item["IndexNumberEnd"].to_string()); } From c4d16da35af7e4fda04894104dfd8c7c9b9942a3 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:02:06 +0200 Subject: [PATCH 19/23] Remove useless if statement --- src/services/jellyfin.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/services/jellyfin.rs b/src/services/jellyfin.rs index 7d68298..5dce8d3 100644 --- a/src/services/jellyfin.rs +++ b/src/services/jellyfin.rs @@ -326,10 +326,7 @@ impl Default for MediaType { impl MediaType { pub fn is_none(&self) -> bool { - if self == &Self::None { - return true; - } - false + self == &Self::None } } From 22be9b736d2262882c71880fc1c37ea87a8cad34 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:12:07 +0200 Subject: [PATCH 20/23] Add new blacklist object to jellyfin in config --- src/core/config.rs | 70 ++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index 365b7eb..eca0530 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -146,6 +146,7 @@ impl Config { let jellyfin: serde_json::Value = res["jellyfin"].clone(); let music: serde_json::Value = jellyfin["music"].clone(); + let blacklist: serde_json::Value = jellyfin["blacklist"].clone(); let discord: serde_json::Value = res["discord"].clone(); let imgur: serde_json::Value = res["imgur"].clone(); @@ -166,40 +167,44 @@ impl Config { .collect::>() ); } - let mut type_blacklist: Vec = vec![MediaType::None]; - if jellyfin["type_blacklist"].get(0).is_some() { - type_blacklist.pop(); - jellyfin["type_blacklist"] - .as_array() - .unwrap() - .iter() - .for_each(|val| { - if val != "music" && val != "movie" && val != "episode" && val != "livetv" { - eprintln!("{} is invalid, valid media types to blacklist include: \"music\", \"movie\", \"episode\" and \"livetv\"", val); - std::process::exit(2) - } - type_blacklist.push( - MediaType::from(val - .as_str() - .expect("Media types to blacklist need to be in quotes \"music\"") - .to_string())) - }); - } let mut library_blacklist: Vec = vec!["".to_string()]; - if jellyfin["library_blacklist"].get(0).is_some() { - library_blacklist.pop(); - jellyfin["library_blacklist"] - .as_array() - .unwrap() - .iter() - .for_each(|val| { - library_blacklist.push( - val.as_str() - .expect("Libraries to blacklist need to be in quotes \"music\"") - .to_lowercase(), - ) - }); + let mut type_blacklist: Vec = vec![MediaType::None]; + if blacklist.is_object() { + if blacklist["media_types"].get(0).is_some() { + type_blacklist.pop(); + blacklist["media_types"] + .as_array() + .unwrap() + .iter() + .for_each(|val| { + if val != "music" && val != "movie" && val != "episode" && val != "livetv" { + eprintln!("{} is invalid, valid media types to blacklist include: \"music\", \"movie\", \"episode\" and \"livetv\"", val); + std::process::exit(2) + } + type_blacklist.push( + MediaType::from(val + .as_str() + .expect("Media types to blacklist need to be in quotes \"music\"") + .to_string())) + }); + } + + if blacklist["libraries"].get(0).is_some() { + library_blacklist.pop(); + blacklist["libraries"] + .as_array() + .unwrap() + .iter() + .for_each(|val| { + library_blacklist.push( + val.as_str() + .expect("Libraries to blacklist need to be in quotes \"music\"") + .to_lowercase(), + ) + }); + } } + config.blacklist(type_blacklist, library_blacklist); if music["display"].is_string() { config.music_display( @@ -229,7 +234,6 @@ impl Config { config.music_seperator(music["separator"].as_str().unwrap_or("-").chars().next()); - config.blacklist(type_blacklist, library_blacklist); config.rpc_client_id(discord["application_id"] .as_str() .unwrap_or("1053747938519679018") From 202ff1e2f8e2f7cfe02bfde040da04f079171c5a Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:12:17 +0200 Subject: [PATCH 21/23] Update example.json and readme --- README.md | 30 ++++++++++++++++-------------- example.json | 30 ++++++++++++++++-------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index abb56b2..e68504d 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,25 @@ If you're unsure about the directory then run jellyfin-rpc and it will tell you ``` { - "Jellyfin": { - "URL": "https://example.com", - "API_KEY": "sadasodsapasdskd", - "USERNAME": "your_username_here", - "_comment": "the 2 lines below and this line arent needed and should be removed, by default nothing will display if these are present" - "TYPE_BLACKLIST": ["music", "movie", "episode", "livetv"] - "LIBRARY_BLACKLIST": ["Anime", "Anime Movies"] + "jellyfin": { + "url": "https://example.com", + "api_key": "sadasodsapasdskd", + "username": "your_username_here", + "_comment": "the 4 lines below and this line arent needed and should be removed, by default nothing will display if these are present", + "blacklist": { + "media_types": ["music", "movie", "episode", "livetv"], + "libraries": ["Anime", "Anime Movies"] + } }, - "Discord": { - "APPLICATION_ID": "1053747938519679018" + "discord": { + "application_id": "1053747938519679018" }, - "Imgur": { - "CLIENT_ID": "asdjdjdg394209fdjs093" + "imgur": { + "client_id": "asdjdjdg394209fdjs093" }, - "Images": { - "ENABLE_IMAGES": true, - "IMGUR_IMAGES": true + "images": { + "enable_images": true, + "imgur_images": true } } ``` diff --git a/example.json b/example.json index b077bf8..d620f25 100644 --- a/example.json +++ b/example.json @@ -1,20 +1,22 @@ { - "Jellyfin": { - "URL": "https://example.com", - "API_KEY": "sadasodsapasdskd", - "USERNAME": "your_username_here", - "_comment": "the 2 lines below and this line arent needed and should be removed, by default nothing will display if these are present" - "TYPE_BLACKLIST": ["music", "movie", "episode", "livetv"] - "LIBRARY_BLACKLIST": ["Anime", "Anime Movies"] + "jellyfin": { + "url": "https://example.com", + "api_key": "sadasodsapasdskd", + "username": "your_username_here", + "_comment": "the 4 lines below and this line arent needed and should be removed, by default nothing will display if these are present", + "blacklist": { + "media_types": ["music", "movie", "episode", "livetv"], + "libraries": ["Anime", "Anime Movies"] + } }, - "Discord": { - "APPLICATION_ID": "1053747938519679018" + "discord": { + "application_id": "1053747938519679018" }, - "Imgur": { - "CLIENT_ID": "asdjdjdg394209fdjs093" + "imgur": { + "client_id": "asdjdjdg394209fdjs093" }, - "Images": { - "ENABLE_IMAGES": true, - "IMGUR_IMAGES": true + "images": { + "enable_images": true, + "imgur_images": true } } From 9cab13924254d201bbd64959b469559ec3e21758 Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:19:46 +0200 Subject: [PATCH 22/23] Add music object to examples --- README.md | 4 ++++ example.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index e68504d..6cbe7fc 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ If you're unsure about the directory then run jellyfin-rpc and it will tell you "url": "https://example.com", "api_key": "sadasodsapasdskd", "username": "your_username_here", + "music": { + "display": "genres", + "separator": "-" + }, "_comment": "the 4 lines below and this line arent needed and should be removed, by default nothing will display if these are present", "blacklist": { "media_types": ["music", "movie", "episode", "livetv"], diff --git a/example.json b/example.json index d620f25..ece5f77 100644 --- a/example.json +++ b/example.json @@ -3,6 +3,10 @@ "url": "https://example.com", "api_key": "sadasodsapasdskd", "username": "your_username_here", + "music": { + "display": "genres", + "separator": "-" + }, "_comment": "the 4 lines below and this line arent needed and should be removed, by default nothing will display if these are present", "blacklist": { "media_types": ["music", "movie", "episode", "livetv"], From 65964522e245edff9680b968898e4cde27dd14ac Mon Sep 17 00:00:00 2001 From: Radiicall <66682497+Radiicall@users.noreply.github.com> Date: Sat, 24 Jun 2023 14:24:01 +0200 Subject: [PATCH 23/23] Add a warning because im lazy --- scripts/install-macos.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh index 0f0cb62..d7ef5fe 100755 --- a/scripts/install-macos.sh +++ b/scripts/install-macos.sh @@ -8,6 +8,9 @@ vared -p "Jellyfin API Key (you can find this at ${jellyfinurl}/web/#!/apikeys.h vared -p "Jellyfin Username: " -c jellyfinuser echo "" +echo "The blacklist creation in this script is currently broken" +echo "Check the README for details on how to set it up in the config " + # Prompt the user for what libraries should be included/blocked responses=()