Update to v7

This commit is contained in:
kozabrada123 2023-10-15 11:51:59 +02:00
parent ca6d629006
commit 6ecbda1550
12 changed files with 289 additions and 31 deletions

View File

@ -34,8 +34,10 @@ impl WebSocketEvent for VoiceStateUpdate {}
/// Received to indicate which voice endpoint, token and guild_id to use; /// Received to indicate which voice endpoint, token and guild_id to use;
pub struct VoiceServerUpdate { pub struct VoiceServerUpdate {
pub token: String, pub token: String,
/// Can be None in dm calls /// The guild this voice server update is for
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
/// The private channel this voice server update is for
pub channel_id: Option<Snowflake>,
pub endpoint: Option<String>, pub endpoint: Option<String>,
} }

View File

@ -0,0 +1,34 @@
use crate::types::{Snowflake, WebSocketEvent};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)]
/// Sent when another user connects to the voice server.
///
/// Contains the user id and "flags".
///
/// Not documented anywhere, if you know what this is, please reach out
///
/// {"op":18,"d":{"user_id":"1234567890","flags":2}}
pub struct VoiceClientConnectFlags {
pub user_id: Snowflake,
// Likely some sort of bitflags
pub flags: u8,
}
impl WebSocketEvent for VoiceClientConnectFlags {}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)]
/// Sent when another user connects to the voice server.
///
/// Contains the user id and "platform".
///
/// Not documented anywhere, if you know what this is, please reach out
///
/// {"op":20,"d":{"user_id":"1234567890","platform":0}}
pub struct VoiceClientConnectPlatform {
pub user_id: Snowflake,
// Likely an enum
pub platform: u8,
}
impl WebSocketEvent for VoiceClientConnectPlatform {}

View File

@ -0,0 +1,14 @@
use crate::types::{Snowflake, WebSocketEvent};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Copy)]
/// Sent when another user disconnects from the voice server.
///
/// When received, the SSRC of the user should be discarded.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#other-client-disconnection>
pub struct VoiceClientDisconnection {
pub user_id: Snowflake,
}
impl WebSocketEvent for VoiceClientDisconnection {}

View File

@ -5,7 +5,14 @@ use serde::{Deserialize, Serialize};
/// Contains info on how often the client should send heartbeats to the server; /// Contains info on how often the client should send heartbeats to the server;
/// ///
/// Differs from the normal hello data in that discord sends heartbeat interval as a float. /// Differs from the normal hello data in that discord sends heartbeat interval as a float.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#heartbeating>
pub struct VoiceHelloData { pub struct VoiceHelloData {
/// The voice gateway version.
///
/// Note: no idea why this is sent, we already specify the version when establishing a connection.
#[serde(rename = "v")]
pub version: u8,
/// How often a client should send heartbeats, in milliseconds /// How often a client should send heartbeats, in milliseconds
pub heartbeat_interval: f64, pub heartbeat_interval: f64,
} }

View File

@ -6,16 +6,16 @@ use serde::{Deserialize, Serialize};
/// ///
/// Contains info to begin a webrtc connection; /// Contains info to begin a webrtc connection;
/// ///
/// See <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload> /// See <https://discord-userdoccers.vercel.app/topics/voice-connections#identify-structure>
pub struct VoiceIdentify { pub struct VoiceIdentify {
/// Not needed when in a dm call /// The ID of the guild or the private channel being connected to
pub server_id: Option<Snowflake>, pub server_id: Snowflake,
pub user_id: Snowflake, pub user_id: Snowflake,
pub session_id: String, pub session_id: String,
pub token: String, pub token: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
/// Undocumented field, but is also in discord client comms
pub video: Option<bool>, pub video: Option<bool>,
// TODO: Add video streams
} }
impl WebSocketEvent for VoiceIdentify {} impl WebSocketEvent for VoiceIdentify {}

View File

