Compare commits

..

6 Commits

Author SHA1 Message Date
Zomatree
c5eb48450b fix: include channel ids in UserMoveVoiceChannel 2025-10-22 19:54:24 +01:00
Zomatree
6ba48a0197 fix: add Speak and Listen to default permissions 2025-10-22 18:19:02 +01:00
Zomatree
47002ec81c fix: dont break on user not still being in channel and force disconnecting 2025-10-20 00:29:56 +01:00
Zomatree
6ab264eb36 fix: return correct error if user is already in a voice channel 2025-10-19 23:25:35 +01:00
Zomatree
59af536e29 refactor: seperate out disconnect logic 2025-10-12 02:02:36 +01:00
Zomatree
5c7eec68d0 chore: add debug logging to voice ingress 2025-10-12 01:12:08 +01:00
11 changed files with 82 additions and 57 deletions

View File

@@ -295,7 +295,9 @@ pub enum EventV1 {
},
UserMoveVoiceChannel {
node: String,
token: String
from: String,
to: String,
token: String,
}
}

View File

@@ -25,7 +25,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 48; // MUST BE +1 to last migration
pub const LATEST_REVISION: i32 = 49; // MUST BE +1 to last migration
pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::<Document>("migrations");
@@ -1246,8 +1246,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("Failed to update voice channels");
};
if revision <= 47 {
info!("Running migration [revision 47 / 29-04-2025]: Add Video to default permissions");
if revision <= 48 {
info!("Running migration [revision 48 / 22-10-2025]: Add Video + Listen to default permissions");
db.col::<Document>("servers")
.update_many(
@@ -1255,7 +1255,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
doc! {
"$bit": {
"default_permissions": {
"or": ChannelPermission::Video as i64
"or": (ChannelPermission::Video + ChannelPermission::Speak + ChannelPermission::Listen) as i64
},
}
}

View File

@@ -35,7 +35,7 @@ pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
.to_internal_error()?
> 0
{
Err(create_error!(NotConnected))
Err(create_error!(AlreadyConnected))
} else {
Ok(())
}

View File

@@ -214,40 +214,59 @@ pub async fn ingress(
// forbid any size which goes over the limit and also limit the aspect ratio to stop people from making too tall or too wide and bypassing the limit.
// TODO: figure out how to track audio stream quality
if event.event == "track_published"
&& (track.r#type == TrackType::Data as i32
|| (track.r#type == TrackType::Video as i32
&& (user_limits.video_resolution[0] != 0
&& user_limits.video_resolution[1] != 0
&& track.width * track.height
> user_limits.video_resolution[0]
* user_limits.video_resolution[1])
|| (user_limits.video_aspect_ratio[0]
!= user_limits.video_aspect_ratio[1]
&& !(user_limits.video_aspect_ratio[0]
..=user_limits.video_aspect_ratio[1])
.contains(&(track.width as f32 / track.height as f32)))))
{
voice_client.remove_user(node, user_id, channel_id).await?;
delete_voice_state(channel_id, channel.server(), user_id).await?;
} else {
let partial = update_voice_state_tracks(
channel_id,
channel.server(),
user_id,
event.event == "track_published", // to avoid duplicating this entire case twice
track.source,
)
.await?;
if event.event == "track_published" {
let mut disconnect = false;
EventV1::UserVoiceStateUpdate {
id: user_id.clone(),
channel_id: channel_id.clone(),
data: partial,
}
.p(channel_id.clone())
.await;
if track.r#type == TrackType::Data as i32 {
log::debug!("User published data");
disconnect = true;
};
if track.r#type == TrackType::Video as i32 {
if user_limits.video_resolution[0] != 0
&& user_limits.video_resolution[1] != 0
&& track.width * track.height
> user_limits.video_resolution[0] * user_limits.video_resolution[1]
{
log::debug!("User published video with out of bounds resolution");
disconnect = true;
};
if user_limits.video_aspect_ratio[0] != user_limits.video_aspect_ratio[1]
&& !(user_limits.video_aspect_ratio[0]..=user_limits.video_aspect_ratio[1])
.contains(&(track.width as f32 / track.height as f32))
{
log::debug!("User published video with out of bounds aspect ratio");
disconnect = true;
};
};
if disconnect {
log::debug!("Removing user {user_id} from channel {channel_id} {event:?} due to forbidden track.");
let _ = voice_client.remove_user(node, user_id, channel_id).await;
delete_voice_state(channel_id, channel.server(), user_id).await?;
return Ok(EmptyResponse);
};
};
let partial = update_voice_state_tracks(
channel_id,
channel.server(),
user_id,
event.event == "track_published", // to avoid duplicating this entire case twice
track.source,
)
.await?;
EventV1::UserVoiceStateUpdate {
id: user_id.clone(),
channel_id: channel_id.clone(),
data: partial,
}
.p(channel_id.clone())
.await;
}
_ => {}
};

View File

@@ -22,10 +22,11 @@ pub async fn delete_bot(
bot.delete(db).await?;
for channel_id in get_user_voice_channels(&bot.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap();
let channel = Reference::from_unchecked(&channel_id).as_channel(db).await?;
if let Some(node) = get_channel_node(&channel_id).await? {
let _ = voice_client.remove_user(&node, &bot.id, &channel_id).await;
}
voice_client.remove_user(&node, &bot.id, &channel_id).await?;
let channel = Reference::from_unchecked(&channel_id).as_channel(db).await?;
delete_voice_state(&channel_id, channel.server(), &bot.id).await?;
}

View File

@@ -48,9 +48,10 @@ pub async fn remove_member(
};
if is_in_voice_channel(&user.id, channel.id()).await? {
let node = get_channel_node(channel.id()).await?.unwrap();
if let Some(node) = get_channel_node(channel.id()).await? {
let _ = voice_client.remove_user(&node, &user.id, channel.id()).await;
}
voice_client.remove_user(&node, &user.id, channel.id()).await?;
delete_voice_state(channel.id(), None, &user.id).await?;
};

View File

@@ -79,15 +79,15 @@ pub async fn call(
// 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();
if let Some(node) = get_channel_node(&channel_id).await? {
// if this errors its just a mismatching state - ignore and proceed to still delete our state
let _ = voice_client.remove_user(&node, &user.id, &channel_id).await;
};
let channel = Reference::from_unchecked(&channel_id)
.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 {

View File

@@ -59,9 +59,9 @@ pub async fn ban(
// If the member is in a voice channel while banned kick them from the voice channel
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?;
if let Some(node) = get_channel_node(&channel_id).await? {
let _ = voice_client.remove_user(&node, &user.id, &channel_id).await;
}
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?
}

View File

@@ -217,6 +217,8 @@ pub async fn edit(
EventV1::UserMoveVoiceChannel {
node: new_node,
from: old_voice_channel.id().to_string(),
to: new_voice_channel.id().to_string(),
token,
}
.p_user(target_user.id.clone(), db)

View File

@@ -47,9 +47,9 @@ pub async fn kick(
.await?;
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?;
if let Some(node) = get_channel_node(&channel_id).await? {
let _ = voice_client.remove_user(&node, &user.id, &channel_id).await;
}
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
};

View File

@@ -39,9 +39,9 @@ pub async fn delete(
} else {
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
if server.channels.iter().any(|c| c == &channel_id) {
let node = get_channel_node(&channel_id).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel_id).await?;
if let Some(node) = get_channel_node(&channel_id).await? {
let _ = voice_client.remove_user(&node, &user.id, &channel_id).await;
}
delete_voice_state(&channel_id, Some(&server.id), &user.id).await?;
}