204 lines
6.2 KiB
Rust
204 lines
6.2 KiB
Rust
use crate::id::{ChannelId, GuildId, UserId};
|
|
use std::fmt;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) enum ConnectionProgress {
|
|
Complete(ConnectionInfo),
|
|
Incomplete(Partial),
|
|
}
|
|
|
|
impl ConnectionProgress {
|
|
pub(crate) fn new(guild_id: GuildId, user_id: UserId, channel_id: ChannelId) -> Self {
|
|
ConnectionProgress::Incomplete(Partial {
|
|
channel_id,
|
|
guild_id,
|
|
user_id,
|
|
token: None,
|
|
endpoint: None,
|
|
session_id: None,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn get_connection_info(&self) -> Option<&ConnectionInfo> {
|
|
if let Self::Complete(c) = self {
|
|
Some(c)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn in_progress(&self) -> bool {
|
|
matches!(self, ConnectionProgress::Incomplete(_))
|
|
}
|
|
|
|
pub(crate) fn channel_id(&self) -> ChannelId {
|
|
match self {
|
|
ConnectionProgress::Complete(conn_info) => conn_info
|
|
.channel_id
|
|
.expect("All code paths MUST set channel_id for local tracking."),
|
|
ConnectionProgress::Incomplete(part) => part.channel_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn guild_id(&self) -> GuildId {
|
|
match self {
|
|
ConnectionProgress::Complete(conn_info) => conn_info.guild_id,
|
|
ConnectionProgress::Incomplete(part) => part.guild_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn user_id(&self) -> UserId {
|
|
match self {
|
|
ConnectionProgress::Complete(conn_info) => conn_info.user_id,
|
|
ConnectionProgress::Incomplete(part) => part.user_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn info(&self) -> Option<ConnectionInfo> {
|
|
self.get_connection_info().cloned()
|
|
}
|
|
|
|
pub(crate) fn apply_state_update(&mut self, session_id: String, channel_id: ChannelId) -> bool {
|
|
if self.channel_id() != channel_id {
|
|
// Likely that the bot was moved to a different channel by an admin.
|
|
*self = ConnectionProgress::new(self.guild_id(), self.user_id(), channel_id);
|
|
}
|
|
|
|
match self {
|
|
Self::Complete(c) => {
|
|
let should_reconn = c.session_id != session_id;
|
|
c.session_id = session_id;
|
|
should_reconn
|
|
},
|
|
Self::Incomplete(i) => i
|
|
.apply_state_update(session_id, channel_id)
|
|
.map(|info| {
|
|
*self = Self::Complete(info);
|
|
})
|
|
.is_some(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn apply_server_update(&mut self, endpoint: String, token: String) -> bool {
|
|
match self {
|
|
Self::Complete(c) => {
|
|
let should_reconn = c.endpoint != endpoint || c.token != token;
|
|
|
|
c.endpoint = endpoint;
|
|
c.token = token;
|
|
|
|
should_reconn
|
|
},
|
|
Self::Incomplete(i) => i
|
|
.apply_server_update(endpoint, token)
|
|
.map(|info| {
|
|
*self = Self::Complete(info);
|
|
})
|
|
.is_some(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parameters and information needed to start communicating with Discord's voice servers, either
|
|
/// with the Songbird driver, lavalink, or other system.
|
|
#[derive(Clone, Eq, Hash, PartialEq)]
|
|
pub struct ConnectionInfo {
|
|
/// ID of the voice channel being joined, if it is known.
|
|
///
|
|
/// This is not needed to establish a connection, but can be useful
|
|
/// for book-keeping.
|
|
pub channel_id: Option<ChannelId>,
|
|
/// URL of the voice websocket gateway server assigned to this call.
|
|
pub endpoint: String,
|
|
/// ID of the target voice channel's parent guild.
|
|
///
|
|
/// Bots cannot connect to a guildless (i.e., direct message) voice call.
|
|
pub guild_id: GuildId,
|
|
/// Unique string describing this session for validation/authentication purposes.
|
|
pub session_id: String,
|
|
/// Ephemeral secret used to validate the above session.
|
|
pub token: String,
|
|
/// UserID of this bot.
|
|
pub user_id: UserId,
|
|
}
|
|
|
|
impl fmt::Debug for ConnectionInfo {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ConnectionInfo")
|
|
.field("channel_id", &self.channel_id)
|
|
.field("endpoint", &self.endpoint)
|
|
.field("guild_id", &self.guild_id)
|
|
.field("session_id", &self.session_id)
|
|
.field("token", &"<secret>")
|
|
.field("user_id", &self.user_id)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct Partial {
|
|
pub channel_id: ChannelId,
|
|
pub endpoint: Option<String>,
|
|
pub guild_id: GuildId,
|
|
pub session_id: Option<String>,
|
|
pub token: Option<String>,
|
|
pub user_id: UserId,
|
|
}
|
|
|
|
impl fmt::Debug for Partial {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("Partial")
|
|
.field("channel_id", &self.channel_id)
|
|
.field("endpoint", &self.endpoint)
|
|
.field("guild_id", &self.guild_id)
|
|
.field("session_id", &self.session_id)
|
|
.field("token_is_some", &self.token.is_some())
|
|
.field("user_id", &self.user_id)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Partial {
|
|
fn finalise(&mut self) -> Option<ConnectionInfo> {
|
|
if self.endpoint.is_some() && self.session_id.is_some() && self.token.is_some() {
|
|
let endpoint = self.endpoint.take().unwrap();
|
|
let session_id = self.session_id.take().unwrap();
|
|
let token = self.token.take().unwrap();
|
|
|
|
Some(ConnectionInfo {
|
|
channel_id: Some(self.channel_id),
|
|
endpoint,
|
|
session_id,
|
|
token,
|
|
guild_id: self.guild_id,
|
|
user_id: self.user_id,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn apply_state_update(
|
|
&mut self,
|
|
session_id: String,
|
|
channel_id: ChannelId,
|
|
) -> Option<ConnectionInfo> {
|
|
if self.channel_id != channel_id {
|
|
self.endpoint = None;
|
|
self.token = None;
|
|
}
|
|
|
|
self.channel_id = channel_id;
|
|
self.session_id = Some(session_id);
|
|
|
|
self.finalise()
|
|
}
|
|
|
|
fn apply_server_update(&mut self, endpoint: String, token: String) -> Option<ConnectionInfo> {
|
|
self.endpoint = Some(endpoint);
|
|
self.token = Some(token);
|
|
|
|
self.finalise()
|
|
}
|
|
}
|