matrix_sdk_base/rooms/
members.rs1use std::{
16 collections::{BTreeSet, HashMap},
17 sync::Arc,
18};
19
20use ruma::{
21 events::{
22 presence::PresenceEvent,
23 room::{
24 member::MembershipState,
25 power_levels::{PowerLevelAction, RoomPowerLevels, RoomPowerLevelsEventContent},
26 },
27 MessageLikeEventType, StateEventType,
28 },
29 MxcUri, OwnedUserId, UserId,
30};
31
32use crate::{
33 deserialized_responses::{DisplayName, MemberEvent, SyncOrStrippedState},
34 store::ambiguity_map::is_display_name_ambiguous,
35 MinimalRoomMemberEvent,
36};
37
38#[derive(Clone, Debug)]
40pub struct RoomMember {
41 pub(crate) event: Arc<MemberEvent>,
42 pub(crate) profile: Arc<Option<MinimalRoomMemberEvent>>,
46 #[allow(dead_code)]
47 pub(crate) presence: Arc<Option<PresenceEvent>>,
48 pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
49 pub(crate) max_power_level: i64,
50 pub(crate) is_room_creator: bool,
51 pub(crate) display_name_ambiguous: bool,
52 pub(crate) is_ignored: bool,
53}
54
55impl RoomMember {
56 pub(crate) fn from_parts(
57 event: MemberEvent,
58 profile: Option<MinimalRoomMemberEvent>,
59 presence: Option<PresenceEvent>,
60 room_info: &MemberRoomInfo<'_>,
61 ) -> Self {
62 let MemberRoomInfo {
63 power_levels,
64 max_power_level,
65 room_creator,
66 users_display_names,
67 ignored_users,
68 } = room_info;
69
70 let is_room_creator = room_creator.as_deref() == Some(event.user_id());
71 let display_name = event.display_name();
72 let display_name_ambiguous = users_display_names
73 .get(&display_name)
74 .is_some_and(|s| is_display_name_ambiguous(&display_name, s));
75 let is_ignored = ignored_users.as_ref().is_some_and(|s| s.contains(event.user_id()));
76
77 Self {
78 event: event.into(),
79 profile: profile.into(),
80 presence: presence.into(),
81 power_levels: power_levels.clone(),
82 max_power_level: *max_power_level,
83 is_room_creator,
84 display_name_ambiguous,
85 is_ignored,
86 }
87 }
88
89 pub fn user_id(&self) -> &UserId {
91 self.event.user_id()
92 }
93
94 pub fn event(&self) -> &Arc<MemberEvent> {
96 &self.event
97 }
98
99 pub fn display_name(&self) -> Option<&str> {
101 if let Some(p) = self.profile.as_ref() {
102 p.as_original().and_then(|e| e.content.displayname.as_deref())
103 } else {
104 self.event.original_content()?.displayname.as_deref()
105 }
106 }
107
108 pub fn name(&self) -> &str {
113 if let Some(d) = self.display_name() {
114 d
115 } else {
116 self.user_id().localpart()
117 }
118 }
119
120 pub fn avatar_url(&self) -> Option<&MxcUri> {
122 if let Some(p) = self.profile.as_ref() {
123 p.as_original().and_then(|e| e.content.avatar_url.as_deref())
124 } else {
125 self.event.original_content()?.avatar_url.as_deref()
126 }
127 }
128
129 pub fn normalized_power_level(&self) -> i64 {
135 if self.max_power_level > 0 {
136 (self.power_level() * 100) / self.max_power_level
137 } else {
138 self.power_level()
139 }
140 }
141
142 pub fn power_level(&self) -> i64 {
144 (*self.power_levels)
145 .as_ref()
146 .map(|e| e.power_levels().for_user(self.user_id()).into())
147 .unwrap_or_else(|| if self.is_room_creator { 100 } else { 0 })
148 }
149
150 pub fn can_ban(&self) -> bool {
154 self.can_do_impl(|pls| pls.user_can_ban(self.user_id()))
155 }
156
157 pub fn can_invite(&self) -> bool {
161 self.can_do_impl(|pls| pls.user_can_invite(self.user_id()))
162 }
163
164 pub fn can_kick(&self) -> bool {
168 self.can_do_impl(|pls| pls.user_can_kick(self.user_id()))
169 }
170
171 pub fn can_redact_own(&self) -> bool {
175 self.can_do_impl(|pls| pls.user_can_redact_own_event(self.user_id()))
176 }
177
178 pub fn can_redact_other(&self) -> bool {
183 self.can_do_impl(|pls| pls.user_can_redact_event_of_other(self.user_id()))
184 }
185
186 pub fn can_send_message(&self, msg_type: MessageLikeEventType) -> bool {
190 self.can_do_impl(|pls| pls.user_can_send_message(self.user_id(), msg_type))
191 }
192
193 pub fn can_send_state(&self, state_type: StateEventType) -> bool {
197 self.can_do_impl(|pls| pls.user_can_send_state(self.user_id(), state_type))
198 }
199
200 pub fn can_pin_or_unpin_event(&self) -> bool {
202 self.can_send_state(StateEventType::RoomPinnedEvents)
203 }
204
205 pub fn can_trigger_room_notification(&self) -> bool {
211 self.can_do_impl(|pls| pls.user_can_trigger_room_notification(self.user_id()))
212 }
213
214 pub fn can_do(&self, action: PowerLevelAction) -> bool {
217 self.can_do_impl(|pls| pls.user_can_do(self.user_id(), action))
218 }
219
220 fn can_do_impl(&self, f: impl FnOnce(RoomPowerLevels) -> bool) -> bool {
221 match &*self.power_levels {
222 Some(event) => f(event.power_levels()),
223 None => self.is_room_creator,
224 }
225 }
226
227 pub fn name_ambiguous(&self) -> bool {
232 self.display_name_ambiguous
233 }
234
235 pub fn membership(&self) -> &MembershipState {
237 self.event.membership()
238 }
239
240 pub fn is_ignored(&self) -> bool {
242 self.is_ignored
243 }
244}
245
246pub(crate) struct MemberRoomInfo<'a> {
248 pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
249 pub(crate) max_power_level: i64,
250 pub(crate) room_creator: Option<OwnedUserId>,
251 pub(crate) users_display_names: HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>,
252 pub(crate) ignored_users: Option<BTreeSet<OwnedUserId>>,
253}