feat: allow kicking members from voice channels (#495)

This commit is contained in:
Angelo Kontaxis
2026-01-25 19:42:48 +00:00
committed by GitHub
parent 9fb55f74dc
commit 0dc5442498
5 changed files with 40 additions and 16 deletions

View File

@@ -72,6 +72,7 @@ auto_derived!(
CanReceive, CanReceive,
CanPublish, CanPublish,
JoinedAt, JoinedAt,
VoiceChannel,
} }
/// Member removal intention /// Member removal intention
@@ -224,13 +225,14 @@ impl Member {
pub fn remove_field(&mut self, field: &FieldsMember) { pub fn remove_field(&mut self, field: &FieldsMember) {
match field { match field {
FieldsMember::JoinedAt => (), FieldsMember::JoinedAt => {},
FieldsMember::Avatar => self.avatar = None, FieldsMember::Avatar => self.avatar = None,
FieldsMember::Nickname => self.nickname = None, FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles.clear(), FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None, FieldsMember::Timeout => self.timeout = None,
FieldsMember::CanReceive => self.can_receive = true, FieldsMember::CanReceive => self.can_receive = true,
FieldsMember::CanPublish => self.can_publish = true, FieldsMember::CanPublish => self.can_publish = true,
FieldsMember::VoiceChannel => {},
} }
} }

View File

@@ -330,14 +330,15 @@ impl AbstractServerMembers for MongoDb {
impl IntoDocumentPath for FieldsMember { impl IntoDocumentPath for FieldsMember {
fn as_path(&self) -> Option<&'static str> { fn as_path(&self) -> Option<&'static str> {
Some(match self { match self {
FieldsMember::JoinedAt => "joined_at", FieldsMember::JoinedAt => Some("joined_at"),
FieldsMember::Avatar => "avatar", FieldsMember::Avatar => Some("avatar"),
FieldsMember::Nickname => "nickname", FieldsMember::Nickname => Some("nickname"),
FieldsMember::Roles => "roles", FieldsMember::Roles => Some("roles"),
FieldsMember::Timeout => "timeout", FieldsMember::Timeout => Some("timeout"),
FieldsMember::CanPublish => "can_publish", FieldsMember::CanPublish => Some("can_publish"),
FieldsMember::CanReceive => "can_receive", FieldsMember::CanReceive => Some("can_receive"),
}) FieldsMember::VoiceChannel => None,
}
} }
} }

View File

@@ -691,6 +691,7 @@ impl From<crate::FieldsMember> for FieldsMember {
crate::FieldsMember::CanReceive => FieldsMember::CanReceive, crate::FieldsMember::CanReceive => FieldsMember::CanReceive,
crate::FieldsMember::CanPublish => FieldsMember::CanPublish, crate::FieldsMember::CanPublish => FieldsMember::CanPublish,
crate::FieldsMember::JoinedAt => FieldsMember::JoinedAt, crate::FieldsMember::JoinedAt => FieldsMember::JoinedAt,
crate::FieldsMember::VoiceChannel => FieldsMember::VoiceChannel,
} }
} }
} }
@@ -705,6 +706,7 @@ impl From<FieldsMember> for crate::FieldsMember {
FieldsMember::CanReceive => crate::FieldsMember::CanReceive, FieldsMember::CanReceive => crate::FieldsMember::CanReceive,
FieldsMember::CanPublish => crate::FieldsMember::CanPublish, FieldsMember::CanPublish => crate::FieldsMember::CanPublish,
FieldsMember::JoinedAt => crate::FieldsMember::JoinedAt, FieldsMember::JoinedAt => crate::FieldsMember::JoinedAt,
FieldsMember::VoiceChannel => crate::FieldsMember::VoiceChannel,
} }
} }
} }

View File

@@ -95,6 +95,7 @@ auto_derived!(
CanReceive, CanReceive,
CanPublish, CanPublish,
JoinedAt, JoinedAt,
VoiceChannel,
} }
/// Member removal intention /// Member removal intention

View File

@@ -98,13 +98,21 @@ pub async fn edit(
permissions.throw_if_lacking_channel_permission(ChannelPermission::DeafenMembers)?; permissions.throw_if_lacking_channel_permission(ChannelPermission::DeafenMembers)?;
} }
let new_voice_channel = if let Some(new_channel) = &data.voice_channel { if data.voice_channel.is_some() && data.remove.contains(&FieldsMember::VoiceChannel) {
return Err(create_error!(InvalidOperation))
}
if data.voice_channel.is_some() || data.remove.contains(&FieldsMember::VoiceChannel) {
if !voice_client.is_enabled() { if !voice_client.is_enabled() {
return Err(create_error!(LiveKitUnavailable)); return Err(create_error!(LiveKitUnavailable));
}; };
permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveMembers)?; if member.id.user != user.id {
permissions.throw_if_lacking_channel_permission(ChannelPermission::MoveMembers)?;
}
}
let new_voice_channel = if let Some(new_channel) = &data.voice_channel {
// ensure the channel we are moving them to is in the server and is a voice channel // ensure the channel we are moving them to is in the server and is a voice channel
let channel = Reference::from_unchecked(new_channel) let channel = Reference::from_unchecked(new_channel)
@@ -116,6 +124,9 @@ pub async fn edit(
Err(create_error!(UnknownChannel))? Err(create_error!(UnknownChannel))?
} }
let channel_permissions = calculate_channel_permissions(&mut query.clone().channel(&channel)).await;
channel_permissions.throw_if_lacking_channel_permission(ChannelPermission::Connect)?;
if get_user_voice_channel_in_server(&target_user.id, &server.id) if get_user_voice_channel_in_server(&target_user.id, &server.id)
.await? .await?
.is_none() .is_none()
@@ -189,10 +200,8 @@ pub async fn edit(
partial.avatar = Some(File::use_user_avatar(db, &avatar, &user.id, &user.id).await?); partial.avatar = Some(File::use_user_avatar(db, &avatar, &user.id, &user.id).await?);
} }
let remove_contains_voice = remove.contains(FieldsMember::CanPublish) || remove.contains(FieldsMember::CanReceive);
member member
.update(db, partial, remove.into_iter().map(Into::into).collect()) .update(db, partial, remove.clone().into_iter().map(Into::into).collect())
.await?; .await?;
if let Some(new_voice_channel) = new_voice_channel { if let Some(new_voice_channel) = new_voice_channel {
@@ -234,7 +243,7 @@ pub async fn edit(
.private(target_user.id.clone()) .private(target_user.id.clone())
.await; .await;
}; };
} else if can_publish.is_some() || can_receive.is_some() || remove_contains_voice { } else if can_publish.is_some() || can_receive.is_some() || remove.contains(FieldsMember::CanPublish) || remove.contains(FieldsMember::CanReceive) {
if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await? if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await?
{ {
let node = get_channel_node(&channel).await?.unwrap(); let node = get_channel_node(&channel).await?.unwrap();
@@ -253,5 +262,14 @@ pub async fn edit(
}; };
}; };
if remove.contains(&FieldsMember::VoiceChannel) {
if let Some(channel) = get_user_voice_channel_in_server(&target_user.id, &server.id).await?
{
let node = get_channel_node(&channel).await?.unwrap();
voice_client.remove_user(&node, &user.id, &channel).await?;
};
}
Ok(Json(member.into())) Ok(Json(member.into()))
} }