split voice ops into its own library

This commit is contained in:
Zomatree
2024-05-13 14:09:08 +01:00
parent 675c7d6c0d
commit ffbc899792
17 changed files with 446 additions and 217 deletions

View File

@@ -99,10 +99,8 @@ pub async fn web() -> Rocket<Build> {
)
.into();
log::info!("{:?}", &config.api.livekit);
let room_client = RoomClient::with_api_key(&config.api.livekit.url, &config.api.livekit.key, &config.api.livekit.secret);
// Voice handler
let voice_client = revolt_voice::VoiceClient::new(config.api.livekit.url.clone(), config.api.livekit.key.clone(), config.api.livekit.secret.clone());
// Configure Rocket
let rocket = rocket::build();
@@ -117,7 +115,7 @@ pub async fn web() -> Rocket<Build> {
.manage(authifier)
.manage(db)
.manage(cors.clone())
.manage(room_client)
.manage(voice_client)
.attach(util::ratelimiter::RatelimitFairing)
.attach(cors)
.configure(rocket::Config {

View File

@@ -1,12 +1,9 @@
use std::borrow::Cow;
use revolt_config::config;
use revolt_models::v0;
use revolt_database::{util::{permissions::perms, reference::Reference}, Channel, Database, User};
use revolt_database::{util::{permissions::perms, reference::Reference}, Database, User};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use revolt_result::Result;
use revolt_voice::{VoiceClient, raise_if_in_voice};
use livekit_api::{access_token::{AccessToken, VideoGrants}, services::room::{CreateRoomOptions, RoomClient}};
use rocket::{serde::json::Json, State};
/// # Join Call
@@ -14,69 +11,20 @@ use rocket::{serde::json::Json, State};
/// Asks the voice server for a token to join the call.
#[openapi(tag = "Voice")]
#[post("/<target>/join_call")]
pub async fn call(db: &State<Database>, rooms: &State<RoomClient>, user: User, target: Reference) -> Result<Json<v0::CreateVoiceUserResponse>> {
pub async fn call(db: &State<Database>, voice: &State<VoiceClient>, user: User, target: Reference) -> Result<Json<v0::CreateVoiceUserResponse>> {
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;
current_permissions.throw_if_lacking_channel_permission(ChannelPermission::Connect)?;
// TODO - decide if we should allow being in multiple voice channels for users
let token = voice.create_token(&user, current_permissions, &channel)?;
let room = voice.create_room(&channel).await?;
// if user.current_voice_channel(&channel.server().unwrap_or_else(|| channel.id()))
// .await?
// .is_some()
// {
// return Err(create_error!(AlreadyInVoiceChannel))
// }
let config = config().await;
if config.api.livekit.url.is_empty() {
return Err(create_error!(LiveKitUnavailable));
};
let voice = match &channel {
Channel::DirectMessage { .. } | Channel::VoiceChannel { .. } => Cow::Owned(v0::VoiceInformation::default()),
Channel::TextChannel { voice: Some(voice), .. } => Cow::Borrowed(voice),
_ => return Err(create_error!(CannotJoinCall))
};
let mut allowed_sources = Vec::new();
if current_permissions.has(ChannelPermission::Speak as u64) {
allowed_sources.push("MICROPHONE".to_string())
};
if current_permissions.has(ChannelPermission::Video as u64) {
allowed_sources.extend(["CAMERA".to_string(), "SCREEN_SHARE".to_string(), "SCREEN_SHARE_AUDIO".to_string()]);
};
let token = AccessToken::with_api_key(&config.api.livekit.key, &config.api.livekit.secret)
.with_name(&format!("{}#{}", user.username, user.discriminator))
.with_identity(&user.id)
.with_metadata(&serde_json::to_string(&user).map_err(|_| create_error!(InternalError))?)
.with_grants(VideoGrants {
room_join: true,
can_publish_sources: allowed_sources,
room: channel.id().to_string(),
..Default::default()
})
.to_jwt()
.inspect_err(|e| log::error!("{e:?}"))
.map_err(|_| create_error!(InternalError))?;
let room = rooms.create_room(&channel.id(), CreateRoomOptions {
max_participants: voice.max_users.unwrap_or(u32::MAX),
empty_timeout: 5 * 60, // 5 minutes
..Default::default()
})
.await
.inspect_err(|e| log::error!("{e:?}"))
.map_err(|_| create_error!(InternalError))?;
log::info!("created room {room:?}");
log::debug!("created room {}", room.name);
Ok(Json(v0::CreateVoiceUserResponse { token }))
}

View File

@@ -1,17 +1,17 @@
use std::collections::HashSet;
use futures::TryFutureExt;
use livekit_api::services::room::{RoomClient, UpdateParticipantOptions};
use livekit_protocol::ParticipantPermission;
use redis_kiss::{get_connection, redis::Pipeline, AsyncCommands};
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
Database, File, PartialMember, User,
events::client::EventV1, util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, File, PartialMember, User
};
use revolt_models::v0;
use revolt_models::v0::{self, PartialUserVoiceState};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
use revolt_result::{create_error, Result, ToRevoltError};
use rocket::{serde::json::Json, State};
use rocket::{form::validate::Contains, serde::json::Json, State};
use validator::Validate;
/// # Edit Member
@@ -99,6 +99,25 @@ pub async fn edit(
permissions.throw_if_lacking_channel_permission(ChannelPermission::DeafenMembers)?;
}
if let Some(new_channel) = &data.voice_channel {
permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveMembers)?;
// ensure the channel we are moving them to is in the server and is a voice channel
let channel = Reference::from_unchecked(new_channel.clone())
.as_channel(db)
.await
.map_err(|_| create_error!(UnknownChannel))?;
if !channel.server().is_some_and(|v| v == member.id.server) {
Err(create_error!(UnknownChannel))?
}
if channel.voice().is_none() {
Err(create_error!(NotAVoiceChannel))?
}
}
// Resolve our ranking
let our_ranking = query.get_member_rank().unwrap_or(i64::MIN);
@@ -136,6 +155,7 @@ pub async fn edit(
remove,
can_publish,
can_receive,
voice_channel
} = data;
let mut partial = PartialMember {
@@ -171,13 +191,15 @@ pub async fn edit(
)
.await?;
if can_publish.is_some() || can_receive.is_some() {
if can_publish.is_some() || can_receive.is_some() || voice_channel.is_some() {
let mut conn = get_connection().await.to_internal_error()?;
let unique_key = format!("{}-{}", &member.id.user, &member.id.server);
// if we edit the member while they are in a voice channel we need to also update the perms
// otherwise it wont take place until they leave and rejoin
if let Some(channel) = conn
.get::<_, Option<String>>(format!("vc-{}-{}", &member.id.user, &member.id.server))
.get::<_, Option<String>>(format!("vc-{}", &unique_key))
.await
.to_internal_error()?
{
@@ -186,7 +208,7 @@ pub async fn edit(
if let Some(can_publish) = can_publish {
pipeline.set(
format!("can_publish-{}-{}", &channel, &member.id.user),
format!("can_publish-{}", unique_key),
can_publish,
);
@@ -196,13 +218,18 @@ pub async fn edit(
if let Some(can_receive) = can_receive {
pipeline.set(
format!("can_receive-{}-{}", &channel, &member.id.user),
format!("can_receive-{}", unique_key),
can_receive,
);
new_perms.can_subscribe = can_receive;
};
if let Some(new_channel) = voice_channel {
pipeline
.smove(format!("vc-members-{channel}"), format!("vc-members-{new_channel}"), &member.id.user);
};
pipeline
.query_async(&mut conn.into_inner())
.await
@@ -220,6 +247,18 @@ pub async fn edit(
)
.await
.to_internal_error()?;
EventV1::UserVoiceStateUpdate {
id: member.id.user.clone(),
channel_id: channel.clone(),
data: PartialUserVoiceState {
can_publish,
can_receive,
..Default::default()
}
}
.p(channel)
.await;
};
};