plex_api/webhook/
mod.rs

1//! This module contains a few structs that should help you handle the webhooks
2//! received from a Plex server.
3//!
4//! The structs are implemented according to the [documentation](https://support.plex.tv/articles/115002267687-webhooks/),
5//! please read it for further information.
6
7use crate::media_container::server::library::Guid;
8use serde::{Deserialize, Serialize};
9use serde_aux::prelude::deserialize_option_number_from_string;
10use time::OffsetDateTime;
11
12#[derive(Deserialize, Debug)]
13#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
14pub struct Webhook {
15    pub event: Event,
16    pub user: bool,
17    pub owner: bool,
18    #[serde(rename = "Account")]
19    pub account: Account,
20    #[serde(rename = "Server")]
21    pub server: Server,
22    #[serde(rename = "Player")]
23    pub player: Option<Player>,
24    #[serde(rename = "Metadata")]
25    pub metadata: Option<Metadata>,
26}
27
28#[derive(Deserialize, Debug)]
29#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
30pub struct Account {
31    pub id: u64,
32    #[serde(with = "http_serde::uri")]
33    pub thumb: http::Uri,
34    pub title: String,
35}
36
37#[derive(Deserialize, Debug)]
38#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
39pub struct Server {
40    pub title: String,
41    pub uuid: String,
42}
43
44#[derive(Deserialize, Debug)]
45#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
46#[serde(rename_all = "camelCase")]
47pub struct Player {
48    pub local: bool,
49    pub public_address: String,
50    pub title: String,
51    pub uuid: String,
52}
53
54// TODO: combine with the regular metadata struct when we get one
55#[derive(Deserialize, Debug)]
56#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
57#[serde(rename_all = "camelCase")]
58pub struct Metadata {
59    pub title: String,
60    #[serde(with = "time::serde::timestamp")]
61    pub added_at: OffsetDateTime,
62
63    pub library_section_type: Option<String>,
64    pub key: Option<String>,
65    pub parent_key: Option<String>,
66    pub grandparent_key: Option<String>,
67    pub rating_key: Option<String>,
68    pub parent_rating_key: Option<String>,
69    pub grandparent_rating_key: Option<String>,
70    pub guid: Option<Guid>,
71    #[serde(rename = "librarySectionID")]
72    #[serde(deserialize_with = "deserialize_option_number_from_string")]
73    pub library_section_id: Option<u32>,
74    pub r#type: Option<String>,
75    pub parent_title: Option<String>,
76    pub grandparent_title: Option<String>,
77    pub summary: Option<String>,
78    pub index: Option<u32>,
79    pub parent_index: Option<u32>,
80    pub rating_count: Option<u64>,
81    pub thumb: Option<String>,
82    pub parent_thumb: Option<String>,
83    pub grandparent_thumb: Option<String>,
84    pub art: Option<String>,
85    pub grandparent_art: Option<String>,
86    #[serde(with = "time::serde::timestamp::option")]
87    pub updated_at: Option<OffsetDateTime>,
88}
89
90/// Event type as described in the [Plex documentation](https://support.plex.tv/articles/115002267687-webhooks/).
91#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
92pub enum Event {
93    /// A new item is added that appears in the user’s On Deck. A poster is
94    /// also attached to this event.
95    #[serde(rename = "library.on.deck")]
96    LibraryOnDeck,
97    /// A new item is added to a library to which the user has access. A poster
98    /// is also attached to this event.
99    #[serde(rename = "library.new")]
100    LibraryNew,
101
102    /// Media playback pauses.
103    #[serde(rename = "media.pause")]
104    MediaPause,
105    /// Media starts playing. An appropriate poster is attached.
106    #[serde(rename = "media.play")]
107    MediaPlay,
108    /// Media is rated. A poster is also attached to this event.
109    #[serde(rename = "media.rate")]
110    MediaRate,
111    /// Media playback resumes.
112    #[serde(rename = "media.resume")]
113    MediaResume,
114    /// Media is viewed (played past the 90% mark).
115    #[serde(rename = "media.scrobble")]
116    MediaScrobble,
117    /// Media playback stops.
118    #[serde(rename = "media.stop")]
119    MediaStop,
120
121    /// A database backup is completed successfully via Scheduled Tasks.
122    #[serde(rename = "admin.database.backup")]
123    AdminDatabaseBackup,
124    /// Corruption is detected in the server database.
125    #[serde(rename = "admin.database.corrupted")]
126    AdminDatabaseCorrupted,
127    /// A device accesses the owner’s server for any reason, which may come
128    /// from background connection testing and doesn’t necessarily indicate
129    /// active browsing or playback.
130    #[serde(rename = "device.new")]
131    DeviceNew,
132    /// Playback is started by a shared user for the server. A poster is also
133    /// attached to this event.
134    #[serde(rename = "playback.started")]
135    PlaybackStarted,
136
137    #[cfg(not(feature = "tests_deny_unknown_fields"))]
138    #[serde(other)]
139    Unknown,
140}
141
142#[cfg(test)]
143mod test {
144    #[plex_api_test_helper::offline_test]
145    fn check_parsing() {
146        serde_json::from_str::<super::Webhook>(WEBHOOK_JSON).expect("failed to parse webhook");
147    }
148
149    const WEBHOOK_JSON: &str = r#"{
150        "event": "media.play",
151        "user": true,
152        "owner": true,
153        "Account": {
154           "id": 1,
155           "thumb": "https://plex.tv/users/1022b120ffbaa/avatar?c=1465525047",
156           "title": "elan"
157        },
158        "Server": {
159           "title": "Office",
160           "uuid": "54664a3d8acc39983675640ec9ce00b70af9cc36"
161        },
162        "Player": {
163           "local": true,
164           "publicAddress": "200.200.200.200",
165           "title": "Plex Web (Safari)",
166           "uuid": "r6yfkdnfggbh2bdnvkffwbms"
167        },
168        "Metadata": {
169           "librarySectionType": "artist",
170           "ratingKey": "1936545",
171           "key": "/library/metadata/1936545",
172           "parentRatingKey": "1936544",
173           "grandparentRatingKey": "1936543",
174           "guid": "com.plexapp.agents.plexmusic://gracenote/track/7572499-91016293BE6BF7F1AB2F848F736E74E5/7572500-3CBAE310D4F3E66C285E104A1458B272?lang=en",
175           "librarySectionID": 1224,
176           "type": "track",
177           "title": "Love The One You're With",
178           "grandparentKey": "/library/metadata/1936543",
179           "parentKey": "/library/metadata/1936544",
180           "grandparentTitle": "Stephen Stills",
181           "parentTitle": "Stephen Stills",
182           "summary": "",
183           "index": 1,
184           "parentIndex": 1,
185           "ratingCount": 6794,
186           "thumb": "/library/metadata/1936544/thumb/1432897518",
187           "art": "/library/metadata/1936543/art/1485951497",
188           "parentThumb": "/library/metadata/1936544/thumb/1432897518",
189           "grandparentThumb": "/library/metadata/1936543/thumb/1485951497",
190           "grandparentArt": "/library/metadata/1936543/art/1485951497",
191           "addedAt": 1000396126,
192           "updatedAt": 1432897518
193        }
194     }
195"#;
196}