@ -2,19 +2,25 @@ use super::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{value::RawValue, Value}; use serde_json::{value::RawValue, Value};
pub use client_connect::*;
pub use client_disconnect::*;
pub use hello::*; pub use hello::*;
pub use identify::*; pub use identify::*;
pub use ready::*; pub use ready::*;
pub use select_protocol::*; pub use select_protocol::*;
pub use session_description::*; pub use session_description::*;
pub use speaking::*; pub use speaking::*;
pub use voice_backend_version::*;
mod client_connect;
mod client_disconnect;
mod hello; mod hello;
mod identify; mod identify;
mod ready; mod ready;
mod select_protocol; mod select_protocol;
mod session_description; mod session_description;
mod speaking; mod speaking;
mod voice_backend_version;
#[derive(Debug, Default, Serialize, Clone)] #[derive(Debug, Default, Serialize, Clone)]
/// The payload used for sending events to the webrtc gateway. /// The payload used for sending events to the webrtc gateway.
@ -48,22 +54,41 @@ pub struct VoiceGatewayReceivePayload<'a> {
impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {} impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {}
/// The modes of encryption available in webrtc connections; /// The modes of encryption available in webrtc connections;
/// See <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-encryption-modes> ///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#encryption-mode> and <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-encryption-modes>
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum WebrtcEncryptionMode { pub enum VoiceEncryptionMode {
#[default] #[default]
// Documented // Officially Documented
Xsalsa20Poly1305, Xsalsa20Poly1305,
Xsalsa20Poly1305Suffix, Xsalsa20Poly1305Suffix,
Xsalsa20Poly1305Lite, Xsalsa20Poly1305Lite,
// Undocumented // Officially Undocumented
Xsalsa20Poly1305LiteRtpsize, Xsalsa20Poly1305LiteRtpsize,
AeadAes256Gcm, AeadAes256Gcm,
AeadAes256GcmRtpsize, AeadAes256GcmRtpsize,
AeadXchacha20Poly1305Rtpsize, AeadXchacha20Poly1305Rtpsize,
} }
/// The possible audio codecs to use
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AudioCodec {
#[default]
Opus,
}
/// The possible video codecs to use
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum VideoCodec {
#[default]
VP8,
VP9,
H264,
}
// The various voice opcodes // The various voice opcodes
pub const VOICE_IDENTIFY: u8 = 0; pub const VOICE_IDENTIFY: u8 = 0;
pub const VOICE_SELECT_PROTOCOL: u8 = 1; pub const VOICE_SELECT_PROTOCOL: u8 = 1;
@ -77,7 +102,12 @@ pub const VOICE_HELLO: u8 = 8;
pub const VOICE_RESUMED: u8 = 9; pub const VOICE_RESUMED: u8 = 9;
/// See <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#voice-opcodes> /// See <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#voice-opcodes>
pub const VOICE_VIDEO: u8 = 12; pub const VOICE_VIDEO: u8 = 12;
pub const VOICE_CLIENT_DISCONENCT: u8 = 13; pub const VOICE_CLIENT_DISCONNECT: u8 = 13;
pub const VOICE_SESSION_UPDATE: u8 = 14;
/// See <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#voice-opcodes> /// See <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#voice-opcodes>
/// Sent with empty data from the client, the server responds with the voice backend version; /// Sent with empty data from the client, the server responds with the voice backend version;
pub const VOICE_BACKEND_VERSION: u8 = 16; pub const VOICE_BACKEND_VERSION: u8 = 16;
// These two get simultaenously fired when a user joins, one has flags and one has a platform
pub const VOICE_CLIENT_CONNECT_FLAGS: u8 = 18;
pub const VOICE_CLIENT_CONNECT_PLATFORM: u8 = 20;

View File

