1use std::collections::HashMap;
4
5use ruma::{
6 OwnedUserId,
7 events::{
8 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}
61
62impl RoomPowerLevelChanges {
63 pub fn new() -> Self {
65 Self {
66 ban: None,
67 invite: None,
68 kick: None,
69 redact: None,
70 events_default: None,
71 state_default: None,
72 users_default: None,
73 room_name: None,
74 room_avatar: None,
75 room_topic: None,
76 space_child: None,
77 }
78 }
79}
80
81impl Default for RoomPowerLevelChanges {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87impl From<RoomPowerLevels> for RoomPowerLevelChanges {
88 fn from(value: RoomPowerLevels) -> Self {
89 Self {
90 ban: Some(value.ban.into()),
91 invite: Some(value.invite.into()),
92 kick: Some(value.kick.into()),
93 redact: Some(value.redact.into()),
94 events_default: Some(value.events_default.into()),
95 state_default: Some(value.state_default.into()),
96 users_default: Some(value.users_default.into()),
97 room_name: value
98 .events
99 .get(&StateEventType::RoomName.into())
100 .map(|v| (*v).into())
101 .or(Some(value.state_default.into())),
102 room_avatar: value
103 .events
104 .get(&StateEventType::RoomAvatar.into())
105 .map(|v| (*v).into())
106 .or(Some(value.state_default.into())),
107 room_topic: value
108 .events
109 .get(&StateEventType::RoomTopic.into())
110 .map(|v| (*v).into())
111 .or(Some(value.state_default.into())),
112 space_child: value
113 .events
114 .get(&StateEventType::SpaceChild.into())
115 .map(|v| (*v).into())
116 .or(Some(value.state_default.into())),
117 }
118 }
119}
120
121pub(crate) trait RoomPowerLevelsExt {
122 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()>;
128}
129
130impl RoomPowerLevelsExt for RoomPowerLevels {
131 fn apply(&mut self, settings: RoomPowerLevelChanges) -> Result<()> {
132 if let Some(ban) = settings.ban {
133 self.ban = ban.try_into()?;
134 }
135 if let Some(invite) = settings.invite {
136 self.invite = invite.try_into()?;
137 }
138 if let Some(kick) = settings.kick {
139 self.kick = kick.try_into()?;
140 }
141 if let Some(redact) = settings.redact {
142 self.redact = redact.try_into()?;
143 }
144 if let Some(events_default) = settings.events_default {
145 self.events_default = events_default.try_into()?;
146 }
147 if let Some(state_default) = settings.state_default {
148 self.state_default = state_default.try_into()?;
149 }
150 if let Some(users_default) = settings.users_default {
151 self.users_default = users_default.try_into()?;
152 }
153 if let Some(room_name) = settings.room_name {
154 self.events.insert(StateEventType::RoomName.into(), room_name.try_into()?);
155 }
156 if let Some(room_avatar) = settings.room_avatar {
157 self.events.insert(StateEventType::RoomAvatar.into(), room_avatar.try_into()?);
158 }
159 if let Some(room_topic) = settings.room_topic {
160 self.events.insert(StateEventType::RoomTopic.into(), room_topic.try_into()?);
161 }
162 if let Some(space_child) = settings.space_child {
163 self.events.insert(StateEventType::SpaceChild.into(), space_child.try_into()?);
164 }
165
166 Ok(())
167 }
168}
169
170impl From<js_int::TryFromIntError> for crate::error::Error {
171 fn from(e: js_int::TryFromIntError) -> Self {
172 crate::error::Error::UnknownError(Box::new(e))
173 }
174}
175
176pub fn power_level_user_changes(
179 content: &RoomPowerLevelsEventContent,
180 prev_content: &Option<PossiblyRedactedRoomPowerLevelsEventContent>,
181) -> HashMap<OwnedUserId, i64> {
182 let Some(prev_content) = prev_content.as_ref() else {
183 return Default::default();
184 };
185
186 let mut changes = HashMap::new();
187 let mut prev_users = prev_content.users.clone();
188 let new_users = content.users.clone();
189
190 for (user_id, power_level) in new_users {
193 let prev_power_level = prev_users.remove(&user_id).unwrap_or(prev_content.users_default);
194 if power_level != prev_power_level {
195 changes.insert(user_id, power_level.into());
196 }
197 }
198
199 for (user_id, power_level) in prev_users {
202 if power_level != content.users_default {
203 changes.insert(user_id, content.users_default.into());
204 }
205 }
206
207 changes
208}
209
210#[cfg(test)]
211mod tests {
212 use std::collections::BTreeMap;
213
214 use ruma::{
215 int, power_levels::NotificationPowerLevels, room_version_rules::AuthorizationRules,
216 };
217
218 use super::*;
219
220 #[test]
221 fn test_apply_actions() {
222 let mut power_levels = default_power_levels();
225
226 let new_level = int!(100);
227 let settings = RoomPowerLevelChanges {
228 ban: Some(new_level.into()),
229 invite: Some(new_level.into()),
230 kick: Some(new_level.into()),
231 redact: Some(new_level.into()),
232 events_default: None,
233 state_default: None,
234 users_default: None,
235 room_name: None,
236 room_avatar: None,
237 room_topic: None,
238 space_child: None,
239 };
240
241 let original_levels = power_levels.clone();
243 power_levels.apply(settings).unwrap();
244
245 assert_eq!(power_levels.ban, new_level);
247 assert_eq!(power_levels.invite, new_level);
248 assert_eq!(power_levels.kick, new_level);
249 assert_eq!(power_levels.redact, new_level);
250 assert_eq!(power_levels.events_default, original_levels.events_default);
252 assert_eq!(power_levels.state_default, original_levels.state_default);
253 assert_eq!(power_levels.users_default, original_levels.users_default);
254 assert_eq!(power_levels.events, original_levels.events);
255 }
256
257 #[test]
258 fn test_apply_room_settings() {
259 let mut power_levels = default_power_levels();
262
263 let new_level = int!(100);
264 let settings = RoomPowerLevelChanges {
265 ban: None,
266 invite: None,
267 kick: None,
268 redact: None,
269 events_default: None,
270 state_default: None,
271 users_default: None,
272 room_name: Some(new_level.into()),
273 room_avatar: Some(new_level.into()),
274 room_topic: Some(new_level.into()),
275 space_child: Some(new_level.into()),
276 };
277
278 let original_levels = power_levels.clone();
280 power_levels.apply(settings).unwrap();
281
282 assert_eq!(
284 power_levels.events,
285 BTreeMap::from_iter(vec![
286 (StateEventType::RoomName.into(), new_level),
287 (StateEventType::RoomAvatar.into(), new_level),
288 (StateEventType::RoomTopic.into(), new_level),
289 (StateEventType::SpaceChild.into(), new_level),
290 ])
291 );
292 assert_eq!(power_levels.ban, original_levels.ban);
294 assert_eq!(power_levels.invite, original_levels.invite);
295 assert_eq!(power_levels.kick, original_levels.kick);
296 assert_eq!(power_levels.redact, original_levels.redact);
297 assert_eq!(power_levels.events_default, original_levels.events_default);
298 assert_eq!(power_levels.state_default, original_levels.state_default);
299 assert_eq!(power_levels.users_default, original_levels.users_default);
300 }
301
302 #[test]
303 fn test_apply_state_event_to_default() {
304 let original_level = int!(100);
307 let mut power_levels = default_power_levels();
308 power_levels.events = BTreeMap::from_iter(vec![
309 (StateEventType::RoomName.into(), original_level),
310 (StateEventType::RoomAvatar.into(), original_level),
311 (StateEventType::RoomTopic.into(), original_level),
312 (StateEventType::SpaceChild.into(), original_level),
313 ]);
314
315 let settings = RoomPowerLevelChanges {
316 ban: None,
317 invite: None,
318 kick: None,
319 redact: None,
320 events_default: None,
321 state_default: None,
322 users_default: None,
323 room_name: Some(power_levels.state_default.into()),
324 room_avatar: None,
325 room_topic: None,
326 space_child: None,
327 };
328
329 let original_levels = power_levels.clone();
331 power_levels.apply(settings).unwrap();
332
333 assert_eq!(
336 power_levels.events,
337 BTreeMap::from_iter(vec![
338 (StateEventType::RoomName.into(), power_levels.state_default),
339 (StateEventType::RoomAvatar.into(), original_level),
340 (StateEventType::RoomTopic.into(), original_level),
341 (StateEventType::SpaceChild.into(), original_level),
342 ])
343 );
344 assert_eq!(power_levels.ban, original_levels.ban);
346 assert_eq!(power_levels.invite, original_levels.invite);
347 assert_eq!(power_levels.kick, original_levels.kick);
348 assert_eq!(power_levels.redact, original_levels.redact);
349 assert_eq!(power_levels.events_default, original_levels.events_default);
350 assert_eq!(power_levels.state_default, original_levels.state_default);
351 assert_eq!(power_levels.users_default, original_levels.users_default);
352 }
353
354 #[test]
355 fn test_user_power_level_changes_add_mod() {
356 let prev_content = default_power_levels_event_content();
359 let mut content = prev_content.clone();
360 content.users.insert(OwnedUserId::try_from("@charlie:example.com").unwrap(), int!(50));
361
362 let changes = power_level_user_changes(&content, &Some(prev_content));
364
365 assert_eq!(changes.len(), 1);
367 assert_eq!(changes.get(&OwnedUserId::try_from("@charlie:example.com").unwrap()), Some(&50));
368 }
369
370 #[test]
371 fn test_user_power_level_changes_remove_mod() {
372 let prev_content = default_power_levels_event_content();
375 let mut content = prev_content.clone();
376 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
377
378 let changes = power_level_user_changes(&content, &Some(prev_content));
380
381 assert_eq!(changes.len(), 1);
383 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&0));
384 }
385
386 #[test]
387 fn test_user_power_level_changes_change_mod() {
388 let prev_content = default_power_levels_event_content();
391 let mut content = prev_content.clone();
392 content.users.insert(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(100));
393
394 let changes = power_level_user_changes(&content, &Some(prev_content));
396
397 assert_eq!(changes.len(), 1);
399 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&100));
400 }
401
402 #[test]
403 fn test_user_power_level_changes_new_default() {
404 let prev_content = default_power_levels_event_content();
407 let mut content = prev_content.clone();
408 content.users_default = int!(50);
409 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
410
411 let changes = power_level_user_changes(&content, &Some(prev_content));
413
414 assert!(changes.is_empty());
416 }
417
418 #[test]
419 fn test_user_power_level_changes_no_change() {
420 let prev_content = default_power_levels_event_content();
422 let content = prev_content.clone();
423
424 let changes = power_level_user_changes(&content, &Some(prev_content));
426
427 assert!(changes.is_empty());
429 }
430
431 #[test]
432 fn test_user_power_level_changes_other_properties() {
433 let prev_content = default_power_levels_event_content();
436 let mut content = prev_content.clone();
437 content.events_default = int!(100);
438
439 let changes = power_level_user_changes(&content, &Some(prev_content));
441
442 assert!(changes.is_empty());
444 }
445
446 fn default_power_levels() -> RoomPowerLevels {
447 RoomPowerLevels::new(
448 default_power_levels_event_content().into(),
449 &AuthorizationRules::V1,
450 [],
451 )
452 }
453
454 fn default_power_levels_event_content() -> RoomPowerLevelsEventContent {
455 let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
456 content.ban = int!(50);
457 content.invite = int!(50);
458 content.kick = int!(50);
459 content.redact = int!(50);
460 content.events_default = int!(0);
461 content.state_default = int!(50);
462 content.users_default = int!(0);
463 content.users = BTreeMap::from_iter(vec![
464 (OwnedUserId::try_from("@alice:example.com").unwrap(), int!(100)),
465 (OwnedUserId::try_from("@bob:example.com").unwrap(), int!(50)),
466 ]);
467 content.notifications = NotificationPowerLevels::default();
468 content
469 }
470}