split voice ops into its own library
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 }))
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user