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::VoiceServerUpdate, id::GuildId};
#[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: GuildId,
pub op: Opcode,
}
impl Destroy {
pub fn new(guild_id: GuildId) -> Self {
Self {
guild_id,
op: Opcode::Destroy,
}
}
}
impl From<GuildId> for Destroy {
fn from(guild_id: GuildId) -> 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: GuildId,
pub op: Opcode,
}
impl Equalizer {
pub fn new(guild_id: GuildId, bands: Vec<EqualizerBand>) -> Self {
Self::from((guild_id, bands))
}
}
impl From<(GuildId, Vec<EqualizerBand>)> for Equalizer {
fn from((guild_id, bands): (GuildId, 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: GuildId,
pub op: Opcode,
pub pause: bool,
}
impl Pause {
pub fn new(guild_id: GuildId, pause: bool) -> Self {
Self::from((guild_id, pause))
}
}
impl From<(GuildId, bool)> for Pause {
fn from((guild_id, pause): (GuildId, 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: GuildId,
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: GuildId,
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<(GuildId, T)> for Play {
fn from((guild_id, track): (GuildId, T)) -> Self {
Self::from((guild_id, track, None, None, true))
}
}
impl<T: Into<String>, S: Into<Option<u64>>> From<(GuildId, T, S)> for Play {
fn from((guild_id, track, start_time): (GuildId, 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<(GuildId, T, S, E)>
for Play
{
fn from((guild_id, track, start_time, end_time): (GuildId, 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<(GuildId, T, S, E, bool)>
for Play
{
fn from(
(guild_id, track, start_time, end_time, no_replace): (GuildId, 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: GuildId,
pub op: Opcode,
pub position: i64,
}
impl Seek {
pub fn new(guild_id: GuildId, position: i64) -> Self {
Self::from((guild_id, position))
}
}
impl From<(GuildId, i64)> for Seek {
fn from((guild_id, position): (GuildId, 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: GuildId,
}
impl Stop {
pub fn new(guild_id: GuildId) -> Self {
Self::from(guild_id)
}
}
impl From<GuildId> for Stop {
fn from(guild_id: GuildId) -> 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: SlimVoiceServerUpdate,
pub guild_id: GuildId,
pub op: Opcode,
pub session_id: String,
}
impl VoiceUpdate {
pub fn new(
guild_id: GuildId,
session_id: impl Into<String>,
event: SlimVoiceServerUpdate,
) -> Self {
Self::from((guild_id, session_id, event))
}
}
impl<T: Into<String>> From<(GuildId, T, SlimVoiceServerUpdate)> for VoiceUpdate {
fn from((guild_id, session_id, event): (GuildId, T, SlimVoiceServerUpdate)) -> 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 = "snake_case")]
pub struct SlimVoiceServerUpdate {
pub endpoint: Option<String>,
pub guild_id: Option<GuildId>,
pub token: String,
}
impl From<VoiceServerUpdate> for SlimVoiceServerUpdate {
fn from(update: VoiceServerUpdate) -> Self {
Self {
endpoint: update.endpoint,
guild_id: update.guild_id,
token: update.token,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct Volume {
pub guild_id: GuildId,
pub op: Opcode,
pub volume: i64,
}
impl Volume {
pub fn new(guild_id: GuildId, volume: i64) -> Self {
Self::from((guild_id, volume))
}
}
impl From<(GuildId, i64)> for Volume {
fn from((guild_id, volume): (GuildId, i64)) -> Self {
Self {
guild_id,
op: Opcode::Volume,
volume,
}
}
}
}
pub mod incoming {
use super::Opcode;
use serde::{Deserialize, Serialize};
use twilight_model::id::GuildId;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(untagged)]
pub enum IncomingEvent {
PlayerUpdate(PlayerUpdate),
Stats(Stats),
TrackEnd(TrackEnd),
TrackStart(TrackStart),
}
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: GuildId,
pub op: Opcode,
pub state: PlayerUpdateState,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct PlayerUpdateState {
pub position: i64,
pub time: 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, 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,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct TrackEnd {
pub guild_id: GuildId,
#[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: GuildId,
#[serde(rename = "type")]
pub kind: TrackEventType,
pub op: Opcode,
pub track: String,
}
}
pub use self::{
incoming::{
IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames, StatsMemory,
TrackEnd, TrackEventType, TrackStart,
},
outgoing::{
Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, SlimVoiceServerUpdate,
Stop, VoiceUpdate, Volume,
},
};
#[cfg(test)]
mod tests {
use super::{
incoming::{
IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrames,
StatsMemory, TrackEnd, TrackEventType, TrackStart,
},
outgoing::{
Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek,
SlimVoiceServerUpdate, 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::id::GuildId;
assert_fields!(Destroy: guild_id, op);
assert_impl_all!(
Destroy: Clone,
Debug,
Deserialize<'static>,
Eq,
From<GuildId>,
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<(GuildId, 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<(GuildId, 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<(GuildId, String)>,
From<(GuildId, String, Option<u64>)>,
From<(GuildId, String, u64)>,
From<(GuildId, String, Option<u64>, Option<u64>)>,
From<(GuildId, String, Option<u64>, u64)>,
From<(GuildId, String, u64, Option<u64>)>,
From<(GuildId, String, u64, u64)>,
From<(GuildId, String, Option<u64>, Option<u64>, bool)>,
From<(GuildId, String, Option<u64>, u64, bool)>,
From<(GuildId, String, u64, Option<u64>, bool)>,
From<(GuildId, 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<(GuildId, i64)>,
PartialEq,
Send,
Serialize,
Sync,
);
assert_fields!(SlimVoiceServerUpdate: endpoint, guild_id, token);
assert_impl_all!(
SlimVoiceServerUpdate: Clone,
Debug,
Deserialize<'static>,
Eq,
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<GuildId>,
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!(VoiceUpdate: event, guild_id, op, session_id);
assert_impl_all!(
VoiceUpdate: Clone,
Debug,
Deserialize<'static>,
Eq,
From<(GuildId, String, SlimVoiceServerUpdate)>,
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() {
let expected = Stats {
cpu: StatsCpu {
cores: 4,
lavalink_load: 0.27611940298507465,
system_load: 0.1953805363788359,
},
frames: None,
memory: StatsMemory {
allocated: 62914560,
free: 27664576,
reservable: 4294967296,
used: 35249984,
},
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(0.27611940298507465),
Token::Str("systemLoad"),
Token::F64(0.1953805363788359),
Token::StructEnd,
Token::Str("memory"),
Token::Struct {
name: "StatsMemory",
len: 4,
},
Token::Str("allocated"),
Token::U64(62914560),
Token::Str("free"),
Token::U64(27664576),
Token::Str("reservable"),
Token::U64(4294967296),
Token::Str("used"),
Token::U64(35249984),
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,
],
);
}
}