From c114fb72761251038b466db86e3c5405944c2a5c Mon Sep 17 00:00:00 2001 From: Jamen Marz Date: Thu, 3 Oct 2024 00:25:44 -0500 Subject: [PATCH] feat: formatting activity text --- Cargo.lock | 16 +- jellyfin-rpc-cli/Cargo.toml | 2 +- jellyfin-rpc-cli/src/config.rs | 79 +++----- jellyfin-rpc-cli/src/main.rs | 38 ++-- jellyfin-rpc/src/lib.rs | 342 +++++++++++++++++++-------------- 5 files changed, 249 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b9546b..350d4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,27 +421,13 @@ dependencies = [ "url", ] -[[package]] -name = "jellyfin-rpc" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf58e81834dcd46aa84eb1a8a076de0d99eb98e007bb124891997e28a9fc75f" -dependencies = [ - "discord-rich-presence", - "log", - "reqwest", - "serde", - "serde_json", - "url", -] - [[package]] name = "jellyfin-rpc-cli" version = "1.3.0" dependencies = [ "clap", "colored", - "jellyfin-rpc 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jellyfin-rpc", "log", "reqwest", "retry", diff --git a/jellyfin-rpc-cli/Cargo.toml b/jellyfin-rpc-cli/Cargo.toml index 27918d8..17a8b1d 100644 --- a/jellyfin-rpc-cli/Cargo.toml +++ b/jellyfin-rpc-cli/Cargo.toml @@ -30,7 +30,7 @@ serde_json = "1.0" [dependencies.jellyfin-rpc] version = "1.3.0" -#path = "../jellyfin-rpc" +path = "../jellyfin-rpc" [dependencies.clap] features = ["derive"] diff --git a/jellyfin-rpc-cli/src/config.rs b/jellyfin-rpc-cli/src/config.rs index 0e9f63a..8e53a98 100644 --- a/jellyfin-rpc-cli/src/config.rs +++ b/jellyfin-rpc-cli/src/config.rs @@ -44,11 +44,11 @@ pub struct Jellyfin { } /// Contains configuration for Music/Movie display. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct DisplayOptions { - /// Display is where you tell the program what should be displayed. - pub display: Option>, - /// Separator is what should be between the artist(s) and the `display` options. - pub separator: Option, + pub state_text: Option, + pub details_text: Option, + pub image_text: Option, } /// Discord configuration @@ -90,8 +90,8 @@ pub struct JellyfinBuilder { pub url: String, pub api_key: String, pub username: Username, - pub music: Option, - pub movies: Option, + pub music: Option, + pub movies: Option, pub blacklist: Option, pub self_signed_cert: Option, pub show_simple: Option, @@ -108,21 +108,6 @@ pub enum Username { String(String), } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct DisplayOptionsBuilder { - pub display: Option, - pub separator: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(untagged)] -pub enum Display { - /// If the Display is a `Vec`. - Vec(Vec), - /// If the Display is a comma separated `String`. - String(String), -} - /// Blacklist MediaTypes and libraries. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct Blacklist { @@ -238,42 +223,24 @@ impl ConfigBuilder { Username::String(username) => username.split(',').map(|u| u.to_string()).collect(), }; - let music_display; - let music_separator; + let mut music_state_text = None; + let mut music_details_text = None; + let mut music_image_text = None; if let Some(music) = self.jellyfin.music { - if let Some(disp) = music.display { - music_display = Some(match disp { - Display::Vec(display) => display, - Display::String(display) => display.split(',').map(|d| d.to_string()).collect(), - }) - } else { - music_display = None; - } - - music_separator = music.separator; - } else { - music_display = None; - music_separator = None; + music_state_text = music.state_text; + music_details_text = music.details_text; + music_image_text = music.image_text; } - let movie_display; - let movie_separator; + let mut movies_state_text = None; + let mut movies_details_text = None; + let mut movies_image_text = None; if let Some(movies) = self.jellyfin.movies { - if let Some(disp) = movies.display { - movie_display = Some(match disp { - Display::Vec(display) => display, - Display::String(display) => display.split(',').map(|d| d.to_string()).collect(), - }) - } else { - movie_display = None; - } - - movie_separator = movies.separator; - } else { - movie_display = None; - movie_separator = None; + movies_state_text = movies.state_text; + movies_details_text = movies.details_text; + movies_image_text = movies.image_text; } let media_types; @@ -326,12 +293,14 @@ impl ConfigBuilder { api_key: self.jellyfin.api_key, username, music: DisplayOptions { - display: music_display, - separator: music_separator, + state_text: music_state_text, + details_text: music_details_text, + image_text: music_image_text, }, movies: DisplayOptions { - display: movie_display, - separator: movie_separator, + state_text: movies_state_text, + details_text: movies_details_text, + image_text: movies_image_text, }, blacklist: Blacklist { media_types, diff --git a/jellyfin-rpc-cli/src/main.rs b/jellyfin-rpc-cli/src/main.rs index 652c58c..07910b7 100644 --- a/jellyfin-rpc-cli/src/main.rs +++ b/jellyfin-rpc-cli/src/main.rs @@ -92,24 +92,34 @@ fn main() -> Result<(), Box> { .large_image_text(format!("Jellyfin-RPC v{}", VERSION.unwrap_or("UNKNOWN"))) .imgur_urls_file_location(args.image_urls.unwrap_or(get_urls_path()?)); - if let Some(display) = conf.jellyfin.music.display { - debug!("Found config.jellyfin.music.display"); - builder.music_display(display); + if let Some(state_text) = conf.jellyfin.music.state_text { + debug!("Found config.jellyfin.music.state_text"); + builder.music_state_text(state_text); } - if let Some(separator) = conf.jellyfin.music.separator { - debug!("Found config.jellyfin.music.separator"); - builder.music_separator(separator); + if let Some(details_text) = conf.jellyfin.music.details_text { + debug!("Found config.jellyfin.music.details_text"); + builder.music_details_text(details_text); } - if let Some(display) = conf.jellyfin.movies.display { - debug!("Found config.jellyfin.music.display"); - builder.movies_display(display); + if let Some(image_text) = conf.jellyfin.music.image_text { + debug!("Found config.jellyfin.music.image_text"); + builder.music_image_text(image_text); } - if let Some(separator) = conf.jellyfin.movies.separator { - debug!("Found config.jellyfin.music.separator"); - builder.movies_separator(separator); + if let Some(state_text) = conf.jellyfin.movies.state_text { + debug!("Found config.jellyfin.movies.state_text"); + builder.movies_state_text(state_text); + } + + if let Some(details_text) = conf.jellyfin.movies.details_text { + debug!("Found config.jellyfin.movies.details_text"); + builder.movies_details_text(details_text); + } + + if let Some(image_text) = conf.jellyfin.movies.image_text { + debug!("Found config.jellyfin.movies.image_text"); + builder.movies_image_text(image_text); } if let Some(media_types) = conf.jellyfin.blacklist.media_types { @@ -162,8 +172,6 @@ fn main() -> Result<(), Box> { let mut currently_playing = String::new(); loop { - sleep(Duration::from_secs(args.wait_time as u64)); - match client.set_activity() { Ok(activity) => { if activity.is_empty() && !currently_playing.is_empty() { @@ -202,5 +210,7 @@ fn main() -> Result<(), Box> { info!("Reconnected!"); } } + + sleep(Duration::from_secs(args.wait_time as u64)); } } diff --git a/jellyfin-rpc/src/lib.rs b/jellyfin-rpc/src/lib.rs index 6436fc3..96957b4 100644 --- a/jellyfin-rpc/src/lib.rs +++ b/jellyfin-rpc/src/lib.rs @@ -34,7 +34,6 @@ pub struct Client { show_paused: bool, show_images: bool, imgur_options: ImgurOptions, - large_image_text: String, } impl Client { @@ -62,7 +61,7 @@ impl Client { /// let mut builder = Client::builder(); /// builder.api_key("abcd1234") /// .url("https://jellyfin.example.com") - /// .username("user"); + /// .username("user"); /// /// let mut client = builder.build().unwrap(); /// @@ -85,7 +84,7 @@ impl Client { /// let mut builder = Client::builder(); /// builder.api_key("abcd1234") /// .url("https://jellyfin.example.com") - /// .username("user"); + /// .username("user"); /// /// let mut client = builder.build().unwrap(); /// @@ -107,6 +106,8 @@ impl Client { let mut activity = Activity::new(); + let activity_text = self.get_activity_text(); + let mut image_url = Url::from_str("https://i.imgur.com/oX6vcds.png")?; if session.now_playing_item.media_type == MediaType::LiveTv { @@ -127,8 +128,10 @@ impl Client { let mut assets = Assets::new().large_image(image_url.as_str()); - if !self.large_image_text.is_empty() { - assets = assets.large_text(&self.large_image_text); + let image_text = activity_text.image_text; + + if !image_text.is_empty() { + assets = assets.large_text(&image_text); } let mut timestamps = Timestamps::new(); @@ -157,20 +160,16 @@ impl Client { ); } - let mut state = self.get_state(); + let mut state_text = activity_text.state_text; - if state.len() > 128 { - state = state.chars().take(128).collect(); - } else if state.len() < 3 { - state += "‎‎‎"; + if state_text.len() > 128 { + state_text = state_text.chars().take(128).collect(); } - let mut details = session.get_details().to_string(); + let mut details_text = activity_text.details_text; - if details.len() > 128 { - details = details.chars().take(128).collect(); - } else if details.len() < 3 { - details += "‎‎‎"; + if details_text.len() > 128 { + details_text = details_text.chars().take(128).collect(); } match session.now_playing_item.media_type { @@ -184,12 +183,12 @@ impl Client { activity = activity .timestamps(timestamps) .assets(assets) - .details(&details) - .state(&state); + .state(&state_text) + .details(&details_text); self.discord_ipc_client.set_activity(activity)?; - return Ok(format!("{} | {}", details, state)); + return Ok(format!("{} | {}", state_text, details_text)); } Ok(String::new()) } @@ -330,7 +329,7 @@ impl Client { } } - fn get_state(&self) -> String { + fn get_activity_text(&self) -> DisplayText { let session = self.session.as_ref().unwrap(); match session.now_playing_item.media_type { @@ -372,55 +371,73 @@ impl Client { state += &format!(" {}", session.now_playing_item.name) } - state + DisplayText { + image_text: String::new(), + state_text: state, + details_text: String::new(), + } } - MediaType::LiveTv => "Live TV".to_string(), + MediaType::LiveTv => DisplayText { + image_text: String::new(), + state_text: "Live TV".to_string(), + details_text: String::new(), + }, MediaType::Music => { - let mut state = String::new(); - let artists = session.format_artists(); - if !artists.is_empty() { - state += &format!("By {}", artists) - } + let title = &session.now_playing_item.name; - for data in &self.music_display_options.display { - match data.as_str() { - "genres" => { - let genres = session - .now_playing_item - .genres - .as_ref() - .unwrap_or(&vec!["".to_string()]) - .join(", "); - if !state.is_empty() && !genres.is_empty() { - state += &format!(" {} ", self.music_display_options.separator); - } - state += &genres - } - "year" => { - if let Some(year) = session.now_playing_item.production_year { - if !state.is_empty() { - state += &format!(" {} ", self.music_display_options.separator); - } - - state += &year.to_string(); - } - } - "album" => { - if let Some(album) = &session.now_playing_item.album { - if !state.is_empty() { - state += &format!(" {} ", self.music_display_options.separator); - } - - state += album; - } - } - _ => (), - } - } + let genres = session + .now_playing_item + .genres + .as_ref() + .unwrap_or(&vec!["".to_string()]) + .join(", "); - state + let year = session + .now_playing_item + .production_year + .map(|x| x.to_string()) + .unwrap_or(String::new()); + + let album = session + .now_playing_item + .album + .clone() + .unwrap_or(String::new()); + + let image_text = self + .music_display_options + .image_text + .replace("{title}", &title) + .replace("{artists}", &artists) + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{album}", &album); + + let state_text = self + .music_display_options + .state_text + .replace("{title}", &title) + .replace("{artists}", &artists) + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{album}", &album); + + let details_text = self + .music_display_options + .details_text + .replace("{title}", &title) + .replace("{artists}", &artists) + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{album}", &album); + + DisplayText { + state_text, + details_text, + image_text, + } } MediaType::Book => { let mut state = String::new(); @@ -433,7 +450,11 @@ impl Client { state += &format!("Reading page {}", page); } - state + DisplayText { + image_text: String::new(), + state_text: state, + details_text: String::new(), + } } MediaType::AudioBook => { let mut state = String::new(); @@ -457,69 +478,82 @@ impl Client { state += &genres; - state + DisplayText { + image_text: String::new(), + state_text: state, + details_text: String::new(), + } } MediaType::Movie => { - let mut state = String::new(); + let genres = session + .now_playing_item + .genres + .as_ref() + .unwrap_or(&vec!["".to_string()]) + .join(", "); - for data in &self.movies_display_options.display { - match data.as_str() { - "genres" => { - let genres = session - .now_playing_item - .genres - .as_ref() - .unwrap_or(&vec!["".to_string()]) - .join(", "); - if !state.is_empty() && !genres.is_empty() { - state += &format!(" {} ", self.movies_display_options.separator); - } - state += &genres - } - "year" => { - if let Some(year) = session.now_playing_item.production_year { - if !state.is_empty() { - state += - &format!(" {} ", self.movies_display_options.separator); - } - - state += &year.to_string(); - } - } - "critic-score" => { - if let Some(critic_score) = &session.now_playing_item.critic_rating { - if !state.is_empty() { - state += - &format!(" {} ", self.movies_display_options.separator); - } - - state += &format!("🍅{}/100", critic_score); - } - } - "community-score" => { - if let Some(community_score) = - &session.now_playing_item.community_rating - { - if !state.is_empty() { - state += - &format!(" {} ", self.movies_display_options.separator); - } - - state += &format!("⭐{:.1}/10", community_score); - } - } - _ => (), - } + let year = session + .now_playing_item + .production_year + .map(|x| x.to_string()) + .unwrap_or(String::new()); + + let critic_rating = session + .now_playing_item + .critic_rating + .map(|x| x.to_string()) + .unwrap_or(String::new()); + + let community_rating = session + .now_playing_item + .community_rating + .map(|x| x.to_string()) + .unwrap_or(String::new()); + + let image_text = self + .music_display_options + .image_text + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{critic_rating}", &critic_rating) + .replace("{community_rating}", &community_rating); + + let details_line_1 = self + .music_display_options + .state_text + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{critic_rating}", &critic_rating) + .replace("{community_rating}", &community_rating); + + let details_line_2 = self + .music_display_options + .details_text + .replace("{genres}", &genres) + .replace("{year}", &year) + .replace("{critic_rating}", &critic_rating) + .replace("{community_rating}", &community_rating); + + DisplayText { + image_text, + state_text: details_line_1, + details_text: details_line_2, } + } + _ => { + let state = session + .now_playing_item + .genres + .as_ref() + .unwrap_or(&vec!["".to_string()]) + .join(", "); - state + DisplayText { + image_text: String::new(), + state_text: state, + details_text: String::new(), + } } - _ => session - .now_playing_item - .genres - .as_ref() - .unwrap_or(&vec!["".to_string()]) - .join(", "), } } @@ -572,8 +606,15 @@ struct EpisodeDisplayOptions { } struct DisplayOptions { - separator: String, - display: Vec, + image_text: String, + state_text: String, + details_text: String, +} + +struct DisplayText { + image_text: String, + state_text: String, + details_text: String, } struct Blacklist { @@ -599,10 +640,12 @@ pub struct ClientBuilder { episode_divider: bool, episode_prefix: bool, episode_simple: bool, - music_separator: String, - music_display: Vec, - movies_separator: String, - movies_display: Vec, + music_image_text: String, + music_state_text: String, + music_details_text: String, + movie_image_text: String, + movie_state_text: String, + movie_details_text: String, blacklist_media_types: Vec, blacklist_libraries: Vec, show_paused: bool, @@ -618,10 +661,12 @@ impl ClientBuilder { pub fn new() -> Self { Self { client_id: "1053747938519679018".to_string(), - music_separator: "-".to_string(), - music_display: vec!["genres".to_string()], - movies_separator: "-".to_string(), - movies_display: vec!["genres".to_string()], + music_image_text: "{track}".to_string(), + music_state_text: "by {artists}".to_string(), + music_details_text: "on {album} ({genres})".to_string(), + movie_image_text: "{title}".to_string(), + movie_state_text: "{episode_title}".to_string(), + movie_details_text: "E{episode_number}S{season_number}".to_string(), show_paused: true, ..Default::default() } @@ -725,23 +770,33 @@ impl ClientBuilder { self } - pub fn music_separator>(&mut self, separator: T) -> &mut Self { - self.music_separator = separator.into(); + pub fn music_state_text(&mut self, state_text: String) -> &mut Self { + self.music_state_text = state_text; + self + } + + pub fn music_details_text(&mut self, details_text: String) -> &mut Self { + self.music_details_text = details_text; + self + } + + pub fn music_image_text(&mut self, image_text: String) -> &mut Self { + self.music_image_text = image_text; self } - pub fn music_display(&mut self, display: Vec) -> &mut Self { - self.music_display = display; + pub fn movies_state_text(&mut self, state_text: String) -> &mut Self { + self.music_state_text = state_text; self } - pub fn movies_separator>(&mut self, separator: T) -> &mut Self { - self.movies_separator = separator.into(); + pub fn movies_details_text(&mut self, details_text: String) -> &mut Self { + self.music_details_text = details_text; self } - pub fn movies_display(&mut self, display: Vec) -> &mut Self { - self.movies_display = display; + pub fn movies_image_text(&mut self, image_text: String) -> &mut Self { + self.music_image_text = image_text; self } @@ -857,12 +912,14 @@ impl ClientBuilder { simple: self.episode_simple, }, music_display_options: DisplayOptions { - separator: self.music_separator, - display: self.music_display, + state_text: self.music_state_text, + details_text: self.music_details_text, + image_text: self.music_image_text, }, movies_display_options: DisplayOptions { - separator: self.movies_separator, - display: self.movies_display, + state_text: self.movie_state_text, + details_text: self.movie_details_text, + image_text: self.movie_image_text, }, blacklist: Blacklist { media_types: self.blacklist_media_types, @@ -875,7 +932,6 @@ impl ClientBuilder { client_id: self.imgur_client_id, urls_location: self.imgur_urls_file_location, }, - large_image_text: self.large_image_text, }) } }