matrix_sdk_base/rooms/
members.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// A member of a room.
39#[derive(Clone, Debug)]
40pub struct RoomMember {
41    pub(crate) event: Arc<MemberEvent>,
42    // The latest member event sent by the member themselves.
43    // Stored in addition to the latest member event overall to get displayname
44    // and avatar from, which should be ignored on events sent by others.
45    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    /// Get the unique user id of this member.
90    pub fn user_id(&self) -> &UserId {
91        self.event.user_id()
92    }
93
94    /// Get the original member event
95    pub fn event(&self) -> &Arc<MemberEvent> {
96        &self.event
97    }
98
99    /// Get the display name of the member if there is one.
100    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    /// Get the name of the member.
109    ///
110    /// This returns either the display name or the local part of the user id if
111    /// the member didn't set a display name.
112    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    /// Get the avatar url of the member, if there is one.
121    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    /// Get the normalized power level of this member.
130    ///
131    /// The normalized power level depends on the maximum power level that can
132    /// be found in a certain room, positive values are always in the range of
133    /// 0-100.
134    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    /// Get the power level of this member.
143    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    /// Whether this user can ban other users based on the power levels.
151    ///
152    /// Same as `member.can_do(PowerLevelAction::Ban)`.
153    pub fn can_ban(&self) -> bool {
154        self.can_do_impl(|pls| pls.user_can_ban(self.user_id()))
155    }
156
157    /// Whether this user can invite other users based on the power levels.
158    ///
159    /// Same as `member.can_do(PowerLevelAction::Invite)`.
160    pub fn can_invite(&self) -> bool {
161        self.can_do_impl(|pls| pls.user_can_invite(self.user_id()))
162    }
163
164    /// Whether this user can kick other users based on the power levels.
165    ///
166    /// Same as `member.can_do(PowerLevelAction::Kick)`.
167    pub fn can_kick(&self) -> bool {
168        self.can_do_impl(|pls| pls.user_can_kick(self.user_id()))
169    }
170
171    /// Whether this user can redact their own events based on the power levels.
172    ///
173    /// Same as `member.can_do(PowerLevelAction::RedactOwn)`.
174    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    /// Whether this user can redact events of other users based on the power
179    /// levels.
180    ///
181    /// Same as `member.can_do(PowerLevelAction::RedactOther)`.
182    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    /// Whether this user can send message events based on the power levels.
187    ///
188    /// Same as `member.can_do(PowerLevelAction::SendMessage(msg_type))`.
189    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    /// Whether this user can send state events based on the power levels.
194    ///
195    /// Same as `member.can_do(PowerLevelAction::SendState(state_type))`.
196    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    /// Whether this user can pin or unpin events based on the power levels.
201    pub fn can_pin_or_unpin_event(&self) -> bool {
202        self.can_send_state(StateEventType::RoomPinnedEvents)
203    }
204
205    /// Whether this user can notify everybody in the room by writing `@room` in
206    /// a message.
207    ///
208    /// Same as `member.
209    /// can_do(PowerLevelAction::TriggerNotification(NotificationPowerLevelType::Room))`.
210    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    /// Whether this user can do the given action based on the power
215    /// levels.
216    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    /// Is the name that the member uses ambiguous in the room.
228    ///
229    /// A name is considered to be ambiguous if at least one other member shares
230    /// the same name.
231    pub fn name_ambiguous(&self) -> bool {
232        self.display_name_ambiguous
233    }
234
235    /// Get the membership state of this member.
236    pub fn membership(&self) -> &MembershipState {
237        self.event.membership()
238    }
239
240    /// Is the room member ignored by the current account user
241    pub fn is_ignored(&self) -> bool {
242        self.is_ignored
243    }
244}
245
246// Information about the room a member is in.
247pub(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}