sdk: Add RoomPowerLevelSettings.
fix: Preserve the event of any settings that are changed back to the default level. sdk: Rename get_room_power_levels (drop the get). chore: Refactor with more sensible naming. sdk: Clean up the RoomPowerLevelChanges API.
This commit is contained in:
Generated
+1
@@ -2985,6 +2985,7 @@ dependencies = [
|
||||
"image",
|
||||
"imbl",
|
||||
"indexmap 2.2.2",
|
||||
"js_int",
|
||||
"language-tags",
|
||||
"mas-oidc-client",
|
||||
"matrix-sdk-base",
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use matrix_sdk::{room::Room as SdkRoom, RoomMemberships, RoomNotableTags, RoomState};
|
||||
use matrix_sdk::{
|
||||
room::{power_levels::RoomPowerLevelChanges, Room as SdkRoom},
|
||||
RoomMemberships, RoomNotableTags, RoomState,
|
||||
};
|
||||
use matrix_sdk_ui::timeline::RoomExt;
|
||||
use mime::Mime;
|
||||
use ruma::{
|
||||
api::client::room::report_content,
|
||||
assign,
|
||||
events::room::{avatar::ImageInfo as RumaAvatarImageInfo, MediaSource},
|
||||
EventId, UserId,
|
||||
EventId, Int, UserId,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
@@ -564,6 +567,35 @@ impl Room {
|
||||
|
||||
self.mark_as_read().await
|
||||
}
|
||||
|
||||
pub async fn build_power_level_changes_from_current(
|
||||
&self,
|
||||
) -> Result<RoomPowerLevelChanges, ClientError> {
|
||||
let power_levels = self.inner.room_power_levels().await?;
|
||||
Ok(power_levels.into())
|
||||
}
|
||||
|
||||
pub async fn apply_power_level_changes(
|
||||
&self,
|
||||
changes: RoomPowerLevelChanges,
|
||||
) -> Result<(), ClientError> {
|
||||
self.inner.apply_power_level_changes(changes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_power_level_for_user(
|
||||
&self,
|
||||
user_id: String,
|
||||
power_level: i64,
|
||||
) -> Result<(), ClientError> {
|
||||
let user_id = UserId::parse(&user_id)?;
|
||||
let power_level = Int::new(power_level).context("Invalid power level")?;
|
||||
self.inner
|
||||
.update_power_levels(vec![(&user_id, power_level)])
|
||||
.await
|
||||
.map_err(|e| ClientError::Generic { msg: e.to_string() })?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export(callback_interface)]
|
||||
|
||||
@@ -84,6 +84,7 @@ http = { workspace = true }
|
||||
hyper = { version = "0.14.20", features = ["http1", "http2", "server"], optional = true }
|
||||
imbl = { version = "2.0.0", features = ["serde"] }
|
||||
indexmap = "2.0.2"
|
||||
js_int = "0.2.2"
|
||||
language-tags = { version = "0.3.2", optional = true }
|
||||
mas-oidc-client = { version = "0.7.0", optional = true }
|
||||
matrix-sdk-base = { workspace = true, features = ["uniffi"] }
|
||||
|
||||
@@ -81,6 +81,7 @@ use crate::{
|
||||
event_handler::{EventHandler, EventHandlerDropGuard, EventHandlerHandle, SyncEvent},
|
||||
media::{MediaFormat, MediaRequest},
|
||||
notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode},
|
||||
room::power_levels::{RoomPowerLevelChanges, RoomPowerLevelsExt},
|
||||
sync::RoomUpdate,
|
||||
utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
|
||||
BaseRoom, Client, Error, HttpError, HttpResult, Result, RoomState, TransmissionProgress,
|
||||
@@ -89,6 +90,7 @@ use crate::{
|
||||
pub mod futures;
|
||||
mod member;
|
||||
mod messages;
|
||||
pub mod power_levels;
|
||||
|
||||
pub use self::{
|
||||
member::{RoomMember, RoomMemberRole},
|
||||
@@ -1720,7 +1722,7 @@ impl Room {
|
||||
&self,
|
||||
updates: Vec<(&UserId, Int)>,
|
||||
) -> Result<send_state_event::v3::Response> {
|
||||
let mut power_levels = self.get_room_power_levels().await?;
|
||||
let mut power_levels = self.room_power_levels().await?;
|
||||
|
||||
for (user_id, new_level) in updates {
|
||||
if new_level == power_levels.users_default {
|
||||
@@ -1733,7 +1735,19 @@ impl Room {
|
||||
self.send_state_event(RoomPowerLevelsEventContent::from(power_levels)).await
|
||||
}
|
||||
|
||||
async fn get_room_power_levels(&self) -> Result<RoomPowerLevels> {
|
||||
/// Applies a set of power level changes to this room.
|
||||
///
|
||||
/// Any values that are `None` in the given `RoomPowerLevelChanges` will
|
||||
/// remain unchanged.
|
||||
pub async fn apply_power_level_changes(&self, changes: RoomPowerLevelChanges) -> Result<()> {
|
||||
let mut power_levels = self.room_power_levels().await?;
|
||||
power_levels.apply(changes)?;
|
||||
self.send_state_event(RoomPowerLevelsEventContent::from(power_levels)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current power levels of this room.
|
||||
pub async fn room_power_levels(&self) -> Result<RoomPowerLevels> {
|
||||
Ok(self
|
||||
.get_state_event_static::<RoomPowerLevelsEventContent>()
|
||||
.await?
|
||||
@@ -2015,7 +2029,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_redact_own(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_redact_own_event(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_redact_own_event(user_id))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to redact
|
||||
@@ -2023,7 +2037,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_redact_other(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_redact_event_of_other(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_redact_event_of_other(user_id))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to ban in the
|
||||
@@ -2031,7 +2045,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_ban(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_ban(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_ban(user_id))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to kick in the
|
||||
@@ -2039,7 +2053,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_invite(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_invite(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_invite(user_id))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to kick in the
|
||||
@@ -2047,7 +2061,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_kick(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_kick(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_kick(user_id))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to send a
|
||||
@@ -2059,7 +2073,7 @@ impl Room {
|
||||
user_id: &UserId,
|
||||
state_event: StateEventType,
|
||||
) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_send_state(user_id, state_event))
|
||||
Ok(self.room_power_levels().await?.user_can_send_state(user_id, state_event))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to send a
|
||||
@@ -2071,7 +2085,7 @@ impl Room {
|
||||
user_id: &UserId,
|
||||
message: MessageLikeEventType,
|
||||
) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_send_message(user_id, message))
|
||||
Ok(self.room_power_levels().await?.user_can_send_message(user_id, message))
|
||||
}
|
||||
|
||||
/// Returns true if the user with the given user_id is able to trigger a
|
||||
@@ -2079,7 +2093,7 @@ impl Room {
|
||||
///
|
||||
/// The call may fail if there is an error in getting the power levels.
|
||||
pub async fn can_user_trigger_room_notification(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.get_room_power_levels().await?.user_can_trigger_room_notification(user_id))
|
||||
Ok(self.room_power_levels().await?.user_can_trigger_room_notification(user_id))
|
||||
}
|
||||
|
||||
/// Get a list of servers that should know this room.
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
//! Power level configuration types used in [the `room` module][super].
|
||||
|
||||
use ruma::events::{room::power_levels::RoomPowerLevels, StateEventType};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// A set of common power levels required for various operations within a room,
|
||||
/// that can be applied as a single operation. When updating these
|
||||
/// settings, any levels that are `None` will remain unchanged.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
|
||||
pub struct RoomPowerLevelChanges {
|
||||
// Actions
|
||||
/// The level required to ban a user.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub ban: Option<i64>,
|
||||
/// The level required to invite a user.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub invite: Option<i64>,
|
||||
/// The level required to kick a user.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub kick: Option<i64>,
|
||||
/// The level required to redact an event.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub redact: Option<i64>,
|
||||
|
||||
// Events
|
||||
/// The default level required to send message events.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub events_default: Option<i64>,
|
||||
/// The default level required to send state events.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub state_default: Option<i64>,
|
||||
/// The default power level for every user in the room.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub users_default: Option<i64>,
|
||||
/// The level required to change the room's name.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub room_name: Option<i64>,
|
||||
/// The level required to change the room's avatar.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub room_avatar: Option<i64>,
|
||||
/// The level required to change the room's topic.
|
||||
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
|
||||
pub room_topic: Option<i64>,
|
||||
}
|
||||
|
||||
impl RoomPowerLevelChanges {
|
||||
/// Constructs an empty set of `RoomPowerLevelChanges`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ban: None,
|
||||
invite: None,
|
||||
kick: None,
|
||||
redact: None,
|
||||
events_default: None,
|
||||
state_default: None,
|
||||
users_default: None,
|
||||
room_name: None,
|
||||
room_avatar: None,
|
||||
room_topic: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RoomPowerLevelChanges {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoomPowerLevels> for RoomPowerLevelChanges {
|
||||
fn from(value: RoomPowerLevels) -> Self {
|
||||
Self {
|
||||
ban: Some(value.ban.into()),
|
||||
invite: Some(value.invite.into()),
|
||||
kick: Some(value.kick.into()),
|
||||
redact: Some(value.redact.into()),
|
||||
events_default: Some(value.events_default.into()),
|
||||
state_default: Some(value.state_default.into()),
|
||||
users_default: Some(value.users_default.into()),
|
||||
room_name: value
|
||||
.events
|
||||
.get(&StateEventType::RoomName.into())
|
||||
.map(|v| (*v).into())
|
||||
.or(Some(value.state_default.into())),
|
||||
room_avatar: value
|
||||
.events
|
||||
.get(&StateEventType::RoomAvatar.into())
|
||||
.map(|v| (*v).into())
|
||||
.or(Some(value.state_default.into())),
|
||||
room_topic: value
|
||||
.events
|
||||
.get(&StateEventType::RoomTopic.into())
|
||||
.map(|v| (*v).into())
|
||||
.or(Some(value.state_default.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait RoomPowerLevelsExt {
|
||||
/// Applies the updated settings to the power levels. Any levels that are
|
||||
/// `None` will remain unchanged. Unlike with members, we don't remove the
|
||||
/// event if the new level matches the default as this could result in
|
||||
/// unintended privileges when updating the default power level in
|
||||
/// isolation of the others.
|
||||
fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()>;
|
||||
}
|
||||
|
||||
impl RoomPowerLevelsExt for RoomPowerLevels {
|
||||
fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()> {
|
||||
if let Some(ban) = settings.ban {
|
||||
self.ban = ban.try_into()?;
|
||||
}
|
||||
if let Some(invite) = settings.invite {
|
||||
self.invite = invite.try_into()?;
|
||||
}
|
||||
if let Some(kick) = settings.kick {
|
||||
self.kick = kick.try_into()?;
|
||||
}
|
||||
if let Some(redact) = settings.redact {
|
||||
self.redact = redact.try_into()?;
|
||||
}
|
||||
if let Some(events_default) = settings.events_default {
|
||||
self.events_default = events_default.try_into()?;
|
||||
}
|
||||
if let Some(state_default) = settings.state_default {
|
||||
self.state_default = state_default.try_into()?;
|
||||
}
|
||||
if let Some(users_default) = settings.users_default {
|
||||
self.users_default = users_default.try_into()?;
|
||||
}
|
||||
if let Some(room_name) = settings.room_name {
|
||||
self.events.insert(StateEventType::RoomName.into(), room_name.try_into()?);
|
||||
}
|
||||
if let Some(room_avatar) = settings.room_avatar {
|
||||
self.events.insert(StateEventType::RoomAvatar.into(), room_avatar.try_into()?);
|
||||
}
|
||||
if let Some(room_topic) = settings.room_topic {
|
||||
self.events.insert(StateEventType::RoomTopic.into(), room_topic.try_into()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<js_int::TryFromIntError> for crate::error::Error {
|
||||
fn from(e: js_int::TryFromIntError) -> Self {
|
||||
crate::error::Error::UnknownError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
events::room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
int,
|
||||
power_levels::NotificationPowerLevels,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_apply_actions() {
|
||||
// Given a set of power levels and some settings that only change the
|
||||
// actions.
|
||||
let mut power_levels = default_power_levels();
|
||||
|
||||
let new_level = int!(100);
|
||||
let settings = RoomPowerLevelChanges {
|
||||
ban: Some(new_level.into()),
|
||||
invite: Some(new_level.into()),
|
||||
kick: Some(new_level.into()),
|
||||
redact: Some(new_level.into()),
|
||||
events_default: None,
|
||||
state_default: None,
|
||||
users_default: None,
|
||||
room_name: None,
|
||||
room_avatar: None,
|
||||
room_topic: None,
|
||||
};
|
||||
|
||||
// When applying the settings to the power levels.
|
||||
let original_levels = power_levels.clone();
|
||||
power_levels.apply(settings).unwrap();
|
||||
|
||||
// Then the levels for the actions should be updated.
|
||||
assert_eq!(power_levels.ban, new_level);
|
||||
assert_eq!(power_levels.invite, new_level);
|
||||
assert_eq!(power_levels.kick, new_level);
|
||||
assert_eq!(power_levels.redact, new_level);
|
||||
// And the rest should remain unchanged.
|
||||
assert_eq!(power_levels.events_default, original_levels.events_default);
|
||||
assert_eq!(power_levels.state_default, original_levels.state_default);
|
||||
assert_eq!(power_levels.users_default, original_levels.users_default);
|
||||
assert_eq!(power_levels.events, original_levels.events);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_room_settings() {
|
||||
// Given a set of power levels and some settings that only change the specific
|
||||
// state event levels.
|
||||
let mut power_levels = default_power_levels();
|
||||
|
||||
let new_level = int!(100);
|
||||
let settings = RoomPowerLevelChanges {
|
||||
ban: None,
|
||||
invite: None,
|
||||
kick: None,
|
||||
redact: None,
|
||||
events_default: None,
|
||||
state_default: None,
|
||||
users_default: None,
|
||||
room_name: Some(new_level.into()),
|
||||
room_avatar: Some(new_level.into()),
|
||||
room_topic: Some(new_level.into()),
|
||||
};
|
||||
|
||||
// When applying the settings to the power levels.
|
||||
let original_levels = power_levels.clone();
|
||||
power_levels.apply(settings).unwrap();
|
||||
|
||||
// Then levels for the necessary state events should be added.
|
||||
assert_eq!(
|
||||
power_levels.events,
|
||||
BTreeMap::from_iter(vec![
|
||||
(StateEventType::RoomName.into(), new_level),
|
||||
(StateEventType::RoomAvatar.into(), new_level),
|
||||
(StateEventType::RoomTopic.into(), new_level),
|
||||
])
|
||||
);
|
||||
// And the rest should remain unchanged.
|
||||
assert_eq!(power_levels.ban, original_levels.ban);
|
||||
assert_eq!(power_levels.invite, original_levels.invite);
|
||||
assert_eq!(power_levels.kick, original_levels.kick);
|
||||
assert_eq!(power_levels.redact, original_levels.redact);
|
||||
assert_eq!(power_levels.events_default, original_levels.events_default);
|
||||
assert_eq!(power_levels.state_default, original_levels.state_default);
|
||||
assert_eq!(power_levels.users_default, original_levels.users_default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_state_event_to_default() {
|
||||
// Given a set of power levels and some settings that change the room name level
|
||||
// back to the default level.
|
||||
let original_level = int!(100);
|
||||
let mut power_levels = default_power_levels();
|
||||
power_levels.events = BTreeMap::from_iter(vec![
|
||||
(StateEventType::RoomName.into(), original_level),
|
||||
(StateEventType::RoomAvatar.into(), original_level),
|
||||
(StateEventType::RoomTopic.into(), original_level),
|
||||
]);
|
||||
|
||||
let settings = RoomPowerLevelChanges {
|
||||
ban: None,
|
||||
invite: None,
|
||||
kick: None,
|
||||
redact: None,
|
||||
events_default: None,
|
||||
state_default: None,
|
||||
users_default: None,
|
||||
room_name: Some(power_levels.state_default.into()),
|
||||
room_avatar: None,
|
||||
room_topic: None,
|
||||
};
|
||||
|
||||
// When applying the settings to the power levels.
|
||||
let original_levels = power_levels.clone();
|
||||
power_levels.apply(settings).unwrap();
|
||||
|
||||
// Then the room name level should be updated (but not removed) without
|
||||
// affecting any other state events.
|
||||
assert_eq!(
|
||||
power_levels.events,
|
||||
BTreeMap::from_iter(vec![
|
||||
(StateEventType::RoomName.into(), power_levels.state_default),
|
||||
(StateEventType::RoomAvatar.into(), original_level),
|
||||
(StateEventType::RoomTopic.into(), original_level),
|
||||
])
|
||||
);
|
||||
// And the rest should remain unchanged.
|
||||
assert_eq!(power_levels.ban, original_levels.ban);
|
||||
assert_eq!(power_levels.invite, original_levels.invite);
|
||||
assert_eq!(power_levels.kick, original_levels.kick);
|
||||
assert_eq!(power_levels.redact, original_levels.redact);
|
||||
assert_eq!(power_levels.events_default, original_levels.events_default);
|
||||
assert_eq!(power_levels.state_default, original_levels.state_default);
|
||||
assert_eq!(power_levels.users_default, original_levels.users_default);
|
||||
}
|
||||
|
||||
fn default_power_levels() -> RoomPowerLevels {
|
||||
let mut content = RoomPowerLevelsEventContent::new();
|
||||
content.ban = int!(50);
|
||||
content.invite = int!(50);
|
||||
content.kick = int!(50);
|
||||
content.redact = int!(50);
|
||||
content.events_default = int!(0);
|
||||
content.state_default = int!(50);
|
||||
content.users_default = int!(0);
|
||||
content.notifications = NotificationPowerLevels::default();
|
||||
content.into()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user