1use std::collections::HashMap;
4
5use ruma::{
6 OwnedUserId,
7 events::{
8 MessageLikeEventType, StateEventType,
9 room::power_levels::{
10 PossiblyRedactedRoomPowerLevelsEventContent, RoomPowerLevels,
11 RoomPowerLevelsEventContent,
12 },
13 },
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 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
59 pub space_child: Option<i64>,
60 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
62 pub beacon: Option<i64>,
63 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
65 pub beacon_info: Option<i64>,
66}
67
68impl RoomPowerLevelChanges {
69 pub fn new() -> Self {
71 Self {
72 ban: None,
73 invite: None,
74 kick: None,
75 redact: None,
76 events_default: None,
77 state_default: None,
78 users_default: None,
79 room_name: None,
80 room_avatar: None,
81 room_topic: None,
82 space_child: None,
83 beacon: None,
84 beacon_info: None,
85 }
86 }
87}
88
89impl Default for RoomPowerLevelChanges {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl From<RoomPowerLevels> for RoomPowerLevelChanges {
96 fn from(value: RoomPowerLevels) -> Self {
97 Self {
98 ban: Some(value.ban.into()),
99 invite: Some(value.invite.into()),
100 kick: Some(value.kick.into()),
101 redact: Some(value.redact.into()),
102 events_default: Some(value.events_default.into()),
103 state_default: Some(value.state_default.into()),
104 users_default: Some(value.users_default.into()),
105 room_name: value
106 .events
107 .get(&StateEventType::RoomName.into())
108 .map(|v| (*v).into())
109 .or(Some(value.state_default.into())),
110 room_avatar: value
111 .events
112 .get(&StateEventType::RoomAvatar.into())
113 .map(|v| (*v).into())
114 .or(Some(value.state_default.into())),
115 room_topic: value
116 .events
117 .get(&StateEventType::RoomTopic.into())
118 .map(|v| (*v).into())
119 .or(Some(value.state_default.into())),
120 space_child: value
121 .events
122 .get(&StateEventType::SpaceChild.into())
123 .map(|v| (*v).into())
124 .or(Some(value.state_default.into())),
125 beacon: value
126 .events
127 .get(&MessageLikeEventType::Beacon.into())
128 .map(|v| (*v).into())
129 .or(Some(value.events_default.into())),
130 beacon_info: value
131 .events
132 .get(&StateEventType::BeaconInfo.into())
133 .map(|v| (*v).into())
134 .or(Some(value.state_default.into())),
135 }
136 }
137}
138
139pub(crate) trait RoomPowerLevelsExt {
140 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()>;
146}
147
148impl RoomPowerLevelsExt for RoomPowerLevels {
149 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()> {
150 if let Some(ban) = settings.ban {
151 self.ban = ban.try_into()?;
152 }
153 if let Some(invite) = settings.invite {
154 self.invite = invite.try_into()?;
155 }
156 if let Some(kick) = settings.kick {
157 self.kick = kick.try_into()?;
158 }
159 if let Some(redact) = settings.redact {
160 self.redact = redact.try_into()?;
161 }
162 if let Some(events_default) = settings.events_default {
163 self.events_default = events_default.try_into()?;
164 }
165 if let Some(state_default) = settings.state_default {
166 self.state_default = state_default.try_into()?;
167 }
168 if let Some(users_default) = settings.users_default {
169 self.users_default = users_default.try_into()?;
170 }
171 if let Some(room_name) = settings.room_name {
172 self.events.insert(StateEventType::RoomName.into(), room_name.try_into()?);
173 }
174 if let Some(room_avatar) = settings.room_avatar {
175 self.events.insert(StateEventType::RoomAvatar.into(), room_avatar.try_into()?);
176 }
177 if let Some(room_topic) = settings.room_topic {
178 self.events.insert(StateEventType::RoomTopic.into(), room_topic.try_into()?);
179 }
180 if let Some(space_child) = settings.space_child {
181 self.events.insert(StateEventType::SpaceChild.into(), space_child.try_into()?);
182 }
183 if let Some(beacon) = settings.beacon {
184 self.events.insert(MessageLikeEventType::Beacon.into(), beacon.try_into()?);
185 }
186 if let Some(beacon_info) = settings.beacon_info {
187 self.events.insert(StateEventType::BeaconInfo.into(), beacon_info.try_into()?);
188 }
189
190 Ok(())
191 }
192}
193
194impl From<js_int::TryFromIntError> for crate::error::Error {
195 fn from(e: js_int::TryFromIntError) -> Self {
196 crate::error::Error::UnknownError(Box::new(e))
197 }
198}
199
200pub fn power_level_user_changes(
203 content: &RoomPowerLevelsEventContent,
204 prev_content: &Option<PossiblyRedactedRoomPowerLevelsEventContent>,
205) -> HashMap<OwnedUserId, i64> {
206 let Some(prev_content) = prev_content.as_ref() else {
207 return Default::default();
208 };
209
210 let mut changes = HashMap::new();
211 let mut prev_users = prev_content.users.clone();
212 let new_users = content.users.clone();
213
214 for (user_id, power_level) in new_users {
217 let prev_power_level = prev_users.remove(&user_id).unwrap_or(prev_content.users_default);
218 if power_level != prev_power_level {
219 changes.insert(user_id, power_level.into());
220 }
221 }
222
223 for (user_id, power_level) in prev_users {
226 if power_level != content.users_default {
227 changes.insert(user_id, content.users_default.into());
228 }
229 }
230
231 changes
232}
233
234#[cfg(test)]
235mod tests {
236 use std::collections::BTreeMap;
237
238 use ruma::{
239 int, power_levels::NotificationPowerLevels, room_version_rules::AuthorizationRules,
240 };
241
242 use super::*;
243
244 #[test]
245 fn test_apply_actions() {
246 let mut power_levels = default_power_levels();
249
250 let new_level = int!(100);
251 let settings = RoomPowerLevelChanges {
252 ban: Some(new_level.into()),
253 invite: Some(new_level.into()),
254 kick: Some(new_level.into()),
255 redact: Some(new_level.into()),
256 events_default: None,
257 state_default: None,
258 users_default: None,
259 room_name: None,
260 room_avatar: None,
261 room_topic: None,
262 space_child: None,
263 beacon: None,
264 beacon_info: None,
265 };
266
267 let original_levels = power_levels.clone();
269 power_levels.apply(settings).unwrap();
270
271 assert_eq!(power_levels.ban, new_level);
273 assert_eq!(power_levels.invite, new_level);
274 assert_eq!(power_levels.kick, new_level);
275 assert_eq!(power_levels.redact, new_level);
276 assert_eq!(power_levels.events_default, original_levels.events_default);
278 assert_eq!(power_levels.state_default, original_levels.state_default);
279 assert_eq!(power_levels.users_default, original_levels.users_default);
280 assert_eq!(power_levels.events, original_levels.events);
281 }
282
283 #[test]
284 fn test_apply_room_settings() {
285 let mut power_levels = default_power_levels();
288
289 let new_level = int!(100);
290 let settings = RoomPowerLevelChanges {
291 ban: None,
292 invite: None,
293 kick: None,
294 redact: None,
295 events_default: None,
296 state_default: None,
297 users_default: None,
298 room_name: Some(new_level.into()),
299 room_avatar: Some(new_level.into()),
300 room_topic: Some(new_level.into()),
301 space_child: Some(new_level.into()),
302 beacon: None,
303 beacon_info: None,
304 };
305
306 let original_levels = power_levels.clone();
308 power_levels.apply(settings).unwrap();
309
310 assert_eq!(
312 power_levels.events,
313 BTreeMap::from_iter(vec![
314 (StateEventType::RoomName.into(), new_level),
315 (StateEventType::RoomAvatar.into(), new_level),
316 (StateEventType::RoomTopic.into(), new_level),
317 (StateEventType::SpaceChild.into(), new_level),
318 ])
319 );
320 assert_eq!(power_levels.ban, original_levels.ban);
322 assert_eq!(power_levels.invite, original_levels.invite);
323 assert_eq!(power_levels.kick, original_levels.kick);
324 assert_eq!(power_levels.redact, original_levels.redact);
325 assert_eq!(power_levels.events_default, original_levels.events_default);
326 assert_eq!(power_levels.state_default, original_levels.state_default);
327 assert_eq!(power_levels.users_default, original_levels.users_default);
328 }
329
330 #[test]
331 fn test_apply_state_event_to_default() {
332 let original_level = int!(100);
335 let mut power_levels = default_power_levels();
336 power_levels.events = BTreeMap::from_iter(vec![
337 (StateEventType::RoomName.into(), original_level),
338 (StateEventType::RoomAvatar.into(), original_level),
339 (StateEventType::RoomTopic.into(), original_level),
340 (StateEventType::SpaceChild.into(), original_level),
341 ]);
342
343 let settings = RoomPowerLevelChanges {
344 ban: None,
345 invite: None,
346 kick: None,
347 redact: None,
348 events_default: None,
349 state_default: None,
350 users_default: None,
351 room_name: Some(power_levels.state_default.into()),
352 room_avatar: None,
353 room_topic: None,
354 space_child: None,
355 beacon: None,
356 beacon_info: None,
357 };
358
359 let original_levels = power_levels.clone();
361 power_levels.apply(settings).unwrap();
362
363 assert_eq!(
366 power_levels.events,
367 BTreeMap::from_iter(vec![
368 (StateEventType::RoomName.into(), power_levels.state_default),
369 (StateEventType::RoomAvatar.into(), original_level),
370 (StateEventType::RoomTopic.into(), original_level),
371 (StateEventType::SpaceChild.into(), original_level),
372 ])
373 );
374 assert_eq!(power_levels.ban, original_levels.ban);
376 assert_eq!(power_levels.invite, original_levels.invite);
377 assert_eq!(power_levels.kick, original_levels.kick);
378 assert_eq!(power_levels.redact, original_levels.redact);
379 assert_eq!(power_levels.events_default, original_levels.events_default);
380 assert_eq!(power_levels.state_default, original_levels.state_default);
381 assert_eq!(power_levels.users_default, original_levels.users_default);
382 }
383
384 #[test]
385 fn test_apply_beacon_settings() {
386 let mut power_levels = default_power_levels();
389
390 let new_level = int!(25);
391 let settings = RoomPowerLevelChanges {
392 ban: None,
393 invite: None,
394 kick: None,
395 redact: None,
396 events_default: None,
397 state_default: None,
398 users_default: None,
399 room_name: None,
400 room_avatar: None,
401 room_topic: None,
402 space_child: None,
403 beacon: Some(new_level.into()),
404 beacon_info: Some(new_level.into()),
405 };
406
407 let original_levels = power_levels.clone();
409 power_levels.apply(settings).unwrap();
410
411 assert_eq!(
413 power_levels.events.get(&MessageLikeEventType::Beacon.into()).copied(),
414 Some(new_level)
415 );
416 assert_eq!(
417 power_levels.events.get(&StateEventType::BeaconInfo.into()).copied(),
418 Some(new_level)
419 );
420 assert_eq!(power_levels.ban, original_levels.ban);
422 assert_eq!(power_levels.invite, original_levels.invite);
423 assert_eq!(power_levels.kick, original_levels.kick);
424 assert_eq!(power_levels.redact, original_levels.redact);
425 assert_eq!(power_levels.events_default, original_levels.events_default);
426 assert_eq!(power_levels.state_default, original_levels.state_default);
427 assert_eq!(power_levels.users_default, original_levels.users_default);
428 }
429
430 #[test]
431 fn test_user_power_level_changes_add_mod() {
432 let prev_content = default_power_levels_event_content();
435 let mut content = prev_content.clone();
436 content.users.insert(OwnedUserId::try_from("@charlie:example.com").unwrap(), int!(50));
437
438 let changes = power_level_user_changes(&content, &Some(prev_content));
440
441 assert_eq!(changes.len(), 1);
443 assert_eq!(changes.get(&OwnedUserId::try_from("@charlie:example.com").unwrap()), Some(&50));
444 }
445
446 #[test]
447 fn test_user_power_level_changes_remove_mod() {
448 let prev_content = default_power_levels_event_content();
451 let mut content = prev_content.clone();
452 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
453
454 let changes = power_level_user_changes(&content, &Some(prev_content));
456
457 assert_eq!(changes.len(), 1);
459 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&0));
460 }
461
462 #[test]
463 fn test_user_power_level_changes_change_mod() {
464 let prev_content = default_power_levels_event_content();
467 let mut content = prev_content.clone();
468 content.users.insert(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(100));
469
470 let changes = power_level_user_changes(&content, &Some(prev_content));
472
473 assert_eq!(changes.len(), 1);
475 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&100));
476 }
477
478 #[test]
479 fn test_user_power_level_changes_new_default() {
480 let prev_content = default_power_levels_event_content();
483 let mut content = prev_content.clone();
484 content.users_default = int!(50);
485 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
486
487 let changes = power_level_user_changes(&content, &Some(prev_content));
489
490 assert!(changes.is_empty());
492 }
493
494 #[test]
495 fn test_user_power_level_changes_no_change() {
496 let prev_content = default_power_levels_event_content();
498 let content = prev_content.clone();
499
500 let changes = power_level_user_changes(&content, &Some(prev_content));
502
503 assert!(changes.is_empty());
505 }
506
507 #[test]
508 fn test_user_power_level_changes_other_properties() {
509 let prev_content = default_power_levels_event_content();
512 let mut content = prev_content.clone();
513 content.events_default = int!(100);
514
515 let changes = power_level_user_changes(&content, &Some(prev_content));
517
518 assert!(changes.is_empty());
520 }
521
522 fn default_power_levels() -> RoomPowerLevels {
523 RoomPowerLevels::new(
524 default_power_levels_event_content().into(),
525 &AuthorizationRules::V1,
526 [],
527 )
528 }
529
530 fn default_power_levels_event_content() -> RoomPowerLevelsEventContent {
531 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
532 content.ban = int!(50);
533 content.invite = int!(50);
534 content.kick = int!(50);
535 content.redact = int!(50);
536 content.events_default = int!(0);
537 content.state_default = int!(50);
538 content.users_default = int!(0);
539 content.users = BTreeMap::from_iter(vec![
540 (OwnedUserId::try_from("@alice:example.com").unwrap(), int!(100)),
541 (OwnedUserId::try_from("@bob:example.com").unwrap(), int!(50)),
542 ]);
543 content.notifications = NotificationPowerLevels::default();
544 content
545 }
546}