diff --git a/examples/login.rs b/examples/login.rs index 9575a71e..e69a9087 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -25,10 +25,7 @@ impl EventEmitter for EventCallback { let member = rooms.members.get(&sender).unwrap(); println!( "{}: {}", - member - .display_name - .as_ref() - .unwrap_or(&sender.to_string()), + member.display_name.as_ref().unwrap_or(&sender.to_string()), msg_body ); } diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index f09dd0a8..0129cadf 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -69,7 +69,6 @@ use tokio::sync::Mutex; /// println!( /// "{}: {}", /// member -/// .user /// .display_name /// .as_ref() /// .unwrap_or(&sender.to_string()), diff --git a/src/models/room.rs b/src/models/room.rs index d8eceea1..53034cc9 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -26,11 +26,12 @@ use crate::events::room::{ encryption::EncryptionEvent, member::{MemberEvent, MembershipChange}, name::NameEvent, - power_levels::PowerLevelsEvent, + power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent}, }; +use crate::events::EventType; use crate::identifiers::{RoomAliasId, RoomId, UserId}; -use js_int::UInt; +use js_int::{Int, UInt}; #[derive(Debug, Default)] /// `RoomName` allows the calculation of a text room name. @@ -43,6 +44,32 @@ pub struct RoomName { aliases: Vec, } +#[derive(Debug, PartialEq, Eq)] +pub struct PowerLevels { + /// The level required to ban a user. + pub ban: Int, + /// The level required to send specific event types. + /// + /// This is a mapping from event type to power level required. + pub events: HashMap, + /// The default level required to send message events. + pub events_default: Int, + /// The level required to invite a user. + pub invite: Int, + /// The level required to kick a user. + pub kick: Int, + /// The level required to redact an event. + pub redact: Int, + /// The default level required to send state events. + pub state_default: Int, + /// The default power level for every user in the room. + pub users_default: Int, + /// The power level requirements for specific notification types. + /// + /// This is a mapping from `key` to power level for that notifications key. + pub notifications: Int, +} + #[derive(Debug)] /// A Matrix rooom. pub struct Room { @@ -58,6 +85,8 @@ pub struct Room { pub members: HashMap, /// A list of users that are currently typing. pub typing_users: Vec, + /// The power level requirements for specific actions in this room + pub power_levels: Option, // TODO when encryption events are handled we store algorithm used and rotation time. /// A flag indicating if the room is encrypted. pub encrypted: bool, @@ -131,6 +160,7 @@ impl Room { creator: None, members: HashMap::new(), typing_users: Vec::new(), + power_levels: None, encrypted: false, unread_highlight: None, unread_notifications: None, @@ -156,7 +186,7 @@ impl Room { } let member = RoomMember::new(event); - + self.members .insert(UserId::try_from(event.state_key.as_str()).unwrap(), member); @@ -180,6 +210,35 @@ impl Room { true } + fn set_room_power_level(&mut self, event: &PowerLevelsEvent) -> bool { + let PowerLevelsEventContent { + ban, + events, + events_default, + invite, + kick, + redact, + state_default, + users_default, + notifications: NotificationPowerLevels { room }, + .. + } = &event.content; + + let power = PowerLevels { + ban: *ban, + events: events.clone(), + events_default: *events_default, + invite: *invite, + kick: *kick, + redact: *redact, + state_default: *state_default, + users_default: *users_default, + notifications: *room, + }; + self.power_levels = Some(power); + true + } + /// Handle a room.member updating the room state if necessary. /// /// Returns true if the joined member list changed, false otherwise. @@ -192,10 +251,7 @@ impl Room { } else { return false; }; - if let Some(member) = self - .members - .get_mut(&user) - { + if let Some(member) = self.members.get_mut(&user) { member.update_member(event) } else { false @@ -239,22 +295,22 @@ impl Room { /// /// Returns true if the room name changed, false otherwise. pub fn handle_power_level(&mut self, event: &PowerLevelsEvent) -> bool { - // when getting a response from the actual matrix server `state_key` - // was empty, the spec says "state_key: A zero-length string." - // for `m.room.power_levels` events - let user = if let Ok(id) = UserId::try_from(event.state_key.as_str()) { - id - } else { - return false; - }; - if let Some(member) = self - .members - .get_mut(&user) - { - member.update_power(event) - } else { - false + // NOTE: this is always true, we assume that if we get an event their is an update. + let mut updated = self.set_room_power_level(event); + + let mut max_power = event.content.users_default; + for power in event.content.users.values() { + max_power = *power.max(&max_power); } + + for user in event.content.users.keys() { + if let Some(member) = self.members.get_mut(user) { + if member.update_power(event, max_power) { + updated = true; + } + } + } + updated } fn handle_encryption_event(&mut self, _event: &EncryptionEvent) -> bool { @@ -297,6 +353,7 @@ impl Room { StateEvent::RoomName(n) => self.handle_room_name(n), StateEvent::RoomCanonicalAlias(ca) => self.handle_canonical(ca), StateEvent::RoomAliases(a) => self.handle_room_aliases(a), + StateEvent::RoomPowerLevels(p) => self.handle_power_level(p), StateEvent::RoomEncryption(e) => self.handle_encryption_event(e), _ => false, } @@ -337,6 +394,7 @@ mod test { use url::Url; use std::convert::TryFrom; + use std::ops::Deref; use std::str::FromStr; use std::time::Duration; @@ -375,5 +433,7 @@ mod test { for (_id, member) in &room.members { assert_eq!(MembershipState::Join, member.membership); } + + assert!(room.deref().power_levels.is_some()) } } diff --git a/src/models/room_member.rs b/src/models/room_member.rs index 4d177226..fd3d14d9 100644 --- a/src/models/room_member.rs +++ b/src/models/room_member.rs @@ -16,11 +16,11 @@ use std::convert::TryFrom; use crate::events::collections::all::Event; +use crate::events::presence::{PresenceEvent, PresenceEventContent, PresenceState}; use crate::events::room::{ member::{MemberEvent, MembershipChange, MembershipState}, power_levels::PowerLevelsEvent, }; -use crate::events::presence::{PresenceEvent, PresenceEventContent, PresenceState}; use crate::identifiers::UserId; use js_int::{Int, UInt}; @@ -106,12 +106,7 @@ impl RoomMember { } } - pub fn update_power(&mut self, event: &PowerLevelsEvent) -> bool { - let mut max_power = event.content.users_default; - for power in event.content.users.values() { - max_power = *power.max(&max_power); - } - + pub fn update_power(&mut self, event: &PowerLevelsEvent, max_power: Int) -> bool { let changed; if let Some(user_power) = event.content.users.get(&self.user_id) { changed = self.power_level != Some(*user_power); @@ -179,18 +174,16 @@ impl RoomMember { self.presence_events.push(presence_ev.clone()); self.avatar_url = avatar_url.clone(); - self.currently_active = currently_active.clone(); + self.currently_active = *currently_active; self.display_name = displayname.clone(); - self.last_active_ago = last_active_ago.clone(); - self.presence = Some(presence.clone()); + self.last_active_ago = *last_active_ago; + self.presence = Some(*presence); self.status_msg = status_msg.clone(); } } #[cfg(test)] mod test { - use super::*; - use crate::identifiers::{EventId, RoomId, UserId}; use crate::{AsyncClient, Session, SyncSettings}; @@ -202,6 +195,7 @@ mod test { use std::collections::HashMap; use std::convert::TryFrom; + use std::ops::Deref; use std::str::FromStr; use std::time::Duration; @@ -240,11 +234,17 @@ mod test { .lock() .await; - for (_id, member) in &mut room.members { - let power = power_levels(); - assert!(member.update_power(&power)); - assert_eq!(MembershipState::Join, member.membership); - } + let power = power_levels(); + assert!(room.handle_power_level(&power)); + + assert_eq!( + room.deref().power_levels.as_ref().unwrap().ban, + Int::new(40).unwrap() + ); + assert_eq!( + room.deref().power_levels.as_ref().unwrap().notifications, + Int::new(35).unwrap() + ); } fn power_levels() -> PowerLevelsEvent {