1use serde::{Deserialize, Serialize};
5
6use crate::{Failure, Track};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum EventType {
10 TrackLoadingEvent,
11 TrackStartEvent,
12 TrackAudibleEvent,
13 TrackEndEvent,
14 TrackExceptionEvent,
15 TrackStuckEvent,
16 MixArmedEvent,
17 MixStartedEvent,
18 MixPromotedEvent,
19 MixDroppedEvent,
20 HostStartedEvent,
21 HostEndedEvent,
22 SendStallEvent,
23 #[serde(rename = "PositionEvent")]
24 PositionJump,
25 WebSocketClosedEvent,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub enum EndReason {
31 Finished,
32 LoadFailed,
33 Stopped,
34 Replaced,
35 Cleanup,
36}
37
38impl EndReason {
39 pub fn may_start_next(self) -> bool {
41 matches!(self, EndReason::Finished | EndReason::LoadFailed)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct Event {
49 pub op: String,
50 #[serde(rename = "type")]
51 pub kind: EventType,
52 pub guild_id: String,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub track: Option<Track>,
55 #[serde(default, skip_serializing_if = "String::is_empty")]
56 pub encoded: String,
57 #[serde(default, skip_serializing_if = "is_zero_u64")]
58 pub instance: u64,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub reason: Option<EndReason>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub exception: Option<Failure>,
63 #[serde(default, skip_serializing_if = "is_zero")]
64 pub position_ms: i64,
65 #[serde(default, skip_serializing_if = "is_zero")]
66 pub sent_at: i64,
67 #[serde(default, skip_serializing_if = "is_zero")]
68 pub audible_at: i64,
69 #[serde(default, skip_serializing_if = "is_zero")]
70 pub duration_ms: i64,
71 #[serde(default, skip_serializing_if = "is_zero")]
72 pub stall_ms: i64,
73 #[serde(default, skip_serializing_if = "is_zero")]
74 pub threshold_ms: i64,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub into: Option<Track>,
77 #[serde(default, rename = "intoPositionMs", skip_serializing_if = "is_zero")]
78 pub into_ms: i64,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub code: Option<u16>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub terminal: Option<bool>,
84 #[serde(default, rename = "closeReason", skip_serializing_if = "String::is_empty")]
85 pub close_reason: String,
86}
87
88fn is_zero(v: &i64) -> bool {
89 *v == 0
90}
91fn is_zero_u64(v: &u64) -> bool {
92 *v == 0
93}
94
95impl Event {
96 pub fn new(kind: EventType, guild_id: impl Into<String>) -> Self {
98 Self {
99 op: "event".into(),
100 kind,
101 guild_id: guild_id.into(),
102 track: None,
103 encoded: String::new(),
104 instance: 0,
105 reason: None,
106 exception: None,
107 position_ms: 0,
108 sent_at: 0,
109 audible_at: 0,
110 duration_ms: 0,
111 stall_ms: 0,
112 threshold_ms: 0,
113 into: None,
114 into_ms: 0,
115 code: None,
116 terminal: None,
117 close_reason: String::new(),
118 }
119 }
120
121 pub fn with_track(mut self, t: &Track) -> Self {
123 self.encoded = t.encode();
124 self.track = Some(t.clone());
125 self
126 }
127
128 pub fn with_instance(mut self, instance: u64) -> Self {
129 self.instance = instance;
130 self
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn serializes_compactly() {
140 let e = Event::new(EventType::TrackEndEvent, "1").with_instance(3);
141 let mut e = e;
142 e.reason = Some(EndReason::LoadFailed);
143 let v = serde_json::to_value(&e).unwrap();
144 assert_eq!(v["type"], "TrackEndEvent");
145 assert_eq!(v["reason"], "loadFailed");
146 assert_eq!(v["op"], "event");
147 assert!(v.get("positionMs").is_none());
148 let p = serde_json::to_value(Event::new(EventType::PositionJump, "1")).unwrap();
149 assert_eq!(p["type"], "PositionEvent");
150 }
151}