twilight_lavalink/
model.rs

1//! Models to (de)serialize incoming/outgoing websocket events and HTTP
2//! responses.
3
4use serde::{Deserialize, Serialize};
5
6/// The type of event that something is.
7#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
8#[non_exhaustive]
9#[serde(rename_all = "camelCase")]
10pub enum Opcode {
11    /// Destroy a player from a node.
12    Destroy,
13    /// Equalize a player.
14    Equalizer,
15    /// Meta information about a track starting or ending.
16    Event,
17    /// Pause a player.
18    Pause,
19    /// Play a track.
20    Play,
21    /// An update about a player's current track.
22    PlayerUpdate,
23    /// Seek a player's active track to a new position.
24    Seek,
25    /// Updated statistics about a node.
26    Stats,
27    /// Stop a player.
28    Stop,
29    /// A combined voice server and voice state update.
30    VoiceUpdate,
31    /// Set the volume of a player.
32    Volume,
33}
34
35pub mod outgoing {
36    //! Events that clients send to Lavalink.
37
38    use super::Opcode;
39    use serde::{Deserialize, Serialize};
40    use twilight_model::{
41        gateway::payload::incoming::VoiceServerUpdate,
42        id::{marker::GuildMarker, Id},
43    };
44
45    /// An outgoing event to send to Lavalink.
46    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
47    #[non_exhaustive]
48    #[serde(untagged)]
49    pub enum OutgoingEvent {
50        /// Destroy a player for a guild.
51        Destroy(Destroy),
52        /// Equalize a player.
53        Equalizer(Equalizer),
54        /// Pause or unpause a player.
55        Pause(Pause),
56        /// Play a track.
57        Play(Play),
58        /// Seek a player's active track to a new position.
59        Seek(Seek),
60        /// Stop a player.
61        Stop(Stop),
62        /// A combined voice server and voice state update.
63        VoiceUpdate(VoiceUpdate),
64        /// Set the volume of a player.
65        Volume(Volume),
66    }
67
68    impl From<Destroy> for OutgoingEvent {
69        fn from(event: Destroy) -> OutgoingEvent {
70            Self::Destroy(event)
71        }
72    }
73
74    impl From<Equalizer> for OutgoingEvent {
75        fn from(event: Equalizer) -> OutgoingEvent {
76            Self::Equalizer(event)
77        }
78    }
79
80    impl From<Pause> for OutgoingEvent {
81        fn from(event: Pause) -> OutgoingEvent {
82            Self::Pause(event)
83        }
84    }
85
86    impl From<Play> for OutgoingEvent {
87        fn from(event: Play) -> OutgoingEvent {
88            Self::Play(event)
89        }
90    }
91
92    impl From<Seek> for OutgoingEvent {
93        fn from(event: Seek) -> OutgoingEvent {
94            Self::Seek(event)
95        }
96    }
97
98    impl From<Stop> for OutgoingEvent {
99        fn from(event: Stop) -> OutgoingEvent {
100            Self::Stop(event)
101        }
102    }
103
104    impl From<VoiceUpdate> for OutgoingEvent {
105        fn from(event: VoiceUpdate) -> OutgoingEvent {
106            Self::VoiceUpdate(event)
107        }
108    }
109
110    impl From<Volume> for OutgoingEvent {
111        fn from(event: Volume) -> OutgoingEvent {
112            Self::Volume(event)
113        }
114    }
115
116    /// Destroy a player from a node.
117    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
118    #[non_exhaustive]
119    #[serde(rename_all = "camelCase")]
120    pub struct Destroy {
121        /// The guild ID of the player.
122        pub guild_id: Id<GuildMarker>,
123        /// The opcode of the event.
124        pub op: Opcode,
125    }
126
127    impl Destroy {
128        /// Create a new destroy event.
129        pub const fn new(guild_id: Id<GuildMarker>) -> Self {
130            Self {
131                guild_id,
132                op: Opcode::Destroy,
133            }
134        }
135    }
136
137    impl From<Id<GuildMarker>> for Destroy {
138        fn from(guild_id: Id<GuildMarker>) -> Self {
139            Self {
140                guild_id,
141                op: Opcode::Destroy,
142            }
143        }
144    }
145
146    /// Equalize a player.
147    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
148    #[non_exhaustive]
149    #[serde(rename_all = "camelCase")]
150    pub struct Equalizer {
151        /// The bands to use as part of the equalizer.
152        pub bands: Vec<EqualizerBand>,
153        /// The guild ID of the player.
154        pub guild_id: Id<GuildMarker>,
155        /// The opcode of the event.
156        pub op: Opcode,
157    }
158
159    impl Equalizer {
160        /// Create a new equalizer event.
161        pub fn new(guild_id: Id<GuildMarker>, bands: Vec<EqualizerBand>) -> Self {
162            Self::from((guild_id, bands))
163        }
164    }
165
166    impl From<(Id<GuildMarker>, Vec<EqualizerBand>)> for Equalizer {
167        fn from((guild_id, bands): (Id<GuildMarker>, Vec<EqualizerBand>)) -> Self {
168            Self {
169                bands,
170                guild_id,
171                op: Opcode::Equalizer,
172            }
173        }
174    }
175
176    /// A band of the equalizer event.
177    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
178    #[non_exhaustive]
179    #[serde(rename_all = "camelCase")]
180    pub struct EqualizerBand {
181        /// The band.
182        pub band: i64,
183        /// The gain.
184        pub gain: f64,
185    }
186
187    impl EqualizerBand {
188        /// Create a new equalizer band.
189        pub fn new(band: i64, gain: f64) -> Self {
190            Self::from((band, gain))
191        }
192    }
193
194    impl From<(i64, f64)> for EqualizerBand {
195        fn from((band, gain): (i64, f64)) -> Self {
196            Self { band, gain }
197        }
198    }
199
200    /// Pause or unpause a player.
201    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
202    #[non_exhaustive]
203    #[serde(rename_all = "camelCase")]
204    pub struct Pause {
205        /// The guild ID of the player.
206        pub guild_id: Id<GuildMarker>,
207        /// The opcode of the event.
208        pub op: Opcode,
209        /// Whether to pause the player.
210        ///
211        /// Set to `true` to pause or `false` to resume.
212        pub pause: bool,
213    }
214
215    impl Pause {
216        /// Create a new pause event.
217        ///
218        /// Set to `true` to pause the player or `false` to resume it.
219        pub fn new(guild_id: Id<GuildMarker>, pause: bool) -> Self {
220            Self::from((guild_id, pause))
221        }
222    }
223
224    impl From<(Id<GuildMarker>, bool)> for Pause {
225        fn from((guild_id, pause): (Id<GuildMarker>, bool)) -> Self {
226            Self {
227                guild_id,
228                op: Opcode::Pause,
229                pause,
230            }
231        }
232    }
233
234    /// Play a track, optionally specifying to not skip the current track.
235    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
236    #[non_exhaustive]
237    #[serde(rename_all = "camelCase")]
238    pub struct Play {
239        /// The position in milliseconds to end the track.
240        ///
241        /// This currently [does nothing] as of this writing.
242        ///
243        /// [does nothing]: https://github.com/freyacodes/Lavalink/issues/179
244        #[serde(skip_serializing_if = "Option::is_none")]
245        pub end_time: Option<u64>,
246        /// The guild ID of the player.
247        pub guild_id: Id<GuildMarker>,
248        /// Whether or not to replace the currently playing track with this new
249        /// track.
250        ///
251        /// Set to `true` to keep playing the current playing track, or `false`
252        /// to replace the current playing track with a new one.
253        pub no_replace: bool,
254        /// The opcode of the event.
255        pub op: Opcode,
256        /// The position in milliseconds to start the track from.
257        ///
258        /// For example, set to 5000 to start the track 5 seconds in.
259        #[serde(skip_serializing_if = "Option::is_none")]
260        pub start_time: Option<u64>,
261        /// The base64 track information.
262        pub track: String,
263    }
264
265    impl Play {
266        /// Create a new play event.
267        pub fn new(
268            guild_id: Id<GuildMarker>,
269            track: impl Into<String>,
270            start_time: impl Into<Option<u64>>,
271            end_time: impl Into<Option<u64>>,
272            no_replace: bool,
273        ) -> Self {
274            Self::from((guild_id, track, start_time, end_time, no_replace))
275        }
276    }
277
278    impl<T: Into<String>> From<(Id<GuildMarker>, T)> for Play {
279        fn from((guild_id, track): (Id<GuildMarker>, T)) -> Self {
280            Self::from((guild_id, track, None, None, true))
281        }
282    }
283
284    impl<T: Into<String>, S: Into<Option<u64>>> From<(Id<GuildMarker>, T, S)> for Play {
285        fn from((guild_id, track, start_time): (Id<GuildMarker>, T, S)) -> Self {
286            Self::from((guild_id, track, start_time, None, true))
287        }
288    }
289
290    impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>>
291        From<(Id<GuildMarker>, T, S, E)> for Play
292    {
293        fn from((guild_id, track, start_time, end_time): (Id<GuildMarker>, T, S, E)) -> Self {
294            Self::from((guild_id, track, start_time, end_time, true))
295        }
296    }
297
298    impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>>
299        From<(Id<GuildMarker>, T, S, E, bool)> for Play
300    {
301        fn from(
302            (guild_id, track, start_time, end_time, no_replace): (Id<GuildMarker>, T, S, E, bool),
303        ) -> Self {
304            Self {
305                end_time: end_time.into(),
306                guild_id,
307                no_replace,
308                op: Opcode::Play,
309                start_time: start_time.into(),
310                track: track.into(),
311            }
312        }
313    }
314
315    /// Seek a player's active track to a new position.
316    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
317    #[non_exhaustive]
318    #[serde(rename_all = "camelCase")]
319    pub struct Seek {
320        /// The guild ID of the player.
321        pub guild_id: Id<GuildMarker>,
322        /// The opcode of the event.
323        pub op: Opcode,
324        /// The position in milliseconds to seek to.
325        pub position: i64,
326    }
327
328    impl Seek {
329        /// Create a new seek event.
330        pub fn new(guild_id: Id<GuildMarker>, position: i64) -> Self {
331            Self::from((guild_id, position))
332        }
333    }
334
335    impl From<(Id<GuildMarker>, i64)> for Seek {
336        fn from((guild_id, position): (Id<GuildMarker>, i64)) -> Self {
337            Self {
338                guild_id,
339                op: Opcode::Seek,
340                position,
341            }
342        }
343    }
344
345    /// Stop a player.
346    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
347    #[non_exhaustive]
348    #[serde(rename_all = "camelCase")]
349    pub struct Stop {
350        /// The opcode of the event.
351        pub op: Opcode,
352        /// The guild ID of the player.
353        pub guild_id: Id<GuildMarker>,
354    }
355
356    impl Stop {
357        /// Create a new stop event.
358        pub fn new(guild_id: Id<GuildMarker>) -> Self {
359            Self::from(guild_id)
360        }
361    }
362
363    impl From<Id<GuildMarker>> for Stop {
364        fn from(guild_id: Id<GuildMarker>) -> Self {
365            Self {
366                guild_id,
367                op: Opcode::Stop,
368            }
369        }
370    }
371
372    /// A combined voice server and voice state update.
373    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
374    #[non_exhaustive]
375    #[serde(rename_all = "camelCase")]
376    pub struct VoiceUpdate {
377        /// The inner event being forwarded to a node.
378        pub event: VoiceServerUpdate,
379        /// The guild ID of the player.
380        pub guild_id: Id<GuildMarker>,
381        /// The opcode of the event.
382        pub op: Opcode,
383        /// The session ID of the voice channel.
384        pub session_id: String,
385    }
386
387    impl VoiceUpdate {
388        /// Create a new voice update event.
389        pub fn new(
390            guild_id: Id<GuildMarker>,
391            session_id: impl Into<String>,
392            event: VoiceServerUpdate,
393        ) -> Self {
394            Self::from((guild_id, session_id, event))
395        }
396    }
397
398    impl<T: Into<String>> From<(Id<GuildMarker>, T, VoiceServerUpdate)> for VoiceUpdate {
399        fn from((guild_id, session_id, event): (Id<GuildMarker>, T, VoiceServerUpdate)) -> Self {
400            Self {
401                event,
402                guild_id,
403                op: Opcode::VoiceUpdate,
404                session_id: session_id.into(),
405            }
406        }
407    }
408
409    /// Set the volume of a player.
410    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
411    #[non_exhaustive]
412    #[serde(rename_all = "camelCase")]
413    pub struct Volume {
414        /// The guild ID of the player.
415        pub guild_id: Id<GuildMarker>,
416        /// The opcode of the event.
417        pub op: Opcode,
418        /// The volume of the player from 0 to 1000. 100 is the default.
419        pub volume: i64,
420    }
421
422    impl Volume {
423        /// Create a new volume event.
424        pub fn new(guild_id: Id<GuildMarker>, volume: i64) -> Self {
425            Self::from((guild_id, volume))
426        }
427    }
428
429    impl From<(Id<GuildMarker>, i64)> for Volume {
430        fn from((guild_id, volume): (Id<GuildMarker>, i64)) -> Self {
431            Self {
432                guild_id,
433                op: Opcode::Volume,
434                volume,
435            }
436        }
437    }
438}
439
440pub mod incoming {
441    //! Events that Lavalink sends to clients.
442
443    use super::Opcode;
444    use serde::{Deserialize, Serialize};
445    use twilight_model::id::{marker::GuildMarker, Id};
446
447    /// An incoming event from a Lavalink node.
448    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
449    #[non_exhaustive]
450    #[serde(untagged)]
451    pub enum IncomingEvent {
452        /// An update about the information of a player.
453        PlayerUpdate(PlayerUpdate),
454        /// New statistics about a node and its host.
455        Stats(Stats),
456        /// A track ended.
457        TrackEnd(TrackEnd),
458        /// A track started.
459        TrackStart(TrackStart),
460        /// The voice websocket connection was closed.
461        WeboscketClosed(WebsocketClosed),
462    }
463
464    impl From<PlayerUpdate> for IncomingEvent {
465        fn from(event: PlayerUpdate) -> IncomingEvent {
466            Self::PlayerUpdate(event)
467        }
468    }
469
470    impl From<Stats> for IncomingEvent {
471        fn from(event: Stats) -> IncomingEvent {
472            Self::Stats(event)
473        }
474    }
475
476    /// An update about the information of a player.
477    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
478    #[non_exhaustive]
479    #[serde(rename_all = "camelCase")]
480    pub struct PlayerUpdate {
481        /// The guild ID of the player.
482        pub guild_id: Id<GuildMarker>,
483        /// The opcode of the event.
484        pub op: Opcode,
485        /// The new state of the player.
486        pub state: PlayerUpdateState,
487    }
488
489    /// New statistics about a node and its host.
490    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
491    #[non_exhaustive]
492    #[serde(rename_all = "camelCase")]
493    pub struct PlayerUpdateState {
494        /// True when the player is connected to the voice gateway.
495        pub connected: bool,
496        /// Unix timestamp of the player in milliseconds.
497        pub time: i64,
498        /// Track position in milliseconds. None if not playing anything.
499        pub position: Option<i64>,
500    }
501
502    /// Statistics about a node and its host.
503    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
504    #[non_exhaustive]
505    #[serde(rename_all = "camelCase")]
506    pub struct Stats {
507        /// CPU information about the node's host.
508        pub cpu: StatsCpu,
509        /// Statistics about audio frames.
510        #[serde(rename = "frameStats", skip_serializing_if = "Option::is_none")]
511        pub frames: Option<StatsFrames>,
512        /// Memory information about the node's host.
513        pub memory: StatsMemory,
514        /// The current number of total players (active and not active) within
515        /// the node.
516        pub players: u64,
517        /// The current number of active players within the node.
518        pub playing_players: u64,
519        /// The opcode of the event.
520        pub op: Opcode,
521        /// The uptime of the Lavalink server in seconds.
522        pub uptime: u64,
523    }
524
525    /// CPU information about a node and its host.
526    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
527    #[non_exhaustive]
528    #[serde(rename_all = "camelCase")]
529    pub struct StatsCpu {
530        /// The number of CPU cores.
531        pub cores: usize,
532        /// The load of the Lavalink server.
533        pub lavalink_load: f64,
534        /// The load of the system as a whole.
535        pub system_load: f64,
536    }
537
538    /// CPU information about a node and its host.
539    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
540    #[non_exhaustive]
541    #[serde(rename_all = "camelCase")]
542    pub struct StatsFrames {
543        /// The number of CPU cores.
544        pub sent: u64,
545        /// The load of the Lavalink server.
546        pub nulled: u64,
547        /// The load of the system as a whole.
548        pub deficit: u64,
549    }
550
551    /// Memory information about a node and its host.
552    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
553    #[non_exhaustive]
554    #[serde(rename_all = "camelCase")]
555    pub struct StatsMemory {
556        /// The number of bytes allocated.
557        pub allocated: u64,
558        /// The number of bytes free.
559        pub free: u64,
560        /// The number of bytes reservable.
561        pub reservable: u64,
562        /// The number of bytes used.
563        pub used: u64,
564    }
565
566    /// The type of track event that was received.
567    #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
568    #[non_exhaustive]
569    pub enum TrackEventType {
570        /// A track for a player ended.
571        #[serde(rename = "TrackEndEvent")]
572        End,
573        /// A track for a player started.
574        #[serde(rename = "TrackStartEvent")]
575        Start,
576        /// The voice websocket connection to Discord has been closed.
577        #[serde(rename = "WebSocketClosedEvent")]
578        WebsocketClosed,
579    }
580
581    /// A track ended.
582    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
583    #[non_exhaustive]
584    #[serde(rename_all = "camelCase")]
585    pub struct TrackEnd {
586        /// The guild ID of the player.
587        pub guild_id: Id<GuildMarker>,
588        /// The type of track event.
589        #[serde(rename = "type")]
590        pub kind: TrackEventType,
591        /// The opcode of the event.
592        pub op: Opcode,
593        /// The reason that the track ended.
594        ///
595        /// For example, this may be `"FINISHED"`.
596        pub reason: String,
597        /// The base64 track that was affected.
598        pub track: String,
599    }
600
601    /// A track started.
602    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
603    #[non_exhaustive]
604    #[serde(rename_all = "camelCase")]
605    pub struct TrackStart {
606        /// The guild ID of the player.
607        pub guild_id: Id<GuildMarker>,
608        /// The type of track event.
609        #[serde(rename = "type")]
610        pub kind: TrackEventType,
611        /// The opcode of the event.
612        pub op: Opcode,
613        /// The base64 track that was affected.
614        pub track: String,
615    }
616
617    /// The voice websocket connection to Discord has been closed.
618    #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
619    #[non_exhaustive]
620    #[serde(rename_all = "camelCase")]
621    pub struct WebsocketClosed {
622        /// Guild ID of the associated player.
623        pub guild_id: Id<GuildMarker>,
624        /// Type of track event.
625        #[serde(rename = "type")]
626        pub kind: TrackEventType,
627        /// Lavalink websocket opcode of the event.
628        pub op: Opcode,
629        /// Discord websocket opcode that closed the connection.
630        pub code: u64,
631        /// True if Discord closed the connection, false if Lavalink closed it.
632        pub by_remote: bool,
633        /// Reason the connection was closed.
634        pub reason: String,
635    }
636}
637
638pub use self::{
639    incoming::{
640        IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames, StatsMemory,
641        TrackEnd, TrackEventType, TrackStart, WebsocketClosed,
642    },
643    outgoing::{
644        Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, Stop, VoiceUpdate,
645        Volume,
646    },
647};
648
649#[cfg(test)]
650mod tests {
651    use super::{
652        incoming::{
653            IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames,
654            StatsMemory, TrackEnd, TrackEventType, TrackStart, WebsocketClosed,
655        },
656        outgoing::{
657            Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, Stop, VoiceUpdate,
658            Volume,
659        },
660        Opcode,
661    };
662    use serde::{Deserialize, Serialize};
663    use serde_test::Token;
664    use static_assertions::{assert_fields, assert_impl_all};
665    use std::fmt::Debug;
666    use twilight_model::{
667        gateway::payload::incoming::VoiceServerUpdate,
668        id::{marker::GuildMarker, Id},
669    };
670
671    assert_fields!(Destroy: guild_id, op);
672    assert_impl_all!(
673        Destroy: Clone,
674        Debug,
675        Deserialize<'static>,
676        Eq,
677        From<Id<GuildMarker>>,
678        PartialEq,
679        Send,
680        Serialize,
681        Sync,
682    );
683    assert_fields!(EqualizerBand: band, gain);
684    assert_impl_all!(
685        EqualizerBand: Clone,
686        Debug,
687        Deserialize<'static>,
688        From<(i64, f64)>,
689        PartialEq,
690        Send,
691        Serialize,
692        Sync,
693    );
694    assert_fields!(Equalizer: bands, guild_id, op);
695    assert_impl_all!(
696        Equalizer: Clone,
697        Debug,
698        Deserialize<'static>,
699        From<(Id<GuildMarker>, Vec<EqualizerBand>)>,
700        PartialEq,
701        Send,
702        Serialize,
703        Sync,
704    );
705    assert_impl_all!(
706        IncomingEvent: Clone,
707        Debug,
708        Deserialize<'static>,
709        From<PlayerUpdate>,
710        From<Stats>,
711        PartialEq,
712        Send,
713        Serialize,
714        Sync,
715    );
716    assert_impl_all!(
717        OutgoingEvent: Clone,
718        Debug,
719        Deserialize<'static>,
720        From<Destroy>,
721        From<Equalizer>,
722        From<Pause>,
723        From<Play>,
724        From<Seek>,
725        From<Stop>,
726        From<VoiceUpdate>,
727        From<Volume>,
728        PartialEq,
729        Send,
730        Serialize,
731        Sync,
732    );
733    assert_fields!(Pause: guild_id, op, pause);
734    assert_impl_all!(
735        Pause: Clone,
736        Debug,
737        Deserialize<'static>,
738        Eq,
739        From<(Id<GuildMarker>, bool)>,
740        PartialEq,
741        Send,
742        Serialize,
743        Sync,
744    );
745    assert_fields!(PlayerUpdateState: position, time);
746    assert_impl_all!(
747        PlayerUpdateState: Clone,
748        Debug,
749        Deserialize<'static>,
750        Eq,
751        PartialEq,
752        Send,
753        Serialize,
754        Sync,
755    );
756    assert_fields!(PlayerUpdate: guild_id, op, state);
757    assert_impl_all!(
758        PlayerUpdate: Clone,
759        Debug,
760        Deserialize<'static>,
761        Eq,
762        PartialEq,
763        Send,
764        Serialize,
765        Sync,
766    );
767    assert_fields!(Play: end_time, guild_id, no_replace, op, start_time, track);
768    assert_impl_all!(
769        Play: Clone,
770        Debug,
771        Deserialize<'static>,
772        Eq,
773        From<(Id<GuildMarker>, String)>,
774        From<(Id<GuildMarker>, String, Option<u64>)>,
775        From<(Id<GuildMarker>, String, u64)>,
776        From<(Id<GuildMarker>, String, Option<u64>, Option<u64>)>,
777        From<(Id<GuildMarker>, String, Option<u64>, u64)>,
778        From<(Id<GuildMarker>, String, u64, Option<u64>)>,
779        From<(Id<GuildMarker>, String, u64, u64)>,
780        From<(Id<GuildMarker>, String, Option<u64>, Option<u64>, bool)>,
781        From<(Id<GuildMarker>, String, Option<u64>, u64, bool)>,
782        From<(Id<GuildMarker>, String, u64, Option<u64>, bool)>,
783        From<(Id<GuildMarker>, String, u64, u64, bool)>,
784        PartialEq,
785        Send,
786        Serialize,
787        Sync,
788    );
789    assert_fields!(Seek: guild_id, op, position);
790    assert_impl_all!(
791        Seek: Clone,
792        Debug,
793        Deserialize<'static>,
794        Eq,
795        From<(Id<GuildMarker>, i64)>,
796        PartialEq,
797        Send,
798        Serialize,
799        Sync,
800    );
801    assert_fields!(
802        Stats: cpu,
803        frames,
804        memory,
805        players,
806        playing_players,
807        op,
808        uptime
809    );
810    assert_impl_all!(
811        Stats: Clone,
812        Debug,
813        Deserialize<'static>,
814        PartialEq,
815        Send,
816        Serialize,
817        Sync,
818    );
819    assert_fields!(StatsCpu: cores, lavalink_load, system_load);
820    assert_impl_all!(
821        StatsCpu: Clone,
822        Debug,
823        Deserialize<'static>,
824        PartialEq,
825        Send,
826        Serialize,
827        Sync,
828    );
829    assert_fields!(StatsFrames: deficit, nulled, sent);
830    assert_impl_all!(
831        StatsFrames: Clone,
832        Debug,
833        Deserialize<'static>,
834        PartialEq,
835        Send,
836        Serialize,
837        Sync,
838    );
839    assert_fields!(StatsMemory: allocated, free, reservable, used);
840    assert_impl_all!(
841        StatsMemory: Clone,
842        Debug,
843        Deserialize<'static>,
844        PartialEq,
845        Send,
846        Serialize,
847        Sync,
848    );
849    assert_fields!(Stop: guild_id, op);
850    assert_impl_all!(
851        Stop: Clone,
852        Debug,
853        Deserialize<'static>,
854        Eq,
855        From<Id<GuildMarker>>,
856        PartialEq,
857        Send,
858        Serialize,
859        Sync,
860    );
861    assert_fields!(TrackEnd: guild_id, kind, op, reason, track);
862    assert_impl_all!(
863        TrackEnd: Clone,
864        Debug,
865        Deserialize<'static>,
866        PartialEq,
867        Send,
868        Serialize,
869        Sync,
870    );
871    assert_impl_all!(
872        TrackEventType: Clone,
873        Copy,
874        Debug,
875        Deserialize<'static>,
876        PartialEq,
877        Send,
878        Serialize,
879        Sync,
880    );
881    assert_fields!(TrackStart: guild_id, kind, op, track);
882    assert_impl_all!(
883        TrackStart: Clone,
884        Debug,
885        Deserialize<'static>,
886        PartialEq,
887        Send,
888        Serialize,
889        Sync,
890    );
891    assert_fields!(WebsocketClosed: guild_id, kind, op, code, reason, by_remote);
892    assert_impl_all!(
893        WebsocketClosed: Clone,
894        Debug,
895        Deserialize<'static>,
896        PartialEq,
897        Send,
898        Serialize,
899        Sync,
900    );
901    assert_fields!(VoiceUpdate: event, guild_id, op, session_id);
902    assert_impl_all!(
903        VoiceUpdate: Clone,
904        Debug,
905        Deserialize<'static>,
906        Eq,
907        From<(Id<GuildMarker>, String, VoiceServerUpdate)>,
908        PartialEq,
909        Send,
910        Serialize,
911        Sync,
912    );
913    assert_fields!(Volume: guild_id, op, volume);
914    assert_impl_all!(
915        Volume: Clone,
916        Debug,
917        Deserialize<'static>,
918        Eq,
919        PartialEq,
920        Send,
921        Serialize,
922        Sync,
923    );
924
925    #[test]
926    fn stats_frames_not_provided() {
927        const LAVALINK_LOAD: f64 = 0.276_119_402_985_074_65;
928        const MEM_ALLOCATED: u64 = 62_914_560;
929        const MEM_FREE: u64 = 27_664_576;
930        const MEM_RESERVABLE: u64 = 4_294_967_296;
931        const MEM_USED: u64 = 35_249_984;
932        const SYSTEM_LOAD: f64 = 0.195_380_536_378_835_9;
933
934        let expected = Stats {
935            cpu: StatsCpu {
936                cores: 4,
937                lavalink_load: LAVALINK_LOAD,
938                system_load: SYSTEM_LOAD,
939            },
940            frames: None,
941            memory: StatsMemory {
942                allocated: MEM_ALLOCATED,
943                free: MEM_FREE,
944                reservable: MEM_RESERVABLE,
945                used: MEM_USED,
946            },
947            players: 0,
948            playing_players: 0,
949            op: Opcode::Stats,
950            uptime: 18589,
951        };
952
953        serde_test::assert_de_tokens(
954            &expected,
955            &[
956                Token::Struct {
957                    name: "Stats",
958                    len: 6,
959                },
960                Token::Str("cpu"),
961                Token::Struct {
962                    name: "StatsCpu",
963                    len: 3,
964                },
965                Token::Str("cores"),
966                Token::U64(4),
967                Token::Str("lavalinkLoad"),
968                Token::F64(LAVALINK_LOAD),
969                Token::Str("systemLoad"),
970                Token::F64(SYSTEM_LOAD),
971                Token::StructEnd,
972                Token::Str("memory"),
973                Token::Struct {
974                    name: "StatsMemory",
975                    len: 4,
976                },
977                Token::Str("allocated"),
978                Token::U64(MEM_ALLOCATED),
979                Token::Str("free"),
980                Token::U64(MEM_FREE),
981                Token::Str("reservable"),
982                Token::U64(MEM_RESERVABLE),
983                Token::Str("used"),
984                Token::U64(MEM_USED),
985                Token::StructEnd,
986                Token::Str("op"),
987                Token::UnitVariant {
988                    name: "Opcode",
989                    variant: "stats",
990                },
991                Token::Str("players"),
992                Token::U64(0),
993                Token::Str("playingPlayers"),
994                Token::U64(0),
995                Token::Str("uptime"),
996                Token::U64(18589),
997                Token::StructEnd,
998            ],
999        );
1000    }
1001}