1use std::collections::HashMap;
4
5use ruma::{
6 events::{
7 room::power_levels::{
8 PossiblyRedactedRoomPowerLevelsEventContent, RoomPowerLevels,
9 RoomPowerLevelsEventContent,
10 },
11 StateEventType,
12 },
13 OwnedUserId,
14};
15
16use crate::Result;
17
18#[derive(Debug)]
22#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
23pub struct RoomPowerLevelChanges {
24 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
27 pub ban: Option<i64>,
28 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
30 pub invite: Option<i64>,
31 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
33 pub kick: Option<i64>,
34 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
36 pub redact: Option<i64>,
37
38 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
41 pub events_default: Option<i64>,
42 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
44 pub state_default: Option<i64>,
45 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
47 pub users_default: Option<i64>,
48 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
50 pub room_name: Option<i64>,
51 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
53 pub room_avatar: Option<i64>,
54 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
56 pub room_topic: Option<i64>,
57}
58
59impl RoomPowerLevelChanges {
60 pub fn new() -> Self {
62 Self {
63 ban: None,
64 invite: None,
65 kick: None,
66 redact: None,
67 events_default: None,
68 state_default: None,
69 users_default: None,
70 room_name: None,
71 room_avatar: None,
72 room_topic: None,
73 }
74 }
75}
76
77impl Default for RoomPowerLevelChanges {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl From<RoomPowerLevels> for RoomPowerLevelChanges {
84 fn from(value: RoomPowerLevels) -> Self {
85 Self {
86 ban: Some(value.ban.into()),
87 invite: Some(value.invite.into()),
88 kick: Some(value.kick.into()),
89 redact: Some(value.redact.into()),
90 events_default: Some(value.events_default.into()),
91 state_default: Some(value.state_default.into()),
92 users_default: Some(value.users_default.into()),
93 room_name: value
94 .events
95 .get(&StateEventType::RoomName.into())
96 .map(|v| (*v).into())
97 .or(Some(value.state_default.into())),
98 room_avatar: value
99 .events
100 .get(&StateEventType::RoomAvatar.into())
101 .map(|v| (*v).into())
102 .or(Some(value.state_default.into())),
103 room_topic: value
104 .events
105 .get(&StateEventType::RoomTopic.into())
106 .map(|v| (*v).into())
107 .or(Some(value.state_default.into())),
108 }
109 }
110}
111
112pub(crate) trait RoomPowerLevelsExt {
113 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()>;
119}
120
121impl RoomPowerLevelsExt for RoomPowerLevels {
122 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()> {
123 if let Some(ban) = settings.ban {
124 self.ban = ban.try_into()?;
125 }
126 if let Some(invite) = settings.invite {
127 self.invite = invite.try_into()?;
128 }
129 if let Some(kick) = settings.kick {
130 self.kick = kick.try_into()?;
131 }
132 if let Some(redact) = settings.redact {
133 self.redact = redact.try_into()?;
134 }
135 if let Some(events_default) = settings.events_default {
136 self.events_default = events_default.try_into()?;
137 }
138 if let Some(state_default) = settings.state_default {
139 self.state_default = state_default.try_into()?;
140 }
141 if let Some(users_default) = settings.users_default {
142 self.users_default = users_default.try_into()?;
143 }
144 if let Some(room_name) = settings.room_name {
145 self.events.insert(StateEventType::RoomName.into(), room_name.try_into()?);
146 }
147 if let Some(room_avatar) = settings.room_avatar {
148 self.events.insert(StateEventType::RoomAvatar.into(), room_avatar.try_into()?);
149 }
150 if let Some(room_topic) = settings.room_topic {
151 self.events.insert(StateEventType::RoomTopic.into(), room_topic.try_into()?);
152 }
153
154 Ok(())
155 }
156}
157
158impl From<js_int::TryFromIntError> for crate::error::Error {
159 fn from(e: js_int::TryFromIntError) -> Self {
160 crate::error::Error::UnknownError(Box::new(e))
161 }
162}
163
164pub fn power_level_user_changes(
167 content: &RoomPowerLevelsEventContent,
168 prev_content: &Option<PossiblyRedactedRoomPowerLevelsEventContent>,
169) -> HashMap<OwnedUserId, i64> {
170 let Some(prev_content) = prev_content.as_ref() else {
171 return Default::default();
172 };
173
174 let mut changes = HashMap::new();
175 let mut prev_users = prev_content.users.clone();
176 let new_users = content.users.clone();
177
178 for (user_id, power_level) in new_users {
181 let prev_power_level = prev_users.remove(&user_id).unwrap_or(prev_content.users_default);
182 if power_level != prev_power_level {
183 changes.insert(user_id, power_level.into());
184 }
185 }
186
187 for (user_id, power_level) in prev_users {
190 if power_level != content.users_default {
191 changes.insert(user_id, content.users_default.into());
192 }
193 }
194
195 changes
196}
197
198#[cfg(test)]
199mod tests {
200 use std::collections::BTreeMap;
201
202 use ruma::{
203 int, power_levels::NotificationPowerLevels, room_version_rules::AuthorizationRules,
204 };
205
206 use super::*;
207
208 #[test]
209 fn test_apply_actions() {
210 let mut power_levels = default_power_levels();
213
214 let new_level = int!(100);
215 let settings = RoomPowerLevelChanges {
216 ban: Some(new_level.into()),
217 invite: Some(new_level.into()),
218 kick: Some(new_level.into()),
219 redact: Some(new_level.into()),
220 events_default: None,
221 state_default: None,
222 users_default: None,
223 room_name: None,
224 room_avatar: None,
225 room_topic: None,
226 };
227
228 let original_levels = power_levels.clone();
230 power_levels.apply(settings).unwrap();
231
232 assert_eq!(power_levels.ban, new_level);
234 assert_eq!(power_levels.invite, new_level);
235 assert_eq!(power_levels.kick, new_level);
236 assert_eq!(power_levels.redact, new_level);
237 assert_eq!(power_levels.events_default, original_levels.events_default);
239 assert_eq!(power_levels.state_default, original_levels.state_default);
240 assert_eq!(power_levels.users_default, original_levels.users_default);
241 assert_eq!(power_levels.events, original_levels.events);
242 }
243
244 #[test]
245 fn test_apply_room_settings() {
246 let mut power_levels = default_power_levels();
249
250 let new_level = int!(100);
251 let settings = RoomPowerLevelChanges {
252 ban: None,
253 invite: None,
254 kick: None,
255 redact: None,
256 events_default: None,
257 state_default: None,
258 users_default: None,
259 room_name: Some(new_level.into()),
260 room_avatar: Some(new_level.into()),
261 room_topic: Some(new_level.into()),
262 };
263
264 let original_levels = power_levels.clone();
266 power_levels.apply(settings).unwrap();
267
268 assert_eq!(
270 power_levels.events,
271 BTreeMap::from_iter(vec![
272 (StateEventType::RoomName.into(), new_level),
273 (StateEventType::RoomAvatar.into(), new_level),
274 (StateEventType::RoomTopic.into(), new_level),
275 ])
276 );
277 assert_eq!(power_levels.ban, original_levels.ban);
279 assert_eq!(power_levels.invite, original_levels.invite);
280 assert_eq!(power_levels.kick, original_levels.kick);
281 assert_eq!(power_levels.redact, original_levels.redact);
282 assert_eq!(power_levels.events_default, original_levels.events_default);
283 assert_eq!(power_levels.state_default, original_levels.state_default);
284 assert_eq!(power_levels.users_default, original_levels.users_default);
285 }
286
287 #[test]
288 fn test_apply_state_event_to_default() {
289 let original_level = int!(100);
292 let mut power_levels = default_power_levels();
293 power_levels.events = BTreeMap::from_iter(vec![
294 (StateEventType::RoomName.into(), original_level),
295 (StateEventType::RoomAvatar.into(), original_level),
296 (StateEventType::RoomTopic.into(), original_level),
297 ]);
298
299 let settings = RoomPowerLevelChanges {
300 ban: None,
301 invite: None,
302 kick: None,
303 redact: None,
304 events_default: None,
305 state_default: None,
306 users_default: None,
307 room_name: Some(power_levels.state_default.into()),
308 room_avatar: None,
309 room_topic: None,
310 };
311
312 let original_levels = power_levels.clone();
314 power_levels.apply(settings).unwrap();
315
316 assert_eq!(
319 power_levels.events,
320 BTreeMap::from_iter(vec![
321 (StateEventType::RoomName.into(), power_levels.state_default),
322 (StateEventType::RoomAvatar.into(), original_level),
323 (StateEventType::RoomTopic.into(), original_level),
324 ])
325 );
326 assert_eq!(power_levels.ban, original_levels.ban);
328 assert_eq!(power_levels.invite, original_levels.invite);
329 assert_eq!(power_levels.kick, original_levels.kick);
330 assert_eq!(power_levels.redact, original_levels.redact);
331 assert_eq!(power_levels.events_default, original_levels.events_default);
332 assert_eq!(power_levels.state_default, original_levels.state_default);
333 assert_eq!(power_levels.users_default, original_levels.users_default);
334 }
335
336 #[test]
337 fn test_user_power_level_changes_add_mod() {
338 let prev_content = default_power_levels_event_content();
341 let mut content = prev_content.clone();
342 content.users.insert(OwnedUserId::try_from("@charlie:example.com").unwrap(), int!(50));
343
344 let changes = power_level_user_changes(&content, &Some(prev_content));
346
347 assert_eq!(changes.len(), 1);
349 assert_eq!(changes.get(&OwnedUserId::try_from("@charlie:example.com").unwrap()), Some(&50));
350 }
351
352 #[test]
353 fn test_user_power_level_changes_remove_mod() {
354 let prev_content = default_power_levels_event_content();
357 let mut content = prev_content.clone();
358 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
359
360 let changes = power_level_user_changes(&content, &Some(prev_content));
362
363 assert_eq!(changes.len(), 1);
365 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&0));
366 }
367
368 #[test]
369 fn test_user_power_level_changes_change_mod() {
370 let prev_content = default_power_levels_event_content();
373 let mut content = prev_content.clone();
374 content.users.insert(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(100));
375
376 let changes = power_level_user_changes(&content, &Some(prev_content));
378
379 assert_eq!(changes.len(), 1);
381 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&100));
382 }
383
384 #[test]
385 fn test_user_power_level_changes_new_default() {
386 let prev_content = default_power_levels_event_content();
389 let mut content = prev_content.clone();
390 content.users_default = int!(50);
391 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
392
393 let changes = power_level_user_changes(&content, &Some(prev_content));
395
396 assert!(changes.is_empty());
398 }
399
400 #[test]
401 fn test_user_power_level_changes_no_change() {
402 let prev_content = default_power_levels_event_content();
404 let content = prev_content.clone();
405
406 let changes = power_level_user_changes(&content, &Some(prev_content));
408
409 assert!(changes.is_empty());
411 }
412
413 #[test]
414 fn test_user_power_level_changes_other_properties() {
415 let prev_content = default_power_levels_event_content();
418 let mut content = prev_content.clone();
419 content.events_default = int!(100);
420
421 let changes = power_level_user_changes(&content, &Some(prev_content));
423
424 assert!(changes.is_empty());
426 }
427
428 fn default_power_levels() -> RoomPowerLevels {
429 RoomPowerLevels::new(
430 default_power_levels_event_content().into(),
431 &AuthorizationRules::V1,
432 [],
433 )
434 }
435
436 fn default_power_levels_event_content() -> RoomPowerLevelsEventContent {
437 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
438 content.ban = int!(50);
439 content.invite = int!(50);
440 content.kick = int!(50);
441 content.redact = int!(50);
442 content.events_default = int!(0);
443 content.state_default = int!(50);
444 content.users_default = int!(0);
445 content.users = BTreeMap::from_iter(vec![
446 (OwnedUserId::try_from("@alice:example.com").unwrap(), int!(100)),
447 (OwnedUserId::try_from("@bob:example.com").unwrap(), int!(50)),
448 ]);
449 content.notifications = NotificationPowerLevels::default();
450 content
451 }
452}