-
Notifications
You must be signed in to change notification settings - Fork 70
/
Copy pathplex.rs
133 lines (121 loc) · 4.52 KB
/
plex.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use anyhow::{bail, Context, Result};
use common_models::StringIdObject;
use dependent_models::{ImportCompletedItem, ImportResult};
use enum_models::{MediaLot, MediaSource};
use media_models::{ImportOrExportMetadataItem, ImportOrExportMetadataItemSeen};
use regex::Regex;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use crate::utils::get_show_by_episode_identifier;
mod models {
use super::*;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlexWebhookMetadataPayload {
#[serde(rename = "type")]
pub item_type: String,
#[serde(rename = "viewOffset")]
pub view_offset: Option<Decimal>,
pub duration: Decimal,
#[serde(rename = "grandparentTitle")]
pub show_name: Option<String>,
#[serde(rename = "parentIndex")]
pub season_number: Option<i32>,
#[serde(rename = "index")]
pub episode_number: Option<i32>,
#[serde(rename = "Guid")]
pub guids: Vec<StringIdObject>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlexWebhookAccount {
#[serde(rename = "title")]
pub plex_user: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlexWebhookPayload {
#[serde(rename = "event")]
pub event_type: String,
pub user: bool,
pub owner: bool,
#[serde(rename = "Metadata")]
pub metadata: PlexWebhookMetadataPayload,
#[serde(rename = "Account")]
pub account: PlexWebhookAccount,
}
}
fn parse_payload(payload: &str) -> Result<models::PlexWebhookPayload> {
let payload_regex = Regex::new(r"\{.*\}").unwrap();
let json_payload = payload_regex
.find(payload)
.map(|x| x.as_str())
.unwrap_or("");
serde_json::from_str(json_payload).context("Error during JSON payload deserialization")
}
fn get_tmdb_identifier(guids: &[StringIdObject]) -> Result<&str> {
guids
.iter()
.find(|g| g.id.starts_with("tmdb://"))
.map(|g| &g.id[7..])
.ok_or_else(|| anyhow::anyhow!("No TMDb ID associated with this media"))
}
async fn get_media_info<'a>(
db: &DatabaseConnection,
metadata: &'a models::PlexWebhookMetadataPayload,
identifier: &'a str,
) -> Result<(String, MediaLot)> {
match metadata.item_type.as_str() {
"movie" => Ok((identifier.to_owned(), MediaLot::Movie)),
"episode" => {
let series_name = metadata.show_name.as_ref().context("Show name missing")?;
let db_show = get_show_by_episode_identifier(db, series_name, identifier).await?;
Ok((db_show.identifier, MediaLot::Show))
}
_ => bail!("Only movies and shows supported"),
}
}
fn calculate_progress(payload: &models::PlexWebhookPayload) -> Result<Decimal> {
match payload.metadata.view_offset {
Some(offset) => Ok(offset / payload.metadata.duration * dec!(100)),
None if payload.event_type == "media.scrobble" => Ok(dec!(100)),
None => bail!("No position associated with this media"),
}
}
pub async fn sink_progress(
payload: String,
db: &DatabaseConnection,
plex_user: Option<String>,
) -> Result<ImportResult> {
let payload = parse_payload(&payload)?;
if let Some(plex_user) = &plex_user {
if *plex_user != payload.account.plex_user {
bail!(
"Ignoring non matching user {:#?}",
payload.account.plex_user
);
}
}
match payload.event_type.as_str() {
"media.scrobble" | "media.play" | "media.pause" | "media.resume" | "media.stop" => {}
_ => bail!("Ignoring event type {:#?}", payload.event_type),
};
let identifier = get_tmdb_identifier(&payload.metadata.guids)?;
let (identifier, lot) = get_media_info(db, &payload.metadata, identifier).await?;
let progress = calculate_progress(&payload)?;
Ok(ImportResult {
completed: vec![ImportCompletedItem::Metadata(ImportOrExportMetadataItem {
lot,
identifier,
source: MediaSource::Tmdb,
seen_history: vec![ImportOrExportMetadataItemSeen {
progress: Some(progress),
provider_watched_on: Some("Plex".to_string()),
show_season_number: payload.metadata.season_number,
show_episode_number: payload.metadata.episode_number,
..Default::default()
}],
..Default::default()
})],
..Default::default()
})
}