fix: store server id in redis and in room metadata to be able to delete voice state in all scenarios (#656)

* fix: store server id in redis values and in room metadata to be able to fully delete voice state in all scenarios

* fix: undo logging change
This commit is contained in:
Angelo Kontaxis
2026-03-10 22:14:32 +00:00
committed by GitHub
parent b0b728fb0d
commit 49c6289580
15 changed files with 322 additions and 154 deletions

View File

@@ -4,7 +4,7 @@ use futures::future::join_all;
use revolt_database::{
events::client::{EventV1, ReadyPayloadFields},
util::permissions::DatabasePermissionQuery,
voice::get_channel_voice_state,
voice::{get_channel_voice_state, UserVoiceChannel},
Channel, Database, Member, MemberCompositeKey, Presence, RelationshipStatus,
};
use revolt_models::v0;
@@ -167,7 +167,9 @@ impl State {
| Channel::TextChannel { voice: Some(_), .. }
)
}) {
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
if let Ok(Some(voice_state)) =
get_channel_voice_state(&UserVoiceChannel::from_channel(channel)).await
{
if let Some(server) = channel.server() {
let set = voice_state_server_members
.entry(server.to_string())

View File

@@ -3,8 +3,8 @@ use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
use revolt_result::{create_error, Result};
use crate::{
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel,
Database, File, Server, SystemMessage, User,
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel, Database, File,
Server, SystemMessage, User,
};
fn default_true() -> bool {
@@ -45,7 +45,6 @@ auto_derived_partial!(
/// Whether the member is server-wide voice deafened
#[serde(skip_serializing_if = "is_true", default = "default_true")]
pub can_receive: bool,
// This value only exists in the database, not the models.
// If it is not-None, the database layer should return None to member fetching queries.
// pub pending_deletion_at: Option<Timestamp>
@@ -153,7 +152,11 @@ impl Member {
#[cfg(feature = "voice")]
for channel in &channels {
if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(channel).await {
if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(
&crate::voice::UserVoiceChannel::from_channel(channel),
)
.await
{
voice_states.push(voice_state)
}
}
@@ -175,7 +178,7 @@ impl Member {
.map(|channel| channel.into())
.collect(),
emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
voice_states
voice_states,
}
.private(user.id.clone())
.await;
@@ -225,14 +228,14 @@ impl Member {
pub fn remove_field(&mut self, field: &FieldsMember) {
match field {
FieldsMember::JoinedAt => {},
FieldsMember::JoinedAt => {}
FieldsMember::Avatar => self.avatar = None,
FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None,
FieldsMember::CanReceive => self.can_receive = true,
FieldsMember::CanPublish => self.can_publish = true,
FieldsMember::VoiceChannel => {},
FieldsMember::VoiceChannel => {}
}
}

View File

@@ -1,3 +1,5 @@
use std::fmt::{Display, Write};
use crate::{
events::client::EventV1,
models::{Channel, User},
@@ -6,7 +8,11 @@ use crate::{
};
use iso8601_timestamp::{Duration, Timestamp};
use livekit_protocol::ParticipantPermission;
use redis_kiss::{get_connection as _get_connection, redis::Pipeline, AsyncCommands, Conn};
use redis_kiss::{
get_connection as _get_connection,
redis::{FromRedisValue, Pipeline, RedisError, RedisWrite, ToRedisArgs, Value},
AsyncCommands, Conn,
};
use revolt_config::FeaturesLimits;
use revolt_models::v0::{self, PartialUserVoiceState, UserVoiceState};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
@@ -21,13 +27,13 @@ async fn get_connection() -> Result<Conn> {
.map_err(|_| create_error!(InternalError))
}
pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
pub async fn raise_if_in_voice(user: &User, channel: &UserVoiceChannel) -> Result<()> {
let mut conn = get_connection().await?;
if user.bot.is_some() {
// bots can be in as many voice channels as it wants so we just check if its already connected to the one its trying to connect to
if conn
.sismember(format!("vc:{}", &user.id), channel_id)
.sismember(format!("vc:{}", &user.id), channel)
.await
.to_internal_error()?
{
@@ -45,31 +51,31 @@ pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> Result<()> {
Ok(())
}
pub async fn set_channel_node(channel: &str, node: &str) -> Result<()> {
pub async fn set_channel_node(channel_id: &str, node: &str) -> Result<()> {
get_connection()
.await?
.set(format!("node:{channel}"), node)
.set(format!("node:{channel_id}"), node)
.await
.to_internal_error()
}
pub async fn get_channel_node(channel: &str) -> Result<Option<String>> {
pub async fn get_channel_node(channel_id: &str) -> Result<Option<String>> {
get_connection()
.await?
.get(format!("node:{channel}"))
.get(format!("node:{channel_id}"))
.await
.to_internal_error()
}
pub async fn delete_channel_node(channel: &str) -> Result<()> {
pub async fn delete_channel_node(channel_id: &str) -> Result<()> {
get_connection()
.await?
.del(format!("node:{channel}"))
.del(format!("node:{channel_id}"))
.await
.to_internal_error()
}
pub async fn get_user_voice_channels(user_id: &str) -> Result<Vec<String>> {
pub async fn get_user_voice_channels(user_id: &str) -> Result<Vec<UserVoiceChannel>> {
get_connection()
.await?
.smembers(format!("vc:{user_id}"))
@@ -78,14 +84,14 @@ pub async fn get_user_voice_channels(user_id: &str) -> Result<Vec<String>> {
}
pub async fn set_user_moved_from_voice(
old_channel: &str,
new_channel: &str,
old_channel_id: &str,
new_channel: &UserVoiceChannel,
user_id: &str,
) -> Result<()> {
get_connection()
.await?
.set_ex(
format!("moved_from:{user_id}:{old_channel}"),
format!("moved_from:{user_id}:{old_channel_id}"),
new_channel,
10,
)
@@ -102,18 +108,25 @@ pub async fn get_user_moved_from_voice(channel_id: &str, user_id: &str) -> Resul
}
pub async fn set_user_moved_to_voice(
new_channel: &str,
old_channel: &str,
new_channel_id: &str,
old_channel: &UserVoiceChannel,
user_id: &str,
) -> Result<()> {
get_connection()
.await?
.set_ex(format!("moved_to:{user_id}:{new_channel}"), old_channel, 10)
.set_ex(
format!("moved_to:{user_id}:{new_channel_id}"),
old_channel,
10,
)
.await
.to_internal_error()
}
pub async fn get_user_moved_to_voice(channel_id: &str, user_id: &str) -> Result<Option<String>> {
pub async fn get_user_moved_to_voice(
channel_id: &str,
user_id: &str,
) -> Result<Option<UserVoiceChannel>> {
get_connection()
.await?
.get_del(format!("moved_to:{user_id}:{channel_id}"))
@@ -121,10 +134,10 @@ pub async fn get_user_moved_to_voice(channel_id: &str, user_id: &str) -> Result<
.to_internal_error()
}
pub async fn is_in_voice_channel(user_id: &str, channel_id: &str) -> Result<bool> {
pub async fn is_in_voice_channel(user_id: &str, channel: &UserVoiceChannel) -> Result<bool> {
get_connection()
.await?
.sismember(format!("vc:{user_id}"), channel_id)
.sismember(format!("vc:{user_id}"), channel)
.await
.to_internal_error()
}
@@ -158,12 +171,15 @@ pub fn get_allowed_sources(
}
pub async fn create_voice_state(
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
user_id: &str,
joined_at: Timestamp,
) -> Result<UserVoiceState> {
let unique_key = format!("{}:{}", &user_id, server_id.unwrap_or(channel_id));
let unique_key = format!(
"{}:{}",
&user_id,
channel.server_id.as_ref().unwrap_or(&channel.id)
);
let voice_state = UserVoiceState {
joined_at,
@@ -175,9 +191,9 @@ pub async fn create_voice_state(
};
Pipeline::new()
.sadd(format!("vc_members:{channel_id}"), user_id)
.sadd(format!("vc:{user_id}"), channel_id)
.set(&unique_key, channel_id)
.sadd(format!("vc_members:{}", &channel.id), user_id)
.sadd(format!("vc:{user_id}"), channel)
.set(&unique_key, &channel.id)
.set(
format!("joined_at:{unique_key}"),
joined_at
@@ -204,16 +220,16 @@ pub async fn create_voice_state(
Ok(voice_state)
}
pub async fn delete_voice_state(
channel_id: &str,
server_id: Option<&str>,
user_id: &str,
) -> Result<()> {
let unique_key = format!("{}:{}", &user_id, server_id.unwrap_or(channel_id));
pub async fn delete_voice_state(channel: &UserVoiceChannel, user_id: &str) -> Result<()> {
let unique_key = format!(
"{}:{}",
&user_id,
channel.server_id.as_ref().unwrap_or(&channel.id)
);
Pipeline::new()
.srem(format!("vc_members:{channel_id}"), user_id)
.srem(format!("vc:{user_id}"), channel_id)
.srem(format!("vc_members:{}", &channel.id), user_id)
.srem(format!("vc:{user_id}"), channel)
.del(&[
format!("joined_at:{unique_key}"),
format!("is_publishing:{unique_key}"),
@@ -228,20 +244,19 @@ pub async fn delete_voice_state(
}
pub async fn delete_channel_voice_state(
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
user_ids: &[String],
) -> Result<()> {
let parent_id = server_id.unwrap_or(channel_id);
let parent_id = channel.server_id.as_ref().unwrap_or(&channel.id);
let mut pipeline = Pipeline::new();
pipeline.del(format!("vc_members:{channel_id}"));
pipeline.del(format!("node:{channel_id}"));
pipeline.del(format!("vc_members:{}", &channel.id));
pipeline.del(format!("node:{}", &channel.id));
for user_id in user_ids {
let unique_key = format!("{user_id}:{parent_id}");
pipeline.srem(format!("vc:{user_id}"), channel_id).del(&[
pipeline.srem(format!("vc:{user_id}"), channel).del(&[
format!("joined_at:{unique_key}"),
format!("is_publishing:{unique_key}"),
format!("is_receiving:{unique_key}"),
@@ -258,8 +273,7 @@ pub async fn delete_channel_voice_state(
}
pub async fn update_voice_state_tracks(
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
user_id: &str,
added: bool,
track: i32,
@@ -284,18 +298,21 @@ pub async fn update_voice_state_tracks(
_ => unreachable!(),
};
update_voice_state(channel_id, server_id, user_id, &partial).await?;
update_voice_state(channel, user_id, &partial).await?;
Ok(partial)
}
pub async fn update_voice_state(
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
user_id: &str,
partial: &PartialUserVoiceState,
) -> Result<()> {
let unique_key = format!("{}:{}", &user_id, server_id.unwrap_or(channel_id));
let unique_key = format!(
"{}:{}",
&user_id,
channel.server_id.as_ref().unwrap_or(&channel.id)
);
let mut pipeline = Pipeline::new();
@@ -321,21 +338,24 @@ pub async fn update_voice_state(
.to_internal_error()
}
pub async fn get_voice_channel_members(channel_id: &str) -> Result<Option<Vec<String>>> {
pub async fn get_voice_channel_members(channel: &UserVoiceChannel) -> Result<Option<Vec<String>>> {
get_connection()
.await?
.smembers::<_, Option<Vec<String>>>(format!("vc_members:{channel_id}"))
.smembers::<_, Option<Vec<String>>>(format!("vc_members:{}", &channel.id))
.await
.to_internal_error()
.map(|opt| opt.and_then(|v| if v.is_empty() { None } else { Some(v) }))
}
pub async fn get_voice_state(
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
user_id: &str,
) -> Result<Option<UserVoiceState>> {
let unique_key = format!("{}:{}", user_id, server_id.unwrap_or(channel_id));
let unique_key = format!(
"{}:{}",
&user_id,
channel.server_id.as_ref().unwrap_or(&channel.id)
);
let (joined_at, is_publishing, is_receiving, screensharing, camera) = get_connection()
.await?
@@ -376,21 +396,21 @@ pub async fn get_voice_state(
}
}
pub async fn get_channel_voice_state(channel: &Channel) -> Result<Option<v0::ChannelVoiceState>> {
let members = get_voice_channel_members(channel.id()).await?;
let server = channel.server();
pub async fn get_channel_voice_state(
channel: &UserVoiceChannel,
) -> Result<Option<v0::ChannelVoiceState>> {
let members = get_voice_channel_members(channel).await?;
if let Some(members) = members {
let mut participants = Vec::with_capacity(members.len());
for user_id in members {
if let Some(voice_state) = get_voice_state(channel.id(), server, &user_id).await? {
if let Some(voice_state) = get_voice_state(channel, &user_id).await? {
participants.push(voice_state);
} else {
log::info!("Voice state not found but member in voice channel members, removing.");
delete_voice_state(channel.id(), server, &user_id).await?;
delete_voice_state(channel, &user_id).await?;
}
}
@@ -398,7 +418,7 @@ pub async fn get_channel_voice_state(channel: &Channel) -> Result<Option<v0::Cha
participants.shrink_to_fit();
Ok(Some(v0::ChannelVoiceState {
id: channel.id().to_string(),
id: channel.id.clone(),
participants,
}))
} else {
@@ -406,12 +426,12 @@ pub async fn get_channel_voice_state(channel: &Channel) -> Result<Option<v0::Cha
}
}
pub async fn move_user(user: &str, from: &str, to: &str) -> Result<()> {
pub async fn move_user(user: &str, from_channel_id: &str, to_channel_id: &str) -> Result<()> {
get_connection()
.await?
.smove(
format!("vc-members-{from}"),
format!("vc-members-{to}"),
format!("vc_members:{from_channel_id}"),
format!("vc_members:{to_channel_id}"),
user,
)
.await
@@ -425,11 +445,13 @@ pub async fn sync_voice_permissions(
server: Option<&Server>,
role_id: Option<&str>,
) -> Result<()> {
let user_voice_channel = UserVoiceChannel::from_channel(channel);
let Some(node) = get_channel_node(channel.id()).await? else {
return Ok(());
};
for user_id in get_voice_channel_members(channel.id())
for user_id in get_voice_channel_members(&user_voice_channel)
.await?
.iter()
.flatten()
@@ -469,7 +491,9 @@ pub async fn sync_user_voice_permissions(
.as_ref()
.is_none_or(|member| member.roles.iter().any(|r| r == role_id))
}) {
let Some(voice_state) = get_voice_state(channel_id, server_id, &user.id).await? else {
let user_voice_channel = UserVoiceChannel::from_channel(channel);
let Some(voice_state) = get_voice_state(&user_voice_channel, &user.id).await? else {
return Ok(());
};
@@ -500,7 +524,7 @@ pub async fn sync_user_voice_permissions(
update_event.screensharing = voice_state.screensharing.then_some(can_video);
update_event.is_publishing = voice_state.is_publishing.then_some(can_speak);
update_voice_state(channel_id, server_id, &user.id, &update_event).await?;
update_voice_state(&user_voice_channel, &user.id, &update_event).await?;
voice_client
.update_permissions(
@@ -579,46 +603,94 @@ pub async fn get_call_notification_recipients(
}
pub async fn remove_user_from_voice_channels(
db: &Database,
voice_client: &VoiceClient,
user_id: &str,
) -> Result<()> {
for channel_id in get_user_voice_channels(user_id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, user_id).await?;
for channel in get_user_voice_channels(user_id).await? {
remove_user_from_voice_channel(voice_client, &channel, user_id).await?;
}
Ok(())
}
pub async fn remove_user_from_voice_channel(
db: &Database,
voice_client: &VoiceClient,
channel_id: &str,
channel: &UserVoiceChannel,
user_id: &str,
) -> Result<()> {
if let Some(node) = get_channel_node(channel_id).await? {
let _ = 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;
}
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
delete_voice_state(channel_id, channel.server(), user_id).await?;
delete_voice_state(channel, user_id).await?;
Ok(())
}
pub async fn delete_voice_channel(
voice_client: &VoiceClient,
channel_id: &str,
server_id: Option<&str>,
channel: &UserVoiceChannel,
) -> Result<()> {
if let Some(users) = get_voice_channel_members(channel_id).await? {
let node = get_channel_node(channel_id).await?.unwrap();
if let Some(users) = get_voice_channel_members(channel).await? {
let node = get_channel_node(&channel.id).await?.unwrap();
voice_client.delete_room(&node, &channel.id).await?;
voice_client.delete_room(&node, channel_id).await?;
delete_channel_voice_state(channel_id, server_id, &users).await?;
delete_channel_voice_state(channel, &users).await?;
};
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoomMetadata {
pub server: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserVoiceChannel {
pub id: String,
pub server_id: Option<String>,
}
impl UserVoiceChannel {
pub fn from_string(input: String) -> Self {
let mut parts = input.splitn(2, '-');
Self {
id: parts.next().unwrap().to_string(),
server_id: parts.next().map(ToString::to_string),
}
}
pub fn from_channel(channel: &Channel) -> Self {
Self {
id: channel.id().to_string(),
server_id: channel.server().map(ToString::to_string),
}
}
}
impl Display for UserVoiceChannel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.id)?;
if let Some(server_id) = &self.server_id {
f.write_char('-')?;
f.write_str(server_id)?
};
Ok(())
}
}
impl ToRedisArgs for UserVoiceChannel {
fn write_redis_args<W: ?Sized + RedisWrite>(&self, out: &mut W) {
out.write_arg_fmt(self);
}
}
impl FromRedisValue for UserVoiceChannel {
fn from_redis_value(v: &Value) -> Result<Self, RedisError> {
String::from_redis_value(v).map(UserVoiceChannel::from_string)
}
}

View File

@@ -1,5 +1,6 @@
use crate::{
models::{Channel, User},
voice::RoomMetadata,
Database,
};
use livekit_api::{
@@ -102,11 +103,16 @@ impl VoiceClient {
pub async fn create_room(&self, node: &str, channel: &Channel) -> Result<Room> {
let room = self.get_node(node)?;
let metadata = RoomMetadata {
server: channel.server().map(|id| id.to_string()),
};
room.client
.create_room(
channel.id(),
CreateRoomOptions {
empty_timeout: 5 * 60, // 5 minutes,
metadata: serde_json::to_string(&metadata).to_internal_error()?,
..Default::default()
},
)

View File

@@ -5,8 +5,9 @@ use revolt_database::{
iso8601_timestamp::{Duration, Timestamp},
util::reference::Reference,
voice::{
create_voice_state, delete_channel_node, delete_voice_state, get_user_moved_from_voice,
get_user_moved_to_voice, get_voice_channel_members, update_voice_state_tracks, VoiceClient,
create_voice_state, delete_channel_voice_state, delete_voice_state,
get_user_moved_from_voice, get_user_moved_to_voice, update_voice_state_tracks,
RoomMetadata, UserVoiceChannel, VoiceClient,
},
Database, AMQP,
};
@@ -50,27 +51,34 @@ pub async fn ingress(
let channel_id = event.room.as_ref().map(|r| &r.name);
let user_id = event.participant.as_ref().map(|r| &r.identity);
let room_metadata = if let Some(room) = event.room.as_ref() {
Some(serde_json::from_str::<RoomMetadata>(&room.metadata).to_internal_error()?)
} else {
None
};
match event.event.as_str() {
// User joined a channel
"participant_joined" => {
let channel_id = channel_id.to_internal_error()?;
let user_id = user_id.to_internal_error()?;
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
let server_id = room_metadata.to_internal_error()?.server;
let channel = UserVoiceChannel {
id: channel_id.clone(),
server_id: server_id.clone(),
};
let joined_at = Timestamp::UNIX_EPOCH
.checked_add(Duration::seconds(event.created_at))
.unwrap();
let voice_state =
create_voice_state(channel_id, channel.server(), user_id, joined_at).await?;
let voice_state = create_voice_state(&channel, user_id, joined_at).await?;
// Only publish one event when a user is moved from one channel to another.
if let Some(moved_from) = get_user_moved_to_voice(channel_id, user_id).await? {
EventV1::VoiceChannelMove {
user: user_id.to_string(),
from: moved_from,
from: moved_from.id,
to: channel_id.to_string(),
state: voice_state,
}
@@ -135,10 +143,13 @@ pub async fn ingress(
"participant_left" => {
let channel_id = channel_id.to_internal_error()?;
let user_id = user_id.to_internal_error()?;
let server_id = room_metadata.to_internal_error()?.server;
let channel = UserVoiceChannel {
id: channel_id.clone(),
server_id: server_id.clone(),
};
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
delete_voice_state(channel_id, channel.server(), user_id).await?;
delete_voice_state(&channel, user_id).await?;
// Dont send leave event when a user is moved
if get_user_moved_from_voice(channel_id, user_id)
@@ -159,8 +170,6 @@ pub async fn ingress(
// let members = get_voice_channel_members(channel_id).await?;
// if members.is_none_or(|m| m.is_empty()) {
// delete_channel_node(channel_id).await?;
//
// // The channel is empty so send out an "end" message for ringing
// if let Err(e) = amqp
// .dm_call_updated(user_id, channel_id, None, true, None)
@@ -204,8 +213,11 @@ pub async fn ingress(
let channel_id = channel_id.to_internal_error()?;
let user_id = user_id.to_internal_error()?;
let track = event.track.as_ref().to_internal_error()?;
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?;
let server_id = room_metadata.to_internal_error()?.server;
let channel = UserVoiceChannel {
id: channel_id.clone(),
server_id: server_id.clone(),
};
let user = Reference::from_unchecked(user_id).as_user(db).await?;
@@ -245,15 +257,14 @@ pub async fn ingress(
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?;
delete_voice_state(&channel, user_id).await?;
return Ok(EmptyResponse);
};
};
let partial = update_voice_state_tracks(
channel_id,
channel.server(),
&channel,
user_id,
event.event == "track_published" || event.event == "track_unmuted", // to avoid duplicating this entire case twice
track.source,
@@ -268,6 +279,16 @@ pub async fn ingress(
.p(channel_id.clone())
.await;
}
"room_finished" => {
let channel_id = channel_id.to_internal_error()?;
let server_id = room_metadata.to_internal_error()?.server;
let channel = UserVoiceChannel {
id: channel_id.clone(),
server_id: server_id.clone(),
};
delete_channel_voice_state(&channel, &[]).await?;
}
_ => {}
};

View File

@@ -1,4 +1,8 @@
use revolt_database::{Database, User, util::reference::Reference, voice::{VoiceClient, remove_user_from_voice_channels}};
use revolt_database::{
util::reference::Reference,
voice::{remove_user_from_voice_channels, VoiceClient},
Database, User,
};
use revolt_result::{create_error, Result};
use rocket::State;
use rocket_empty::EmptyResponse;
@@ -21,7 +25,7 @@ pub async fn delete_bot(
bot.delete(db).await?;
remove_user_from_voice_channels(db, voice_client, &bot.id).await?;
remove_user_from_voice_channels(voice_client, &bot.id).await?;
Ok(EmptyResponse)
}

View File

@@ -1,7 +1,10 @@
use revolt_database::{
AMQP, Channel, Database, PartialChannel, User, util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{
VoiceClient, delete_voice_channel, is_in_voice_channel, remove_user_from_voice_channel
}
util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{
delete_voice_channel, is_in_voice_channel, remove_user_from_voice_channel,
UserVoiceChannel, VoiceClient,
},
Channel, Database, PartialChannel, User, AMQP,
};
use revolt_models::v0;
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
@@ -54,15 +57,17 @@ pub async fn delete(
)
.await?;
if is_in_voice_channel(&user.id, channel.id()).await? {
remove_user_from_voice_channel(db, voice_client, channel.id(), &user.id).await?;
let user_voice_channel = UserVoiceChannel::from_channel(&channel);
if is_in_voice_channel(&user.id, &user_voice_channel).await? {
remove_user_from_voice_channel(voice_client, &user_voice_channel, &user.id).await?;
};
}
Channel::TextChannel { .. } => {
permissions.throw_if_lacking_channel_permission(ChannelPermission::ManageChannel)?;
channel.delete(db).await?;
delete_voice_channel(voice_client, channel.id(), channel.server()).await?;
delete_voice_channel(voice_client, &UserVoiceChannel::from_channel(&channel)).await?;
}
};

View File

@@ -1,6 +1,6 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{delete_voice_channel, VoiceClient},
voice::{delete_voice_channel, UserVoiceChannel, VoiceClient},
Channel, Database, File, PartialChannel, SystemMessage, User, AMQP,
};
use revolt_models::v0;
@@ -260,7 +260,7 @@ pub async fn edit(
.await?;
if channel.voice().is_none() {
delete_voice_channel(voice_client, channel.id(), channel.server()).await?;
delete_voice_channel(voice_client, &UserVoiceChannel::from_channel(&channel)).await?;
}
Ok(Json(channel.into()))

View File

@@ -1,7 +1,5 @@
use revolt_database::{
util::reference::Reference,
voice::{is_in_voice_channel, remove_user_from_voice_channel, VoiceClient},
Channel, Database, User, AMQP,
AMQP, Channel, Database, User, util::reference::Reference, voice::{UserVoiceChannel, VoiceClient, is_in_voice_channel, remove_user_from_voice_channel}
};
use revolt_permissions::ChannelPermission;
use revolt_result::{create_error, Result};
@@ -54,8 +52,10 @@ pub async fn remove_member(
return Err(create_error!(InvalidOperation));
};
if is_in_voice_channel(&member.id, channel.id()).await? {
remove_user_from_voice_channel(db, voice_client, channel.id(), &member.id).await?;
let user_voice_channel = UserVoiceChannel::from_channel(&channel);
if is_in_voice_channel(member.id, &user_voice_channel).await? {
remove_user_from_voice_channel(voice_client, &user_voice_channel, member.id).await?;
};
Ok(EmptyResponse)

View File

@@ -3,7 +3,8 @@ use revolt_database::{
util::{permissions::perms, reference::Reference},
voice::{
delete_voice_state, get_channel_node, get_user_voice_channels, get_voice_channel_members,
raise_if_in_voice, set_call_notification_recipients, set_channel_node, VoiceClient,
raise_if_in_voice, set_call_notification_recipients, set_channel_node, UserVoiceChannel,
VoiceClient,
},
Database, User,
};
@@ -50,7 +51,9 @@ pub async fn call(
let current_permissions = calculate_channel_permissions(&mut permissions).await;
current_permissions.throw_if_lacking_channel_permission(ChannelPermission::Connect)?;
if get_voice_channel_members(channel.id())
let user_voice_channel = UserVoiceChannel::from_channel(&channel);
if get_voice_channel_members(&user_voice_channel)
.await?
.zip(voice_info.max_users)
.is_some_and(|(ms, max_users)| ms.len() >= max_users)
@@ -79,22 +82,18 @@ pub async fn call(
// 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? {
if let Some(node) = get_channel_node(&channel_id).await? {
for previous_channel in get_user_voice_channels(&user.id).await? {
if let Some(node) = get_channel_node(&previous_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 _ = voice_client
.remove_user(&node, &user.id, &previous_channel.id)
.await;
};
let channel = Reference::from_unchecked(&channel_id)
.as_channel(db)
.await;
if channel.is_ok() {
delete_voice_state(&channel_id, channel.unwrap().server(), &user.id).await?;
}
delete_voice_state(&previous_channel, &user.id).await?;
}
} else {
raise_if_in_voice(&user, channel.id()).await?;
raise_if_in_voice(&user, &user_voice_channel).await?;
}
let token = voice_client

View File

@@ -1,6 +1,6 @@
use revolt_database::{
util::reference::Reference,
voice::{get_voice_state, VoiceClient},
voice::{get_voice_state, UserVoiceChannel, VoiceClient},
Channel, Database, User, AMQP,
};
use revolt_result::{create_error, Result, ToRevoltError};
@@ -32,9 +32,15 @@ pub async fn stop_ring(
return Err(create_error!(NoEffect));
}
if get_voice_state(channel.id(), None, &user.id)
.await?
.is_none()
if get_voice_state(
&UserVoiceChannel {
id: channel.id().to_string(),
server_id: None,
},
&user.id,
)
.await?
.is_none()
{
return Err(create_error!(NotConnected));
}
@@ -46,7 +52,7 @@ pub async fn stop_ring(
_ => return Err(create_error!(NoEffect)),
};
if members.iter().any(|m| &target_user.id == m) {
if members.iter().any(|m| target_user.id == m) {
if let Err(e) = amqp
.dm_call_updated(
&user.id,

View File

@@ -1,6 +1,9 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{get_user_voice_channel_in_server, remove_user_from_voice_channel, VoiceClient},
voice::{
get_user_voice_channel_in_server, remove_user_from_voice_channel, UserVoiceChannel,
VoiceClient,
},
Database, RemovalIntention, ServerBan, User,
};
use revolt_models::v0;
@@ -58,8 +61,16 @@ pub async fn ban(
.await?;
// 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(&target.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &target.id).await?;
if let Some(channel_id) = get_user_voice_channel_in_server(target.id, &server.id).await? {
remove_user_from_voice_channel(
voice_client,
&UserVoiceChannel {
id: channel_id,
server_id: Some(server.id.clone()),
},
target.id,
)
.await?;
}
}

View File

@@ -9,7 +9,7 @@ use revolt_database::{
voice::{
get_channel_node, get_user_voice_channel_in_server, set_channel_node,
set_user_moved_from_voice, set_user_moved_to_voice, sync_user_voice_permissions,
VoiceClient,
UserVoiceChannel, VoiceClient,
},
Database, File, PartialMember, User,
};
@@ -52,7 +52,9 @@ pub async fn edit(
let permissions = calculate_server_permissions(&mut query).await;
// Fetch target permissions
let mut target_query = DatabasePermissionQuery::new(db, &target_user).server(&server).member(&member);
let mut target_query = DatabasePermissionQuery::new(db, &target_user)
.server(&server)
.member(&member);
let target_permissions = calculate_server_permissions(&mut target_query).await;
// Check permissions in server
@@ -83,7 +85,7 @@ pub async fn edit(
}
if target_permissions.has_channel_permission(ChannelPermission::TimeoutMembers) {
return Err(create_error!(IsElevated))
return Err(create_error!(IsElevated));
}
}
@@ -99,7 +101,7 @@ pub async fn edit(
}
if data.voice_channel.is_some() && data.remove.contains(&FieldsMember::VoiceChannel) {
return Err(create_error!(InvalidOperation))
return Err(create_error!(InvalidOperation));
}
if data.voice_channel.is_some() || data.remove.contains(&FieldsMember::VoiceChannel) {
@@ -217,8 +219,19 @@ pub async fn edit(
}
};
set_user_moved_from_voice(&channel, new_voice_channel.id(), &target_user.id).await?;
set_user_moved_to_voice(new_voice_channel.id(), &channel, &target_user.id).await?;
let new_user_voice_channel = UserVoiceChannel::from_channel(&new_voice_channel);
let old_user_voice_channel = UserVoiceChannel {
id: channel.clone(),
server_id: new_user_voice_channel.server_id.clone(),
};
set_user_moved_from_voice(&channel, &new_user_voice_channel, &target_user.id).await?;
set_user_moved_to_voice(
new_voice_channel.id(),
&old_user_voice_channel,
&target_user.id,
)
.await?;
let mut query = perms(db, &target_user).channel(&new_voice_channel);
let permissions = calculate_channel_permissions(&mut query).await;

View File

@@ -1,6 +1,9 @@
use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{get_user_voice_channel_in_server, remove_user_from_voice_channel, VoiceClient},
voice::{
get_user_voice_channel_in_server, remove_user_from_voice_channel, UserVoiceChannel,
VoiceClient,
},
Database, RemovalIntention, User,
};
use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -46,8 +49,16 @@ pub async fn kick(
.remove(db, &server, RemovalIntention::Kick, false)
.await?;
if let Some(channel_id) = get_user_voice_channel_in_server(&target.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &target.id).await?;
if let Some(channel_id) = get_user_voice_channel_in_server(target.id, &server.id).await? {
remove_user_from_voice_channel(
voice_client,
&UserVoiceChannel {
id: channel_id,
server_id: Some(server.id.clone()),
},
target.id,
)
.await?;
};
Ok(EmptyResponse)

View File

@@ -2,7 +2,7 @@ use revolt_database::{
util::reference::Reference,
voice::{
delete_voice_channel, get_user_voice_channel_in_server, remove_user_from_voice_channel,
VoiceClient,
UserVoiceChannel, VoiceClient,
},
Database, RemovalIntention, User,
};
@@ -29,13 +29,28 @@ pub async fn delete(
if server.owner == user.id {
for channel_id in &server.channels {
delete_voice_channel(voice_client, channel_id, Some(&server.id)).await?;
delete_voice_channel(
voice_client,
&UserVoiceChannel {
id: channel_id.clone(),
server_id: Some(server.id.clone()),
},
)
.await?;
}
server.delete(db).await
} else {
if let Some(channel_id) = get_user_voice_channel_in_server(&user.id, &server.id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, &user.id).await?;
remove_user_from_voice_channel(
voice_client,
&UserVoiceChannel {
id: channel_id,
server_id: Some(server.id.clone()),
},
&user.id,
)
.await?;
};
member