@ -3,21 +3,24 @@ use std::net::Ipv4Addr;
use crate::types::WebSocketEvent; use crate::types::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::WebrtcEncryptionMode; use super::VoiceEncryptionMode;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// The ready event for the webrtc stream; /// The ready event for the webrtc stream;
/// ///
/// Used to give info after the identify event; /// Used to give info after the identify event;
/// ///
/// See <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-ready-payload> /// See <https://discord-userdoccers.vercel.app/topics/voice-connections#ready-structure>
pub struct VoiceReady { pub struct VoiceReady {
/// See <https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpStreamStats/ssrc> /// See <https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpStreamStats/ssrc>
pub ssrc: i32, pub ssrc: i32,
pub ip: Ipv4Addr, pub ip: Ipv4Addr,
pub port: u32, pub port: u32,
/// The available encryption modes for the webrtc connection /// The available encryption modes for the webrtc connection
pub modes: Vec<WebrtcEncryptionMode>, pub modes: Vec<VoiceEncryptionMode>,
#[serde(default)]
pub experiments: Vec<String>,
// TODO: Add video streams
// Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload." // Heartbeat interval is also sent, but is "an erroneous field and should be ignored. The correct heartbeat_interval value comes from the Hello payload."
} }
@ -28,6 +31,7 @@ impl Default for VoiceReady {
ip: Ipv4Addr::UNSPECIFIED, ip: Ipv4Addr::UNSPECIFIED,
port: 0, port: 0,
modes: Vec::new(), modes: Vec::new(),
experiments: Vec::new(),
} }
} }
} }

View File

@ -2,27 +2,58 @@ use std::net::Ipv4Addr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::WebrtcEncryptionMode; use super::VoiceEncryptionMode;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// An event sent by the client to the webrtc server, detailing what protocol, address and encryption to use; /// An event sent by the client to the webrtc server, detailing what protocol, address and encryption to use;
/// ///
/// See <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-example-select-protocol-payload> /// See <https://discord-userdoccers.vercel.app/topics/voice-connections#select-protocol-structure>
pub struct SelectProtocol { pub struct SelectProtocol {
/// The protocol to use. The only option detailed in discord docs is "udp" /// The protocol to use. The only option chorus supports is [VoiceProtocol::Udp].
pub protocol: String, pub protocol: VoiceProtocol,
pub data: SelectProtocolData, pub data: SelectProtocolData,
/// The UUID4 RTC connection ID, used for analytics.
///
/// Note: Not recommended to set this
pub rtc_connection_id: Option<String>,
// TODO: Add codecs, what is a codec object
/// The possible experiments we want to enable
#[serde(rename = "experiments")]
pub enabled_experiments: Vec<String>,
}
/// The possible protocol for sending a receiving voice data.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#select-protocol-structure>
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VoiceProtocol {
#[default]
/// Sending data via UDP, documented and the only protocol chorus supports.
Udp,
// Possible value, yet NOT RECOMMENED, AS CHORUS DOES NOT SUPPORT WEBRTC
//Webrtc,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
/// The data field of the SelectProtocol Event /// The data field of the SelectProtocol Event
/// ///
/// See <https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-udp-connection-example-select-protocol-payload> /// See <https://discord-userdoccers.vercel.app/topics/voice-connections#protocol-data-structure>
pub struct SelectProtocolData { pub struct SelectProtocolData {
/// Our external ip /// Our external ip
pub address: Ipv4Addr, pub address: Ipv4Addr,
/// Our external udp port /// Our external udp port
pub port: u32, pub port: u32,
/// The mode of encryption to use /// The mode of encryption to use
pub mode: WebrtcEncryptionMode, pub mode: VoiceEncryptionMode,
}
impl Default for SelectProtocolData {
fn default() -> Self {
SelectProtocolData {
address: Ipv4Addr::UNSPECIFIED,
port: 0,
mode: Default::default(),
}
}
} }

View File

@ -1,14 +1,39 @@
use super::WebrtcEncryptionMode; use super::{AudioCodec, VideoCodec, VoiceEncryptionMode};
use crate::types::WebSocketEvent; use crate::types::WebSocketEvent;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone, Default)] #[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// Event that describes our encryption mode and secret key for encryption /// Event that describes our encryption mode and secret key for encryption
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#session-description-structure>
pub struct SessionDescription { pub struct SessionDescription {
/// The encryption mode we're using in webrtc pub audio_codec: AudioCodec,
pub mode: WebrtcEncryptionMode, pub video_codec: VideoCodec,
pub media_session_id: String,
/// The encryption mode to use
#[serde(rename = "mode")]
pub encryption_mode: VoiceEncryptionMode,
/// The secret key we'll use for encryption /// The secret key we'll use for encryption
pub secret_key: [u8; 32], pub secret_key: [u8; 32],
/// The keyframe interval in milliseconds
pub keyframe_interval: Option<u64>,
} }
impl WebSocketEvent for SessionDescription {} impl WebSocketEvent for SessionDescription {}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// Event that might be sent to update session parameters
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#session-update-structure>
pub struct SessionUpdate {
#[serde(rename = "audio_codec")]
pub new_audio_codec: Option<AudioCodec>,
#[serde(rename = "video_codec")]
pub new_video_codec: Option<VideoCodec>,
#[serde(rename = "media_session_id")]
pub new_media_session_id: Option<String>,
}
impl WebSocketEvent for SessionUpdate {}

