fix: remove VoiceChannel channel type

fix: calculate user overwrites correctly
fix: dont include personal info in livekit user metadata
fix: revoke video permissons on denied publish
fix: add video to default permissions
This commit is contained in:
Zomatree
2025-08-19 17:37:09 +01:00
parent c6f121f585
commit 0a4d77ffd3
27 changed files with 172 additions and 282 deletions

View File

@@ -9,14 +9,13 @@ use crate::{
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
options::FindOptions,
},
AbstractChannels, AbstractServers, Channel, Invite, MongoDb, User, DISCRIMINATOR_SEARCH_SPACE,
AbstractServers, Invite, MongoDb, User, DISCRIMINATOR_SEARCH_SPACE,
};
use bson::{oid::ObjectId, to_bson};
use futures::StreamExt;
use iso8601_timestamp::Timestamp;
use rand::seq::SliceRandom;
use revolt_permissions::DEFAULT_WEBHOOK_PERMISSIONS;
use revolt_result::{Error, ErrorType};
use revolt_permissions::{ChannelPermission, DEFAULT_WEBHOOK_PERMISSIONS};
use serde::{Deserialize, Serialize};
use unicode_segmentation::UnicodeSegmentation;
@@ -1081,6 +1080,14 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
channel_id: String,
}
#[allow(clippy::enum_variant_names)]
#[derive(serde::Serialize, serde::Deserialize)]
enum Channel {
Group { owner: String },
TextChannel { server: String },
VoiceChannel { server: String }
}
let webhooks = db
.db()
.collection::<WebhookShell>("channel_webhooks")
@@ -1092,9 +1099,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.await;
for webhook in webhooks {
#[allow(deprecated)]
match db.fetch_channel(&webhook.channel_id).await {
Ok(channel) => {
match db.col::<Channel>("channels").find_one(doc! { "_id": &webhook.channel_id }).await.unwrap() {
Some(channel) => {
let creator_id = match channel {
Channel::Group { owner, .. } => owner,
Channel::TextChannel { server, .. }
@@ -1102,7 +1108,6 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
let server = db.fetch_server(&server).await.expect("server");
server.owner
}
_ => unreachable!("not server or group channel!"),
};
db.db()
@@ -1120,17 +1125,13 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.await
.expect("update webhook");
}
Err(Error {
error_type: ErrorType::NotFound,
..
}) => {
None => {
db.db()
.collection::<WebhookShell>("channel_webhooks")
.delete_one(doc! { "_id": webhook._id })
.await
.expect("failed to delete invalid webhook");
}
Err(err) => panic!("{err:?}"),
}
}
}
@@ -1171,40 +1172,6 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("failed to update users");
}
if revision <= 41 {
info!("Running migration [revision 32 / 26-01-2025]: Add `is_publishing` and `is_receiving` to members");
db.col::<Document>("server_members")
.update_many(
doc! {},
doc! {
"$set": {
"is_publishing": true,
"is_receiving": true
}
}
)
.await
.expect("Failed to update members");
}
if revision <= 42 {
info!("Running migration [revision 33 / 29-04-2025]: Convert all `VoiceChannel`'s into `TextChannel` ");
db.col::<Document>("channels")
.update_many(
doc! { "channel_type": "VoiceChannel" },
doc! {
"$set": {
"channel_type": "TextChannel",
"voice": {}
}
}
)
.await
.expect("Failed to update voice channels");
};
if revision <= 43 {
info!(
"Running migration [revision 43 / 05-06-2025]: convert role ranks to uniform numbers."
@@ -1262,6 +1229,41 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
}
if revision <= 46 {
info!("Running migration [revision 46 / 29-04-2025]: Convert all `VoiceChannel`'s into `TextChannel`");
db.col::<Document>("channels")
.update_many(
doc! { "channel_type": "VoiceChannel" },
doc! {
"$set": {
"channel_type": "TextChannel",
"voice": {}
}
}
)
.await
.expect("Failed to update voice channels");
};
if revision <= 47 {
info!("Running migration [revision 47 / 29-04-2025]: Add Video to default permissions");
db.col::<Document>("servers")
.update_many(
doc! { },
doc! {
"$bit": {
"default_permissions": {
"or": ChannelPermission::Video as i64
},
}
}
)
.await
.expect("Failed to update voice channels");
};
// Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION.max(revision)
}

