From 95ca1844405ceec8bcb3cd837789ffac4e12ea5e Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:20:12 +0530 Subject: [PATCH 1/6] refactor(integration): rename yank_progress to sink_progress across providers - Update method names from `yank_progress` to `sink_progress` in all integration sink modules - Modify integration service to use the new method names - Consistent naming across Kodi, Emby, Jellyfin, Plex, and Generic JSON providers --- crates/services/integration/src/lib.rs | 10 +++++----- crates/services/integration/src/sink/emby.rs | 2 +- crates/services/integration/src/sink/generic_json.rs | 2 +- crates/services/integration/src/sink/jellyfin.rs | 2 +- crates/services/integration/src/sink/kodi.rs | 2 +- crates/services/integration/src/sink/plex.rs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/services/integration/src/lib.rs b/crates/services/integration/src/lib.rs index 04f9356d89..9088540cd5 100644 --- a/crates/services/integration/src/lib.rs +++ b/crates/services/integration/src/lib.rs @@ -165,14 +165,14 @@ impl IntegrationService { return Err(Error::new("Integration is disabled".to_owned())); } let maybe_progress_update = match integration.provider { - IntegrationProvider::Kodi => sink::kodi::yank_progress(payload).await, - IntegrationProvider::Emby => sink::emby::yank_progress(payload, &self.0.db).await, - IntegrationProvider::JellyfinSink => sink::jellyfin::yank_progress(payload).await, + IntegrationProvider::Kodi => sink::kodi::sink_progress(payload).await, + IntegrationProvider::Emby => sink::emby::sink_progress(payload, &self.0.db).await, + IntegrationProvider::JellyfinSink => sink::jellyfin::sink_progress(payload).await, IntegrationProvider::PlexSink => { let specifics = integration.clone().provider_specifics.unwrap(); - sink::plex::yank_progress(payload, &self.0.db, specifics.plex_sink_username).await + sink::plex::sink_progress(payload, &self.0.db, specifics.plex_sink_username).await } - IntegrationProvider::GenericJson => sink::generic_json::yank_progress(payload).await, + IntegrationProvider::GenericJson => sink::generic_json::sink_progress(payload).await, _ => return Err(Error::new("Unsupported integration source".to_owned())), }; match maybe_progress_update { diff --git a/crates/services/integration/src/sink/emby.rs b/crates/services/integration/src/sink/emby.rs index db849b14a2..cb650eff29 100644 --- a/crates/services/integration/src/sink/emby.rs +++ b/crates/services/integration/src/sink/emby.rs @@ -47,7 +47,7 @@ mod models { } } -pub async fn yank_progress(payload: String, db: &DatabaseConnection) -> Result { +pub async fn sink_progress(payload: String, db: &DatabaseConnection) -> Result { let payload: models::EmbyWebhookPayload = serde_json::from_str(&payload)?; let runtime = payload .item diff --git a/crates/services/integration/src/sink/generic_json.rs b/crates/services/integration/src/sink/generic_json.rs index d8c12301f9..1e602fe2f0 100644 --- a/crates/services/integration/src/sink/generic_json.rs +++ b/crates/services/integration/src/sink/generic_json.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Result}; use dependent_models::{CompleteExport, ImportCompletedItem, ImportResult}; -pub async fn yank_progress(payload: String) -> Result { +pub async fn sink_progress(payload: String) -> Result { let payload = match serde_json::from_str::(&payload) { Ok(val) => val, Err(err) => bail!(err), diff --git a/crates/services/integration/src/sink/jellyfin.rs b/crates/services/integration/src/sink/jellyfin.rs index b5e174b250..874d5bb427 100644 --- a/crates/services/integration/src/sink/jellyfin.rs +++ b/crates/services/integration/src/sink/jellyfin.rs @@ -46,7 +46,7 @@ mod models { } } -pub async fn yank_progress(payload: String) -> Result { +pub async fn sink_progress(payload: String) -> Result { let payload = serde_json::from_str::(&payload)?; let identifier = payload .item diff --git a/crates/services/integration/src/sink/kodi.rs b/crates/services/integration/src/sink/kodi.rs index 55326a8d08..93be86d35b 100644 --- a/crates/services/integration/src/sink/kodi.rs +++ b/crates/services/integration/src/sink/kodi.rs @@ -14,7 +14,7 @@ struct IntegrationMediaSeen { show_episode_number: Option, } -pub async fn yank_progress(payload: String) -> Result { +pub async fn sink_progress(payload: String) -> Result { let payload = match serde_json::from_str::(&payload) { Ok(val) => val, Err(err) => bail!(err), diff --git a/crates/services/integration/src/sink/plex.rs b/crates/services/integration/src/sink/plex.rs index 1f8ce65976..26712b0878 100644 --- a/crates/services/integration/src/sink/plex.rs +++ b/crates/services/integration/src/sink/plex.rs @@ -89,7 +89,7 @@ fn calculate_progress(payload: &models::PlexWebhookPayload) -> Result { } } -pub async fn yank_progress( +pub async fn sink_progress( payload: String, db: &DatabaseConnection, plex_user: Option, From 083182ff9ccaeb0368c612f4115a8bc5ccf59024 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:22:48 +0530 Subject: [PATCH 2/6] refactor(integration): update Jellyfin webhook payload model --- crates/services/integration/src/sink/jellyfin.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/services/integration/src/sink/jellyfin.rs b/crates/services/integration/src/sink/jellyfin.rs index 874d5bb427..f1325691df 100644 --- a/crates/services/integration/src/sink/jellyfin.rs +++ b/crates/services/integration/src/sink/jellyfin.rs @@ -27,14 +27,14 @@ mod models { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] pub struct JellyfinWebhookItemPayload { - pub run_time_ticks: Option, #[serde(rename = "Type")] pub item_type: String, - pub provider_ids: JellyfinWebhookItemProviderIdsPayload, #[serde(rename = "ParentIndexNumber")] pub season_number: Option, #[serde(rename = "IndexNumber")] pub episode_number: Option, + pub run_time_ticks: Option, + pub provider_ids: JellyfinWebhookItemProviderIdsPayload, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] @@ -42,7 +42,7 @@ mod models { pub event: Option, pub item: JellyfinWebhookItemPayload, pub series: Option, - pub session: JellyfinWebhookSessionPayload, + pub session: Option, } } @@ -69,8 +69,8 @@ pub async fn sink_progress(payload: String) -> Result { let position = payload .session - .play_state - .position_ticks + .as_ref() + .and_then(|s| s.play_state.position_ticks.as_ref()) .ok_or_else(|| anyhow::anyhow!("No position associated with this media"))?; let lot = match payload.item.item_type.as_str() { From 4c9aec8766863758139ce2606d22f5addf3bddde Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:29:42 +0530 Subject: [PATCH 3/6] refactor(integration): enhance Jellyfin progress tracking logic --- .../services/integration/src/sink/jellyfin.rs | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/crates/services/integration/src/sink/jellyfin.rs b/crates/services/integration/src/sink/jellyfin.rs index f1325691df..cf6ab5c3e8 100644 --- a/crates/services/integration/src/sink/jellyfin.rs +++ b/crates/services/integration/src/sink/jellyfin.rs @@ -62,37 +62,46 @@ pub async fn sink_progress(payload: String) -> Result { .ok_or_else(|| anyhow::anyhow!("No TMDb ID associated with this media"))? .clone(); - let runtime = payload - .item - .run_time_ticks - .ok_or_else(|| anyhow::anyhow!("No run time associated with this media"))?; - - let position = payload - .session - .as_ref() - .and_then(|s| s.play_state.position_ticks.as_ref()) - .ok_or_else(|| anyhow::anyhow!("No position associated with this media"))?; - let lot = match payload.item.item_type.as_str() { "Episode" => MediaLot::Show, "Movie" => MediaLot::Movie, _ => bail!("Only movies and shows supported"), }; - Ok(ImportResult { - completed: vec![ImportCompletedItem::Metadata(ImportOrExportMetadataItem { - lot, - identifier, - source: MediaSource::Tmdb, - seen_history: vec![ImportOrExportMetadataItemSeen { + let mut item = ImportOrExportMetadataItem { + lot, + identifier, + seen_history: vec![], + source: MediaSource::Tmdb, + ..Default::default() + }; + + match payload.event.unwrap_or_default().as_str() { + _ => { + let runtime = payload + .item + .run_time_ticks + .ok_or_else(|| anyhow::anyhow!("No run time associated with this media"))?; + + let position = payload + .session + .as_ref() + .and_then(|s| s.play_state.position_ticks.as_ref()) + .ok_or_else(|| anyhow::anyhow!("No position associated with this media"))?; + + item.seen_history.push(ImportOrExportMetadataItemSeen { progress: Some(position / runtime * dec!(100)), show_season_number: payload.item.season_number, show_episode_number: payload.item.episode_number, provider_watched_on: Some("Jellyfin".to_string()), ..Default::default() - }], - ..Default::default() - })], + }); + } + } + + let completed = ImportResult { + completed: vec![ImportCompletedItem::Metadata(item)], ..Default::default() - }) + }; + Ok(completed) } From ad486d10f48237d55bce8df7f64b85135574e610 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:31:17 +0530 Subject: [PATCH 4/6] feat(integration): add MarkPlayed event handling for Jellyfin progress tracking --- crates/services/integration/src/sink/jellyfin.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/services/integration/src/sink/jellyfin.rs b/crates/services/integration/src/sink/jellyfin.rs index cf6ab5c3e8..b074aa1e6f 100644 --- a/crates/services/integration/src/sink/jellyfin.rs +++ b/crates/services/integration/src/sink/jellyfin.rs @@ -77,6 +77,14 @@ pub async fn sink_progress(payload: String) -> Result { }; match payload.event.unwrap_or_default().as_str() { + "MarkPlayed" => { + item.seen_history.push(ImportOrExportMetadataItemSeen { + show_season_number: payload.item.season_number, + show_episode_number: payload.item.episode_number, + provider_watched_on: Some("Jellyfin".to_string()), + ..Default::default() + }); + } _ => { let runtime = payload .item From 170f40bbdd75d4c4bd0a6f02e95e2240899ccf23 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:35:23 +0530 Subject: [PATCH 5/6] refactor(integration): simplify Jellyfin progress tracking implementation --- .../services/integration/src/sink/jellyfin.rs | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/crates/services/integration/src/sink/jellyfin.rs b/crates/services/integration/src/sink/jellyfin.rs index b074aa1e6f..1c54fb9f3c 100644 --- a/crates/services/integration/src/sink/jellyfin.rs +++ b/crates/services/integration/src/sink/jellyfin.rs @@ -68,23 +68,15 @@ pub async fn sink_progress(payload: String) -> Result { _ => bail!("Only movies and shows supported"), }; - let mut item = ImportOrExportMetadataItem { - lot, - identifier, - seen_history: vec![], - source: MediaSource::Tmdb, + let mut seen_item = ImportOrExportMetadataItemSeen { + show_season_number: payload.item.season_number, + show_episode_number: payload.item.episode_number, + provider_watched_on: Some("Jellyfin".to_string()), ..Default::default() }; match payload.event.unwrap_or_default().as_str() { - "MarkPlayed" => { - item.seen_history.push(ImportOrExportMetadataItemSeen { - show_season_number: payload.item.season_number, - show_episode_number: payload.item.episode_number, - provider_watched_on: Some("Jellyfin".to_string()), - ..Default::default() - }); - } + "MarkPlayed" => {} _ => { let runtime = payload .item @@ -97,19 +89,18 @@ pub async fn sink_progress(payload: String) -> Result { .and_then(|s| s.play_state.position_ticks.as_ref()) .ok_or_else(|| anyhow::anyhow!("No position associated with this media"))?; - item.seen_history.push(ImportOrExportMetadataItemSeen { - progress: Some(position / runtime * dec!(100)), - show_season_number: payload.item.season_number, - show_episode_number: payload.item.episode_number, - provider_watched_on: Some("Jellyfin".to_string()), - ..Default::default() - }); + seen_item.progress = Some(position / runtime * dec!(100)); } } - let completed = ImportResult { - completed: vec![ImportCompletedItem::Metadata(item)], + Ok(ImportResult { + completed: vec![ImportCompletedItem::Metadata(ImportOrExportMetadataItem { + lot, + identifier, + source: MediaSource::Tmdb, + seen_history: vec![seen_item], + ..Default::default() + })], ..Default::default() - }; - Ok(completed) + }) } From ea4dce082f82790d5be844fd5244c40cb79e1dbb Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Fri, 7 Mar 2025 16:39:46 +0530 Subject: [PATCH 6/6] docs(integration): update Jellyfin webhook events documentation - Add `MarkPlayed` to the list of supported webhook events - Clarify event configuration for Jellyfin integration --- docs/content/integrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/integrations.md b/docs/content/integrations.md index 379549e4d1..93f9a63e74 100644 --- a/docs/content/integrations.md +++ b/docs/content/integrations.md @@ -42,7 +42,7 @@ will work for all the media that have a valid TMDb ID attached to their metadata - Webhook Url => `` - Payload format => `Default` - Listen to events only for => Choose your user - - Events => `Play`, `Pause`, `Resume`, `Stop` and `Progress` + - Events => `Play`, `Pause`, `Resume`, `Stop`, `Progress` and `MarkPlayed` ### Emby