View File

@ -1,20 +1,28 @@
use bitflags::bitflags; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Snowflake, WebSocketEvent};
/// Event that tells the server we are speaking; /// Event that tells the server we are speaking;
/// ///
/// Essentially, what allows us to send udp data and lights up the green circle around your avatar. /// Essentially, what allows us to send udp data and lights up the green circle around your avatar.
/// ///
/// See <https://discord.com/developers/docs/topics/voice-connections#speaking-example-speaking-payload> /// See <https://discord-userdoccers.vercel.app/topics/voice-connections#speaking-structure>
#[derive(Debug, Deserialize, Serialize, Clone, Default)] #[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Speaking { pub struct Speaking {
/// Data about the audio we're transmitting, its type /// Data about the audio we're transmitting, its type
pub speaking: SpeakingBitflags, pub speaking: SpeakingBitflags,
/// Assuming delay in milliseconds for the audio, should be 0 most of the time
pub delay: u64,
pub ssrc: i32, pub ssrc: i32,
/// The user id of the speaking user, only sent by the server
#[serde(skip_serializing)]
pub user_id: Option<Snowflake>,
/// Delay in milliseconds, not sent by the server
#[serde(default)]
pub delay: u64,
} }
impl WebSocketEvent for Speaking {}
bitflags! { bitflags! {
/// Bitflags of speaking types; /// Bitflags of speaking types;
/// ///

View File

@ -0,0 +1,17 @@
use crate::types::WebSocketEvent;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
/// Received from the server to describe the backend version.
///
/// See <https://discord-userdoccers.vercel.app/topics/voice-connections#voice-backend-version>
pub struct VoiceBackendVersion {
/// The voice backend's version
#[serde(rename = "voice")]
pub voice_version: String,
/// The WebRTC worker's version
#[serde(rename = "rtc_worker")]
pub rtc_worker_version: String,
}
impl WebSocketEvent for VoiceBackendVersion {}

View File

@ -19,9 +19,10 @@ use crate::errors::VoiceGatewayError;
use crate::gateway::{GatewayEvent, HEARTBEAT_ACK_TIMEOUT}; use crate::gateway::{GatewayEvent, HEARTBEAT_ACK_TIMEOUT};
use crate::types::{ use crate::types::{
self, SelectProtocol, Speaking, VoiceGatewayReceivePayload, VoiceGatewaySendPayload, self, SelectProtocol, Speaking, VoiceGatewayReceivePayload, VoiceGatewaySendPayload,
VoiceIdentify, WebSocketEvent, VOICE_BACKEND_VERSION, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK, VoiceIdentify, WebSocketEvent, VOICE_BACKEND_VERSION, VOICE_CLIENT_CONNECT_FLAGS,
VOICE_CLIENT_CONNECT_PLATFORM, VOICE_CLIENT_DISCONNECT, VOICE_HEARTBEAT, VOICE_HEARTBEAT_ACK,
VOICE_HELLO, VOICE_IDENTIFY, VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL, VOICE_HELLO, VOICE_IDENTIFY, VOICE_READY, VOICE_RESUME, VOICE_SELECT_PROTOCOL,
VOICE_SESSION_DESCRIPTION, VOICE_SPEAKING, VOICE_SESSION_DESCRIPTION, VOICE_SESSION_UPDATE, VOICE_SPEAKING,
}; };
use self::voice_events::VoiceEvents; use self::voice_events::VoiceEvents;
@ -199,7 +200,7 @@ impl VoiceGateway {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub async fn new(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> { pub async fn new(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
// Append the needed things to the websocket url // Append the needed things to the websocket url
let processed_url = format!("wss://{}/?v=4", websocket_url); let processed_url = format!("wss://{}/?v=7", websocket_url);
trace!("Created voice socket url: {}", processed_url.clone()); trace!("Created voice socket url: {}", processed_url.clone());
let (websocket_stream, _) = match connect_async_tls_with_config( let (websocket_stream, _) = match connect_async_tls_with_config(
@ -357,6 +358,19 @@ impl VoiceGateway {
return; return;
} }
} }
VOICE_BACKEND_VERSION => {
trace!("VGW: Received Backend Version");
let event = &mut self.events.lock().await.backend_version;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_BACKEND_VERSION ({})",
result.err().unwrap()
);
return;
}
}
VOICE_SESSION_DESCRIPTION => { VOICE_SESSION_DESCRIPTION => {
trace!("VGW: Received Session Description"); trace!("VGW: Received Session Description");
@ -364,12 +378,75 @@ impl VoiceGateway {
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await; let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() { if result.is_err() {
warn!( warn!(
"Failed to parse VOICE_SELECT_PROTOCOL ({})", "Failed to parse VOICE_SESSION_DESCRIPTION ({})",
result.err().unwrap() result.err().unwrap()
); );
return; return;
} }
} }
VOICE_SESSION_UPDATE => {
trace!("VGW: Received Session Update");
let event = &mut self.events.lock().await.session_update;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_SESSION_UPDATE ({})",
result.err().unwrap()
);
return;
}
}
VOICE_SPEAKING => {
trace!("VGW: Received Speaking");
let event = &mut self.events.lock().await.speaking;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!("Failed to parse VOICE_SPEAKING ({})", result.err().unwrap());
return;
}
}
VOICE_CLIENT_DISCONNECT => {
trace!("VGW: Received Client Disconnect");
let event = &mut self.events.lock().await.client_disconnect;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_CLIENT_DISCONNECT ({})",
result.err().unwrap()
);
return;
}
}
VOICE_CLIENT_CONNECT_FLAGS => {
trace!("VGW: Received Client Connect Flags");
let event = &mut self.events.lock().await.client_connect_flags;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_CLIENT_CONNECT_FLAGS ({})",
result.err().unwrap()
);
return;
}
}
VOICE_CLIENT_CONNECT_PLATFORM => {
trace!("VGW: Received Client Connect Platform");
let event = &mut self.events.lock().await.client_connect_platform;
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_CLIENT_CONNECT_PLATFORM ({})",
result.err().unwrap()
);
return;
}
}
// We received a heartbeat from the server // We received a heartbeat from the server
// "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately." // "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately."
VOICE_HEARTBEAT => { VOICE_HEARTBEAT => {
@ -563,14 +640,23 @@ struct VoiceHeartbeatThreadCommunication {
} }
pub mod voice_events { pub mod voice_events {
use crate::types::{SessionDescription, VoiceReady}; use crate::types::{
SessionDescription, SessionUpdate, VoiceBackendVersion, VoiceClientConnectFlags,
VoiceClientConnectPlatform, VoiceClientDisconnection, VoiceReady,
};
use super::*; use super::*;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct VoiceEvents { pub struct VoiceEvents {
pub voice_ready: GatewayEvent<VoiceReady>, pub voice_ready: GatewayEvent<VoiceReady>,
pub backend_version: GatewayEvent<VoiceBackendVersion>,
pub session_description: GatewayEvent<SessionDescription>, pub session_description: GatewayEvent<SessionDescription>,
pub session_update: GatewayEvent<SessionUpdate>,
pub speaking: GatewayEvent<Speaking>,
pub client_disconnect: GatewayEvent<VoiceClientDisconnection>,
pub client_connect_flags: GatewayEvent<VoiceClientConnectFlags>,
pub client_connect_platform: GatewayEvent<VoiceClientConnectPlatform>,
pub error: GatewayEvent<VoiceGatewayError>, pub error: GatewayEvent<VoiceGatewayError>,
} }
} }