use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub enum Opcode {
Destroy,
Equalizer,
Event,
Pause,
Play,
PlayerUpdate,
Seek,
Stats,
Stop,
VoiceUpdate,
Volume,
}
pub mod outgoing {
use super::Opcode;
use serde::{Deserialize, Serialize};
use twilight_model::{
gateway::payload::incoming::VoiceServerUpdate,
id::{marker::GuildMarker, Id},
};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(untagged)]
pub enum OutgoingEvent {
Destroy(Destroy),
Equalizer(Equalizer),
Pause(Pause),
Play(Play),
Seek(Seek),
Stop(Stop),
VoiceUpdate(VoiceUpdate),
Volume(Volume),
}
impl From<Destroy> for OutgoingEvent {
fn from(event: Destroy) -> OutgoingEvent {
Self::Destroy(event)
}
}
impl From<Equalizer> for OutgoingEvent {
fn from(event: Equalizer) -> OutgoingEvent {
Self::Equalizer(event)
}
}
impl From<Pause> for OutgoingEvent {
fn from(event: Pause) -> OutgoingEvent {
Self::Pause(event)
}
}
impl From<Play> for OutgoingEvent {
fn from(event: Play) -> OutgoingEvent {
Self::Play(event)
}
}
impl From<Seek> for OutgoingEvent {
fn from(event: Seek) -> OutgoingEvent {
Self::Seek(event)
}
}
impl From<Stop> for OutgoingEvent {
fn from(event: Stop) -> OutgoingEvent {
Self::Stop(event)
}
}
impl From<VoiceUpdate> for OutgoingEvent {
fn from(event: VoiceUpdate) -> OutgoingEvent {
Self::VoiceUpdate(event)
}
}
impl From<Volume> for OutgoingEvent {
fn from(event: Volume) -> OutgoingEvent {
Self::Volume(event)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Destroy {
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
}
impl Destroy {
pub const fn new(guild_id: Id<GuildMarker>) -> Self {
Self {
guild_id,
op: Opcode::Destroy,
}
}
}
impl From<Id<GuildMarker>> for Destroy {
fn from(guild_id: Id<GuildMarker>) -> Self {
Self {
guild_id,
op: Opcode::Destroy,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Equalizer {
pub bands: Vec<EqualizerBand>,
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
}
impl Equalizer {
pub fn new(guild_id: Id<GuildMarker>, bands: Vec<EqualizerBand>) -> Self {
Self::from((guild_id, bands))
}
}
impl From<(Id<GuildMarker>, Vec<EqualizerBand>)> for Equalizer {
fn from((guild_id, bands): (Id<GuildMarker>, Vec<EqualizerBand>)) -> Self {
Self {
bands,
guild_id,
op: Opcode::Equalizer,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct EqualizerBand {
pub band: i64,
pub gain: f64,
}
impl EqualizerBand {
pub fn new(band: i64, gain: f64) -> Self {
Self::from((band, gain))
}
}
impl From<(i64, f64)> for EqualizerBand {
fn from((band, gain): (i64, f64)) -> Self {
Self { band, gain }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Pause {
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
pub pause: bool,
}
impl Pause {
pub fn new(guild_id: Id<GuildMarker>, pause: bool) -> Self {
Self::from((guild_id, pause))
}
}
impl From<(Id<GuildMarker>, bool)> for Pause {
fn from((guild_id, pause): (Id<GuildMarker>, bool)) -> Self {
Self {
guild_id,
op: Opcode::Pause,
pause,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Play {
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<u64>,
pub guild_id: Id<GuildMarker>,
pub no_replace: bool,
pub op: Opcode,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<u64>,
pub track: String,
}
impl Play {
pub fn new(
guild_id: Id<GuildMarker>,
track: impl Into<String>,
start_time: impl Into<Option<u64>>,
end_time: impl Into<Option<u64>>,
no_replace: bool,
) -> Self {
Self::from((guild_id, track, start_time, end_time, no_replace))
}
}
impl<T: Into<String>> From<(Id<GuildMarker>, T)> for Play {
fn from((guild_id, track): (Id<GuildMarker>, T)) -> Self {
Self::from((guild_id, track, None, None, true))
}
}
impl<T: Into<String>, S: Into<Option<u64>>> From<(Id<GuildMarker>, T, S)> for Play {
fn from((guild_id, track, start_time): (Id<GuildMarker>, T, S)) -> Self {
Self::from((guild_id, track, start_time, None, true))
}
}
impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>>
From<(Id<GuildMarker>, T, S, E)> for Play
{
fn from((guild_id, track, start_time, end_time): (Id<GuildMarker>, T, S, E)) -> Self {
Self::from((guild_id, track, start_time, end_time, true))
}
}
impl<T: Into<String>, S: Into<Option<u64>>, E: Into<Option<u64>>>
From<(Id<GuildMarker>, T, S, E, bool)> for Play
{
fn from(
(guild_id, track, start_time, end_time, no_replace): (Id<GuildMarker>, T, S, E, bool),
) -> Self {
Self {
end_time: end_time.into(),
guild_id,
no_replace,
op: Opcode::Play,
start_time: start_time.into(),
track: track.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Seek {
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
pub position: i64,
}
impl Seek {
pub fn new(guild_id: Id<GuildMarker>, position: i64) -> Self {
Self::from((guild_id, position))
}
}
impl From<(Id<GuildMarker>, i64)> for Seek {
fn from((guild_id, position): (Id<GuildMarker>, i64)) -> Self {
Self {
guild_id,
op: Opcode::Seek,
position,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Stop {
pub op: Opcode,
pub guild_id: Id<GuildMarker>,
}
impl Stop {
pub fn new(guild_id: Id<GuildMarker>) -> Self {
Self::from(guild_id)
}
}
impl From<Id<GuildMarker>> for Stop {
fn from(guild_id: Id<GuildMarker>) -> Self {
Self {
guild_id,
op: Opcode::Stop,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct VoiceUpdate {
pub event: VoiceServerUpdate,
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
pub session_id: String,
}
impl VoiceUpdate {
pub fn new(
guild_id: Id<GuildMarker>,
session_id: impl Into<String>,
event: VoiceServerUpdate,
) -> Self {
Self::from((guild_id, session_id, event))
}
}
impl<T: Into<String>> From<(Id<GuildMarker>, T, VoiceServerUpdate)> for VoiceUpdate {
fn from((guild_id, session_id, event): (Id<GuildMarker>, T, VoiceServerUpdate)) -> Self {
Self {
event,
guild_id,
op: Opcode::VoiceUpdate,
session_id: session_id.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Volume {
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
pub volume: i64,
}
impl Volume {
pub fn new(guild_id: Id<GuildMarker>, volume: i64) -> Self {
Self::from((guild_id, volume))
}
}
impl From<(Id<GuildMarker>, i64)> for Volume {
fn from((guild_id, volume): (Id<GuildMarker>, i64)) -> Self {
Self {
guild_id,
op: Opcode::Volume,
volume,
}
}
}
}
pub mod incoming {
use super::Opcode;
use serde::{Deserialize, Serialize};
use twilight_model::id::{marker::GuildMarker, Id};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(untagged)]
pub enum IncomingEvent {
PlayerUpdate(PlayerUpdate),
Stats(Stats),
TrackEnd(TrackEnd),
TrackStart(TrackStart),
WeboscketClosed(WebsocketClosed),
}
impl From<PlayerUpdate> for IncomingEvent {
fn from(event: PlayerUpdate) -> IncomingEvent {
Self::PlayerUpdate(event)
}
}
impl From<Stats> for IncomingEvent {
fn from(event: Stats) -> IncomingEvent {
Self::Stats(event)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct PlayerUpdate {
pub guild_id: Id<GuildMarker>,
pub op: Opcode,
pub state: PlayerUpdateState,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct PlayerUpdateState {
pub connected: bool,
pub time: i64,
pub position: Option<i64>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Stats {
pub cpu: StatsCpu,
#[serde(rename = "frameStats", skip_serializing_if = "Option::is_none")]
pub frames: Option<StatsFrames>,
pub memory: StatsMemory,
pub players: u64,
pub playing_players: u64,
pub op: Opcode,
pub uptime: u64,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct StatsCpu {
pub cores: usize,
pub lavalink_load: f64,
pub system_load: f64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct StatsFrames {
pub sent: u64,
pub nulled: u64,
pub deficit: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct StatsMemory {
pub allocated: u64,
pub free: u64,
pub reservable: u64,
pub used: u64,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub enum TrackEventType {
#[serde(rename = "TrackEndEvent")]
End,
#[serde(rename = "TrackStartEvent")]
Start,
#[serde(rename = "WebSocketClosedEvent")]
WebsocketClosed,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct TrackEnd {
pub guild_id: Id<GuildMarker>,
#[serde(rename = "type")]
pub kind: TrackEventType,
pub op: Opcode,
pub reason: String,
pub track: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct TrackStart {
pub guild_id: Id<GuildMarker>,
#[serde(rename = "type")]
pub kind: TrackEventType,
pub op: Opcode,
pub track: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct WebsocketClosed {
pub guild_id: Id<GuildMarker>,
#[serde(rename = "type")]
pub kind: TrackEventType,
pub op: Opcode,
pub code: u64,
pub by_remote: bool,
pub reason: String,
}
}
pub use self::{
incoming::{
IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames, StatsMemory,
TrackEnd, TrackEventType, TrackStart, WebsocketClosed,
},
outgoing::{
Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, Stop, VoiceUpdate,
Volume,
},
};
#[cfg(test)]
mod tests {
use super::{
incoming::{
IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames,
StatsMemory, TrackEnd, TrackEventType, TrackStart, WebsocketClosed,
},
outgoing::{
Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, Stop, VoiceUpdate,
Volume,
},
Opcode,
};
use serde::{Deserialize, Serialize};
use serde_test::Token;
use static_assertions::{assert_fields, assert_impl_all};
use std::fmt::Debug;
use twilight_model::{
gateway::payload::incoming::VoiceServerUpdate,
id::{marker::GuildMarker, Id},
};
assert_fields!(Destroy: guild_id, op);
assert_impl_all!(
Destroy: Clone,
Debug,
Deserialize<'static>,
Eq,
From<Id<GuildMarker>>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(EqualizerBand: band, gain);
assert_impl_all!(
EqualizerBand: Clone,
Debug,
Deserialize<'static>,
From<(i64, f64)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Equalizer: bands, guild_id, op);
assert_impl_all!(
Equalizer: Clone,
Debug,
Deserialize<'static>,
From<(Id<GuildMarker>, Vec<EqualizerBand>)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_impl_all!(
IncomingEvent: Clone,
Debug,
Deserialize<'static>,
From<PlayerUpdate>,
From<Stats>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_impl_all!(
OutgoingEvent: Clone,
Debug,
Deserialize<'static>,
From<Destroy>,
From<Equalizer>,
From<Pause>,
From<Play>,
From<Seek>,
From<Stop>,
From<VoiceUpdate>,
From<Volume>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Pause: guild_id, op, pause);
assert_impl_all!(
Pause: Clone,
Debug,
Deserialize<'static>,
Eq,
From<(Id<GuildMarker>, bool)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(PlayerUpdateState: position, time);
assert_impl_all!(
PlayerUpdateState: Clone,
Debug,
Deserialize<'static>,
Eq,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(PlayerUpdate: guild_id, op, state);
assert_impl_all!(
PlayerUpdate: Clone,
Debug,
Deserialize<'static>,
Eq,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Play: end_time, guild_id, no_replace, op, start_time, track);
assert_impl_all!(
Play: Clone,
Debug,
Deserialize<'static>,
Eq,
From<(Id<GuildMarker>, String)>,
From<(Id<GuildMarker>, String, Option<u64>)>,
From<(Id<GuildMarker>, String, u64)>,
From<(Id<GuildMarker>, String, Option<u64>, Option<u64>)>,
From<(Id<GuildMarker>, String, Option<u64>, u64)>,
From<(Id<GuildMarker>, String, u64, Option<u64>)>,
From<(Id<GuildMarker>, String, u64, u64)>,
From<(Id<GuildMarker>, String, Option<u64>, Option<u64>, bool)>,
From<(Id<GuildMarker>, String, Option<u64>, u64, bool)>,
From<(Id<GuildMarker>, String, u64, Option<u64>, bool)>,
From<(Id<GuildMarker>, String, u64, u64, bool)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Seek: guild_id, op, position);
assert_impl_all!(
Seek: Clone,
Debug,
Deserialize<'static>,
Eq,
From<(Id<GuildMarker>, i64)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(
Stats: cpu,
frames,
memory,
players,
playing_players,
op,
uptime
);
assert_impl_all!(
Stats: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(StatsCpu: cores, lavalink_load, system_load);
assert_impl_all!(
StatsCpu: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(StatsFrames: deficit, nulled, sent);
assert_impl_all!(
StatsFrames: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(StatsMemory: allocated, free, reservable, used);
assert_impl_all!(
StatsMemory: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Stop: guild_id, op);
assert_impl_all!(
Stop: Clone,
Debug,
Deserialize<'static>,
Eq,
From<Id<GuildMarker>>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(TrackEnd: guild_id, kind, op, reason, track);
assert_impl_all!(
TrackEnd: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_impl_all!(
TrackEventType: Clone,
Copy,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(TrackStart: guild_id, kind, op, track);
assert_impl_all!(
TrackStart: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(WebsocketClosed: guild_id, kind, op, code, reason, by_remote);
assert_impl_all!(
WebsocketClosed: Clone,
Debug,
Deserialize<'static>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(VoiceUpdate: event, guild_id, op, session_id);
assert_impl_all!(
VoiceUpdate: Clone,
Debug,
Deserialize<'static>,
Eq,
From<(Id<GuildMarker>, String, VoiceServerUpdate)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(Volume: guild_id, op, volume);
assert_impl_all!(
Volume: Clone,
Debug,
Deserialize<'static>,
Eq,
PartialEq,
Send,
Serialize,
Sync,
);
#[test]
fn stats_frames_not_provided() {
const LAVALINK_LOAD: f64 = 0.276_119_402_985_074_65;
const MEM_ALLOCATED: u64 = 62_914_560;
const MEM_FREE: u64 = 27_664_576;
const MEM_RESERVABLE: u64 = 4_294_967_296;
const MEM_USED: u64 = 35_249_984;
const SYSTEM_LOAD: f64 = 0.195_380_536_378_835_9;
let expected = Stats {
cpu: StatsCpu {
cores: 4,
lavalink_load: LAVALINK_LOAD,
system_load: SYSTEM_LOAD,
},
frames: None,
memory: StatsMemory {
allocated: MEM_ALLOCATED,
free: MEM_FREE,
reservable: MEM_RESERVABLE,
used: MEM_USED,
},
players: 0,
playing_players: 0,
op: Opcode::Stats,
uptime: 18589,
};
serde_test::assert_de_tokens(
&expected,
&[
Token::Struct {
name: "Stats",
len: 6,
},
Token::Str("cpu"),
Token::Struct {
name: "StatsCpu",
len: 3,
},
Token::Str("cores"),
Token::U64(4),
Token::Str("lavalinkLoad"),
Token::F64(LAVALINK_LOAD),
Token::Str("systemLoad"),
Token::F64(SYSTEM_LOAD),
Token::StructEnd,
Token::Str("memory"),
Token::Struct {
name: "StatsMemory",
len: 4,
},
Token::Str("allocated"),
Token::U64(MEM_ALLOCATED),
Token::Str("free"),
Token::U64(MEM_FREE),
Token::Str("reservable"),
Token::U64(MEM_RESERVABLE),
Token::Str("used"),
Token::U64(MEM_USED),
Token::StructEnd,
Token::Str("op"),
Token::UnitVariant {
name: "Opcode",
variant: "stats",
},
Token::Str("players"),
Token::U64(0),
Token::Str("playingPlayers"),
Token::U64(0),
Token::Str("uptime"),
Token::U64(18589),
Token::StructEnd,
],
);
}
}