feat: ability to force disconnect existing voice connection

This commit is contained in:
Zomatree
2025-08-04 03:19:53 +01:00
parent eb48975428
commit 10c9682bba
2 changed files with 42 additions and 8 deletions

View File

@@ -328,7 +328,12 @@ auto_derived!(
/// Join a voice channel
pub struct DataJoinCall {
/// Name of the node to join
pub node: Option<String>,
/// Whether to force disconnect any other existing voice connections.
///
/// useful for disconnecting on another device and joining on a new.
pub force_disconnect: Option<bool>
}
);

View File

@@ -1,6 +1,6 @@
use revolt_config::config;
use revolt_models::v0;
use revolt_database::{util::{permissions::perms, reference::Reference}, voice::{get_channel_node, raise_if_in_voice, VoiceClient}, Channel, Database, SystemMessage, User, AMQP};
use revolt_database::{util::{permissions::perms, reference::Reference}, voice::{delete_voice_state, get_channel_node, get_user_voice_channels, raise_if_in_voice, VoiceClient}, Channel, Database, SystemMessage, User, AMQP};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
@@ -11,17 +11,31 @@ use rocket::{serde::json::Json, State};
/// Asks the voice server for a token to join the call.
#[openapi(tag = "Voice")]
#[post("/<target>/join_call", data="<data>")]
pub async fn call(db: &State<Database>, amqp: &State<AMQP>, voice: &State<VoiceClient>, user: User, target: Reference, data: Json<v0::DataJoinCall>) -> Result<Json<v0::CreateVoiceUserResponse>> {
if !voice.is_enabled() {
pub async fn call(
db: &State<Database>,
amqp: &State<AMQP>,
voice_client: &State<VoiceClient>,
user: User,
target: Reference,
data: Json<v0::DataJoinCall>
) -> Result<Json<v0::CreateVoiceUserResponse>> {
if !voice_client.is_enabled() {
return Err(create_error!(LiveKitUnavailable))
}
let v0::DataJoinCall {
node,
force_disconnect
} = data.into_inner();
if user.bot.is_some() && force_disconnect == Some(true) {
return Err(create_error!(IsBot))
}
let config = config().await;
let channel = target.as_channel(db).await?;
raise_if_in_voice(&user, channel.id()).await?;
let mut permissions = perms(db, &user).channel(&channel);
let current_permissions = calculate_channel_permissions(&mut permissions).await;
@@ -29,15 +43,30 @@ pub async fn call(db: &State<Database>, amqp: &State<AMQP>, voice: &State<VoiceC
let existing_node = get_channel_node(channel.id()).await?;
let node = existing_node.or(data.into_inner().node)
let node = existing_node.or(node)
.ok_or_else(|| create_error!(UnknownNode))?;
let node_host = config.hosts.livekit.get(&node)
.ok_or_else(|| create_error!(UnknownNode))?
.clone();
let token = voice.create_token(&node, &user, current_permissions, &channel)?;
let room = voice.create_room(&node, &channel).await?;
if force_disconnect == Some(true) {
// Finds and disconnects any existing voice connections by the user,
// should only ever loop once but just to cover our backs.
for channel_id in get_user_voice_channels(&user.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap();
let channel = Reference::from_unchecked(channel_id.clone()).as_channel(db).await?;
voice_client.remove_user(&node, &user.id, &channel_id).await?;
delete_voice_state(&channel_id, channel.server(), &user.id).await?;
}
} else {
raise_if_in_voice(&user, channel.id()).await?;
}
let token = voice_client.create_token(&node, &user, current_permissions, &channel)?;
let room = voice_client.create_room(&node, &channel).await?;
log::debug!("Created room {}", room.name);