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;
pub struct VoiceServerUpdate {
pub token: String,
/// Can be None in dm calls
/// The guild this voice server update is for
pub guild_id: Option<Snowflake>,
/// The private channel this voice server update is for
pub channel_id: Option<Snowflake>,
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;
///
/// 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 {
/// 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
pub heartbeat_interval: f64,
}

View File

@ -6,16 +6,16 @@ use serde::{Deserialize, Serialize};
///
/// 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 {
/// Not needed when in a dm call
pub server_id: Option<Snowflake>,
/// The ID of the guild or the private channel being connected to
pub server_id: Snowflake,
pub user_id: Snowflake,
pub session_id: String,
pub token: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// Undocumented field, but is also in discord client comms
pub video: Option<bool>,
// TODO: Add video streams
}
impl WebSocketEvent for VoiceIdentify {}

View File

@ -2,19 +2,25 @@ use super::WebSocketEvent;
use serde::{Deserialize, Serialize};
use serde_json::{value::RawValue, Value};
pub use client_connect::*;
pub use client_disconnect::*;
pub use hello::*;
pub use identify::*;
pub use ready::*;
pub use select_protocol::*;
pub use session_description::*;
pub use speaking::*;
pub use voice_backend_version::*;
mod client_connect;
mod client_disconnect;
mod hello;
mod identify;
mod ready;
mod select_protocol;
mod session_description;
mod speaking;
mod voice_backend_version;
#[derive(Debug, Default, Serialize, Clone)]
/// The payload used for sending events to the webrtc gateway.
@ -48,22 +54,41 @@ pub struct VoiceGatewayReceivePayload<'a> {
impl<'a> WebSocketEvent for VoiceGatewayReceivePayload<'a> {}
/// 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)]
#[serde(rename_all = "snake_case")]
pub enum WebrtcEncryptionMode {
pub enum VoiceEncryptionMode {
#[default]
// Documented
// Officially Documented
Xsalsa20Poly1305,
Xsalsa20Poly1305Suffix,
Xsalsa20Poly1305Lite,
// Undocumented
// Officially Undocumented
Xsalsa20Poly1305LiteRtpsize,
AeadAes256Gcm,
AeadAes256GcmRtpsize,
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
pub const VOICE_IDENTIFY: u8 = 0;
pub const VOICE_SELECT_PROTOCOL: u8 = 1;
@ -77,7 +102,12 @@ pub const VOICE_HELLO: u8 = 8;
pub const VOICE_RESUMED: u8 = 9;
/// See <https://discord-userdoccers.vercel.app/topics/opcodes-and-status-codes#voice-opcodes>
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>
/// Sent with empty data from the client, the server responds with the voice backend version;
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 serde::{Deserialize, Serialize};
use super::WebrtcEncryptionMode;
use super::VoiceEncryptionMode;
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
/// The ready event for the webrtc stream;
///
/// 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 {
/// See <https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpStreamStats/ssrc>
pub ssrc: i32,
pub ip: Ipv4Addr,
pub port: u32,
/// 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."
}
@ -28,6 +31,7 @@ impl Default for VoiceReady {
ip: Ipv4Addr::UNSPECIFIED,
port: 0,
modes: Vec::new(),
experiments: Vec::new(),
}
}
}

View File

@ -2,27 +2,58 @@ use std::net::Ipv4Addr;
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;
///
/// 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 {
/// The protocol to use. The only option detailed in discord docs is "udp"
pub protocol: String,
/// The protocol to use. The only option chorus supports is [VoiceProtocol::Udp].
pub protocol: VoiceProtocol,
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)]
/// 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 {
/// Our external ip
pub address: Ipv4Addr,
/// Our external udp port
pub port: u32,
/// 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 serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
/// 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 {
/// The encryption mode we're using in webrtc
pub mode: WebrtcEncryptionMode,
pub audio_codec: AudioCodec,
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
pub secret_key: [u8; 32],
/// The keyframe interval in milliseconds
pub keyframe_interval: Option<u64>,
}
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 serde::{Deserialize, Serialize};
use crate::types::{Snowflake, WebSocketEvent};
/// 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.
///
/// 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)]
pub struct Speaking {
/// Data about the audio we're transmitting, its type
pub speaking: SpeakingBitflags,
/// Assuming delay in milliseconds for the audio, should be 0 most of the time
pub delay: u64,
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 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::types::{
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_SESSION_DESCRIPTION, VOICE_SPEAKING,
VOICE_SESSION_DESCRIPTION, VOICE_SESSION_UPDATE, VOICE_SPEAKING,
};
use self::voice_events::VoiceEvents;
@ -199,7 +200,7 @@ impl VoiceGateway {
#[allow(clippy::new_ret_no_self)]
pub async fn new(websocket_url: String) -> Result<VoiceGatewayHandle, VoiceGatewayError> {
// 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());
let (websocket_stream, _) = match connect_async_tls_with_config(
@ -357,6 +358,19 @@ impl VoiceGateway {
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 => {
trace!("VGW: Received Session Description");
@ -364,12 +378,75 @@ impl VoiceGateway {
let result = VoiceGateway::handle_event(gateway_payload.data.get(), event).await;
if result.is_err() {
warn!(
"Failed to parse VOICE_SELECT_PROTOCOL ({})",
"Failed to parse VOICE_SESSION_DESCRIPTION ({})",
result.err().unwrap()
);
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
// "Discord may send the app a Heartbeat (opcode 1) event, in which case the app should send a Heartbeat event immediately."
VOICE_HEARTBEAT => {
@ -563,14 +640,23 @@ struct VoiceHeartbeatThreadCommunication {
}
pub mod voice_events {
use crate::types::{SessionDescription, VoiceReady};
use crate::types::{
SessionDescription, SessionUpdate, VoiceBackendVersion, VoiceClientConnectFlags,
VoiceClientConnectPlatform, VoiceClientDisconnection, VoiceReady,
};
use super::*;
#[derive(Default, Debug)]
pub struct VoiceEvents {
pub voice_ready: GatewayEvent<VoiceReady>,
pub backend_version: GatewayEvent<VoiceBackendVersion>,
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>,
}
}