use std::collections::HashMap;
use ruma::{
events::{
room::power_levels::{
PossiblyRedactedRoomPowerLevelsEventContent, RoomPowerLevels,
RoomPowerLevelsEventContent,
},
StateEventType,
},
OwnedUserId,
};
use crate::Result;
#[derive(Debug)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct RoomPowerLevelChanges {
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub ban: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub invite: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub kick: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub redact: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub events_default: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub state_default: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub users_default: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub room_name: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub room_avatar: Option<i64>,
#[cfg_attr(feature = "uniffi", uniffi(default = None))]
pub room_topic: Option<i64>,
}
impl 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 {
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))
}
}
pub fn power_level_user_changes(
content: &RoomPowerLevelsEventContent,
prev_content: &Option<PossiblyRedactedRoomPowerLevelsEventContent>,
) -> HashMap<OwnedUserId, i64> {
let Some(prev_content) = prev_content.as_ref() else {
return Default::default();
};
let mut changes = HashMap::new();
let mut prev_users = prev_content.users.clone();
let new_users = content.users.clone();
for (user_id, power_level) in new_users {
let prev_power_level = prev_users.remove(&user_id).unwrap_or(prev_content.users_default);
if power_level != prev_power_level {
changes.insert(user_id, power_level.into());
}
}
for (user_id, power_level) in prev_users {
if power_level != content.users_default {
changes.insert(user_id, content.users_default.into());
}
}
changes
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use ruma::{int, power_levels::NotificationPowerLevels};
use super::*;
#[test]
fn test_apply_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,
};
let original_levels = power_levels.clone();
power_levels.apply(settings).unwrap();
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);
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() {
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()),
};
let original_levels = power_levels.clone();
power_levels.apply(settings).unwrap();
assert_eq!(
power_levels.events,
BTreeMap::from_iter(vec![
(StateEventType::RoomName.into(), new_level),
(StateEventType::RoomAvatar.into(), new_level),
(StateEventType::RoomTopic.into(), new_level),
])
);
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() {
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,
};
let original_levels = power_levels.clone();
power_levels.apply(settings).unwrap();
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),
])
);
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_user_power_level_changes_add_mod() {
let prev_content = default_power_levels_event_content();
let mut content = prev_content.clone();
content.users.insert(OwnedUserId::try_from("@charlie:example.com").unwrap(), int!(50));
let changes = power_level_user_changes(&content, &Some(prev_content));
assert_eq!(changes.len(), 1);
assert_eq!(changes.get(&OwnedUserId::try_from("@charlie:example.com").unwrap()), Some(&50));
}
#[test]
fn test_user_power_level_changes_remove_mod() {
let prev_content = default_power_levels_event_content();
let mut content = prev_content.clone();
content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
let changes = power_level_user_changes(&content, &Some(prev_content));
assert_eq!(changes.len(), 1);
assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&0));
}
#[test]
fn test_user_power_level_changes_change_mod() {
let prev_content = default_power_levels_event_content();
let mut content = prev_content.clone();
content.users.insert(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(100));
let changes = power_level_user_changes(&content, &Some(prev_content));
assert_eq!(changes.len(), 1);
assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&100));
}
#[test]
fn test_user_power_level_changes_new_default() {
let prev_content = default_power_levels_event_content();
let mut content = prev_content.clone();
content.users_default = int!(50);
content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
let changes = power_level_user_changes(&content, &Some(prev_content));
assert!(changes.is_empty());
}
#[test]
fn test_user_power_level_changes_no_change() {
let prev_content = default_power_levels_event_content();
let content = prev_content.clone();
let changes = power_level_user_changes(&content, &Some(prev_content));
assert!(changes.is_empty());
}
#[test]
fn test_user_power_level_changes_other_properties() {
let prev_content = default_power_levels_event_content();
let mut content = prev_content.clone();
content.events_default = int!(100);
let changes = power_level_user_changes(&content, &Some(prev_content));
assert!(changes.is_empty());
}
fn default_power_levels() -> RoomPowerLevels {
default_power_levels_event_content().into()
}
fn default_power_levels_event_content() -> RoomPowerLevelsEventContent {
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.users = BTreeMap::from_iter(vec![
(OwnedUserId::try_from("@alice:example.com").unwrap(), int!(100)),
(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(50)),
]);
content.notifications = NotificationPowerLevels::default();
content
}
}