View File

@@ -9,8 +9,7 @@ use serde::{Deserialize, Serialize};
use ulid::Ulid;
use crate::{
events::client::EventV1, Database, File, PartialServer,
Server, SystemMessage, User, AMQP,
events::client::EventV1, Database, File, PartialServer, Server, SystemMessage, User, AMQP,
};
#[cfg(feature = "mongodb")]
@@ -112,37 +111,6 @@ auto_derived!(
#[serde(skip_serializing_if = "Option::is_none")]
voice: Option<VoiceInformation>,
},
#[deprecated = "Use TextChannel { voice } instead"]
VoiceChannel {
/// Unique Id
#[serde(rename = "_id")]
id: String,
/// Id of the server this channel belongs to
server: String,
/// Display name of the channel
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
/// Channel description
description: Option<String>,
/// Custom icon attachment
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<File>,
/// Default permissions assigned to users in this channel
#[serde(skip_serializing_if = "Option::is_none")]
default_permissions: Option<OverrideField>,
/// Permissions assigned based on role to this channel
#[serde(
default = "HashMap::<String, OverrideField>::new",
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
)]
role_permissions: HashMap<String, OverrideField>,
/// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
},
}
#[derive(Default)]
@@ -449,15 +417,14 @@ impl Channel {
Channel::DirectMessage { id, .. }
| Channel::Group { id, .. }
| Channel::SavedMessages { id, .. }
| Channel::TextChannel { id, .. }
| Channel::VoiceChannel { id, .. } => id,
| Channel::TextChannel { id, .. } => id,
}
}
/// Clone this channel's server id
pub fn server(&self) -> Option<&str> {
match self {
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
Channel::TextChannel { server, .. } => {
Some(server)
}
_ => None,
@@ -467,7 +434,7 @@ impl Channel {
/// Gets this channel's voice information
pub fn voice(&self) -> Option<Cow<VoiceInformation>> {
match self {
Self::DirectMessage { .. } | Channel::VoiceChannel { .. } => {
Self::DirectMessage { .. } | Self::Group { .. } => {
Some(Cow::Owned(VoiceInformation::default()))
}
Self::TextChannel {
@@ -659,7 +626,6 @@ impl Channel {
voice.replace(v);
}
}
Self::VoiceChannel { .. } => {}
}
}

View File

@@ -115,7 +115,10 @@ auto_derived!(
#[serde(rename = "message_unpinned")]
MessageUnpinned { id: String, by: String },
#[serde(rename = "call_started")]
CallStarted { by: String, finished_at: Option<Timestamp> },
CallStarted {
by: String,
finished_at: Option<Timestamp>,
},
}
/// Name and / or avatar override information
@@ -328,9 +331,7 @@ impl Message {
}
let server_id = match channel {
Channel::TextChannel { ref server, .. } | Channel::VoiceChannel { ref server, .. } => {
Some(server.clone())
}
Channel::TextChannel { ref server, .. } => Some(server.clone()),
_ => None,
};
@@ -488,8 +489,7 @@ impl Message {
user_mentions.retain(|m| recipients_hash.contains(m));
role_mentions.clear();
}
Channel::TextChannel { ref server, .. }
| Channel::VoiceChannel { ref server, .. } => {
Channel::TextChannel { ref server, .. }=> {
let mentions_vec = Vec::from_iter(user_mentions.iter().cloned());
let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
@@ -681,7 +681,6 @@ impl Message {
)
.await?;
if !self.has_suppressed_notifications()
&& (self.mentions.is_some() || self.contains_mass_push_mention())
{
@@ -797,7 +796,7 @@ impl Message {
query: MessageQuery,
perspective: &User,
include_users: Option<bool>,
server_id: Option<String>,
server_id: Option<&str>,
) -> Result<BulkMessageResponse> {
let messages: Vec<v0::Message> = db
.fetch_messages(query)
@@ -840,9 +839,7 @@ impl Message {
v0::SystemMessage::MessageUnpinned { by, .. } => {
users.push(by.clone());
}
v0::SystemMessage::CallStarted { by, .. } => {
users.push(by.clone())
}
v0::SystemMessage::CallStarted { by, .. } => users.push(by.clone()),
}
}
users
@@ -857,7 +854,7 @@ impl Message {
users,
members: if let Some(server_id) = server_id {
Some(
db.fetch_members(&server_id, &user_ids)
db.fetch_members(server_id, &user_ids)
.await?
.into_iter()
.map(Into::into)

View File

@@ -4,10 +4,18 @@ use revolt_result::{create_error, Result};
use crate::voice::get_channel_voice_state;
use crate::{
events::client::EventV1, if_false, util::permissions::DatabasePermissionQuery, Channel,
events::client::EventV1, util::permissions::DatabasePermissionQuery, Channel,
Database, File, Server, SystemMessage, User,
};
fn default_true() -> bool {
true
}
fn is_true(x: &bool) -> bool {
*x
}
auto_derived_partial!(
/// Server Member
pub struct Member {
@@ -33,11 +41,11 @@ auto_derived_partial!(
pub timeout: Option<Timestamp>,
/// Whether the member is server-wide voice muted
#[serde(skip_serializing_if = "Option::is_none")]
pub can_publish: Option<bool>,
#[serde(skip_serializing_if = "is_true", default = "default_true")]
pub can_publish: bool,
/// Whether the member is server-wide voice deafened
#[serde(skip_serializing_if = "Option::is_none")]
pub can_receive: Option<bool>,
#[serde(skip_serializing_if = "is_true", default = "default_true")]
pub can_receive: bool,
},
"PartialMember"
);
@@ -79,8 +87,8 @@ impl Default for Member {
avatar: None,
roles: vec![],
timeout: None,
can_publish: None,
can_receive: None,
can_publish: true,
can_receive: true,
}
}
}
@@ -211,8 +219,8 @@ impl Member {
FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None,
FieldsMember::CanReceive => self.can_receive = None,
FieldsMember::CanPublish => self.can_publish = None,
FieldsMember::CanReceive => self.can_receive = true,
FieldsMember::CanPublish => self.can_publish = true,
}
}

View File

@@ -14,7 +14,7 @@ use validator::HasLen;
use revolt_result::Result;
use super::DelayedTask;
use crate::Channel::{TextChannel, VoiceChannel};
use crate::Channel::TextChannel;
/// Enumeration of possible events
#[derive(Debug, Eq, PartialEq)]
@@ -191,17 +191,14 @@ pub async fn handle_ack_event(
.await
.expect("Failed to fetch channel from db");
match channel {
TextChannel { server, .. } | VoiceChannel { server, .. } => {
if let Err(err) =
amqp.mass_mention_message_sent(server, mass_mentions).await
{
revolt_config::capture_error(&err);
}
}
_ => {
panic!("Unknown channel type when sending mass mention event");
if let TextChannel { server, .. } = channel {
if let Err(err) =
amqp.mass_mention_message_sent(server, mass_mentions).await
{
revolt_config::capture_error(&err);
}
} else {
panic!("Unknown channel type when sending mass mention event");
}
}
}

View File

@@ -202,25 +202,6 @@ impl From<crate::Channel> for Channel {
nsfw,
voice: voice.map(|voice| voice.into()),
},
crate::Channel::VoiceChannel {
id,
server,
name,
description,
icon,
default_permissions,
role_permissions,
nsfw,
} => Channel::VoiceChannel {
id,
server,
name,
description,
icon: icon.map(|file| file.into()),
default_permissions,
role_permissions,
nsfw,
},
}
}
}
@@ -285,25 +266,6 @@ impl From<Channel> for crate::Channel {
nsfw,
voice: voice.map(|voice| voice.into()),
},
Channel::VoiceChannel {
id,
server,
name,
description,
icon,
default_permissions,
role_permissions,
nsfw,
} => crate::Channel::VoiceChannel {
id,
server,
name,
description,
icon: icon.map(|file| file.into()),
default_permissions,
role_permissions,
nsfw,
},
}
}
}

View File

@@ -159,9 +159,7 @@ impl<'z> BulkDatabasePermissionQuery<'z> {
Channel::DirectMessage { .. } => ChannelType::DirectMessage,
Channel::Group { .. } => ChannelType::Group,
Channel::SavedMessages { .. } => ChannelType::SavedMessages,
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
ChannelType::ServerChannel
}
Channel::TextChannel { .. } => ChannelType::ServerChannel,
}
} else {
ChannelType::Unknown

View File

@@ -187,17 +187,17 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
async fn do_we_have_publish_overwrites(&mut self) -> bool {
if let Some(member) = &self.member {
member.can_publish.unwrap_or(false)
member.can_publish
} else {
false
true
}
}
async fn do_we_have_receive_overwrites(&mut self) -> bool {
if let Some(member) = &self.member {
member.can_receive.unwrap_or(false)
member.can_receive
} else {
false
true
}
}
@@ -216,9 +216,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
Cow::Borrowed(Channel::SavedMessages { .. })
| Cow::Owned(Channel::SavedMessages { .. }) => ChannelType::SavedMessages,
Cow::Borrowed(Channel::TextChannel { .. })
| Cow::Owned(Channel::TextChannel { .. })
| Cow::Borrowed(Channel::VoiceChannel { .. })
| Cow::Owned(Channel::VoiceChannel { .. }) => ChannelType::ServerChannel,
| Cow::Owned(Channel::TextChannel { .. }) => ChannelType::ServerChannel,
}
} else {
ChannelType::Unknown
@@ -349,9 +347,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
#[allow(deprecated)]
match channel {
Cow::Borrowed(Channel::TextChannel { server, .. })
| Cow::Owned(Channel::TextChannel { server, .. })
| Cow::Borrowed(Channel::VoiceChannel { server, .. })
| Cow::Owned(Channel::VoiceChannel { server, .. }) => {
| Cow::Owned(Channel::TextChannel { server, .. }) => {
if let Some(known_server) =
// I'm not sure why I can't just pattern match both at once here?
// It throws some weird error and the provided fix doesn't work :/

View File

@@ -129,14 +129,19 @@ pub async fn get_user_voice_channel_in_server(
.to_internal_error()
}
pub fn get_allowed_sources(limits: &FeaturesLimits, permissions: PermissionValue) -> Vec<&'static str> {
pub fn get_allowed_sources(
limits: &FeaturesLimits,
permissions: PermissionValue,
) -> Vec<&'static str> {
let mut allowed_sources = Vec::new();
if permissions.has(ChannelPermission::Speak as u64) {
println!("can speak");
allowed_sources.push("microphone")
};
if permissions.has(ChannelPermission::Video as u64) && limits.video {
println!("can video");
allowed_sources.extend(["camera", "screen_share", "screen_share_audio"]);
};
@@ -362,9 +367,7 @@ pub async fn sync_voice_permissions(
.iter()
.flatten()
{
let user = Reference::from_unchecked(&user_id)
.as_user(db)
.await?;
let user = Reference::from_unchecked(user_id).as_user(db).await?;
sync_user_voice_permissions(db, voice_client, &node, &user, channel, server, role_id)
.await?;

View File

@@ -1,4 +1,4 @@
use crate::models::{Channel, User};
use crate::{models::{Channel, User}, Database};
use livekit_api::{
access_token::{AccessToken, VideoGrants},
services::room::{CreateRoomOptions, RoomClient as InnerRoomClient, UpdateParticipantOptions},
@@ -63,6 +63,7 @@ impl VoiceClient {
pub async fn create_token(
&self,
node: &str,
db: &Database,
user: &User,
permissions: PermissionValue,
channel: &Channel,
@@ -75,11 +76,12 @@ impl VoiceClient {
AccessToken::with_api_key(&room.node.key, &room.node.secret)
.with_name(&format!("{}#{}", user.username, user.discriminator))
.with_identity(&user.id)
.with_metadata(&serde_json::to_string(&user).to_internal_error()?)
.with_metadata(&serde_json::to_string(&user.clone().into(db, None).await).to_internal_error()?)
.with_ttl(Duration::from_secs(10))
.with_grants(VideoGrants {
room_join: true,
can_publish: true,
can_publish_data: false,
can_publish_sources: allowed_sources
.into_iter()
.map(ToString::to_string)