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::{int, power_levels::NotificationPowerLevels};
203
204 use super::*;
205
206 #[test]
207 fn test_apply_actions() {
208 let mut power_levels = default_power_levels();
211
212 let new_level = int!(100);
213 let settings = RoomPowerLevelChanges {
214 ban: Some(new_level.into()),
215 invite: Some(new_level.into()),
216 kick: Some(new_level.into()),
217 redact: Some(new_level.into()),
218 events_default: None,
219 state_default: None,
220 users_default: None,
221 room_name: None,
222 room_avatar: None,
223 room_topic: None,
224 };
225
226 let original_levels = power_levels.clone();
228 power_levels.apply(settings).unwrap();
229
230 assert_eq!(power_levels.ban, new_level);
232 assert_eq!(power_levels.invite, new_level);
233 assert_eq!(power_levels.kick, new_level);
234 assert_eq!(power_levels.redact, new_level);
235 assert_eq!(power_levels.events_default, original_levels.events_default);
237 assert_eq!(power_levels.state_default, original_levels.state_default);
238 assert_eq!(power_levels.users_default, original_levels.users_default);
239 assert_eq!(power_levels.events, original_levels.events);
240 }
241
242 #[test]
243 fn test_apply_room_settings() {
244 let mut power_levels = default_power_levels();
247
248 let new_level = int!(100);
249 let settings = RoomPowerLevelChanges {
250 ban: None,
251 invite: None,
252 kick: None,
253 redact: None,
254 events_default: None,
255 state_default: None,
256 users_default: None,
257 room_name: Some(new_level.into()),
258 room_avatar: Some(new_level.into()),
259 room_topic: Some(new_level.into()),
260 };
261
262 let original_levels = power_levels.clone();
264 power_levels.apply(settings).unwrap();
265
266 assert_eq!(
268 power_levels.events,
269 BTreeMap::from_iter(vec![
270 (StateEventType::RoomName.into(), new_level),
271 (StateEventType::RoomAvatar.into(), new_level),
272 (StateEventType::RoomTopic.into(), new_level),
273 ])
274 );
275 assert_eq!(power_levels.ban, original_levels.ban);
277 assert_eq!(power_levels.invite, original_levels.invite);
278 assert_eq!(power_levels.kick, original_levels.kick);
279 assert_eq!(power_levels.redact, original_levels.redact);
280 assert_eq!(power_levels.events_default, original_levels.events_default);
281 assert_eq!(power_levels.state_default, original_levels.state_default);
282 assert_eq!(power_levels.users_default, original_levels.users_default);
283 }
284
285 #[test]
286 fn test_apply_state_event_to_default() {
287 let original_level = int!(100);
290 let mut power_levels = default_power_levels();
291 power_levels.events = BTreeMap::from_iter(vec![
292 (StateEventType::RoomName.into(), original_level),
293 (StateEventType::RoomAvatar.into(), original_level),
294 (StateEventType::RoomTopic.into(), original_level),
295 ]);
296
297 let settings = RoomPowerLevelChanges {
298 ban: None,
299 invite: None,
300 kick: None,
301 redact: None,
302 events_default: None,
303 state_default: None,
304 users_default: None,
305 room_name: Some(power_levels.state_default.into()),
306 room_avatar: None,
307 room_topic: None,
308 };
309
310 let original_levels = power_levels.clone();
312 power_levels.apply(settings).unwrap();
313
314 assert_eq!(
317 power_levels.events,
318 BTreeMap::from_iter(vec![
319 (StateEventType::RoomName.into(), power_levels.state_default),
320 (StateEventType::RoomAvatar.into(), original_level),
321 (StateEventType::RoomTopic.into(), original_level),
322 ])
323 );
324 assert_eq!(power_levels.ban, original_levels.ban);
326 assert_eq!(power_levels.invite, original_levels.invite);
327 assert_eq!(power_levels.kick, original_levels.kick);
328 assert_eq!(power_levels.redact, original_levels.redact);
329 assert_eq!(power_levels.events_default, original_levels.events_default);
330 assert_eq!(power_levels.state_default, original_levels.state_default);
331 assert_eq!(power_levels.users_default, original_levels.users_default);
332 }
333
334 #[test]
335 fn test_user_power_level_changes_add_mod() {
336 let prev_content = default_power_levels_event_content();
339 let mut content = prev_content.clone();
340 content.users.insert(OwnedUserId::try_from("@charlie:example.com").unwrap(), int!(50));
341
342 let changes = power_level_user_changes(&content, &Some(prev_content));
344
345 assert_eq!(changes.len(), 1);
347 assert_eq!(changes.get(&OwnedUserId::try_from("@charlie:example.com").unwrap()), Some(&50));
348 }
349
350 #[test]
351 fn test_user_power_level_changes_remove_mod() {
352 let prev_content = default_power_levels_event_content();
355 let mut content = prev_content.clone();
356 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
357
358 let changes = power_level_user_changes(&content, &Some(prev_content));
360
361 assert_eq!(changes.len(), 1);
363 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&0));
364 }
365
366 #[test]
367 fn test_user_power_level_changes_change_mod() {
368 let prev_content = default_power_levels_event_content();
371 let mut content = prev_content.clone();
372 content.users.insert(OwnedUserId::try_from("@bob:example.com").unwrap(), int!(100));
373
374 let changes = power_level_user_changes(&content, &Some(prev_content));
376
377 assert_eq!(changes.len(), 1);
379 assert_eq!(changes.get(&OwnedUserId::try_from("@bob:example.com").unwrap()), Some(&100));
380 }
381
382 #[test]
383 fn test_user_power_level_changes_new_default() {
384 let prev_content = default_power_levels_event_content();
387 let mut content = prev_content.clone();
388 content.users_default = int!(50);
389 content.users.remove(&OwnedUserId::try_from("@bob:example.com").unwrap());
390
391 let changes = power_level_user_changes(&content, &Some(prev_content));
393
394 assert!(changes.is_empty());
396 }
397
398 #[test]
399 fn test_user_power_level_changes_no_change() {
400 let prev_content = default_power_levels_event_content();
402 let content = prev_content.clone();
403
404 let changes = power_level_user_changes(&content, &Some(prev_content));
406
407 assert!(changes.is_empty());
409 }
410
411 #[test]
412 fn test_user_power_level_changes_other_properties() {
413 let prev_content = default_power_levels_event_content();
416 let mut content = prev_content.clone();
417 content.events_default = int!(100);
418
419 let changes = power_level_user_changes(&content, &Some(prev_content));
421
422 assert!(changes.is_empty());
424 }
425
426 fn default_power_levels() -> RoomPowerLevels {
427 default_power_levels_event_content().into()
428 }
429
430 fn default_power_levels_event_content() -> RoomPowerLevelsEventContent {
431 let mut content = RoomPowerLevelsEventContent::new();
432 content.ban = int!(50);
433 content.invite = int!(50);
434 content.kick = int!(50);
435 content.redact = int!(50);
436 content.events_default = int!(0);
437 content.state_default = int!(50);
438 content.users_default = int!(0);
439 content.users = BTreeMap::from_iter(vec![
440 (OwnedUserId::try_from("@alice:example.com").unwrap(), int!(100)),
441 (OwnedUserId::try_from("@bob:example.com").unwrap(), int!(50)),
442 ]);
443 content.notifications = NotificationPowerLevels::default();
444 content
445 }
446}