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.
1415use std::{
16 collections::{BTreeSet, HashMap},
17 sync::Arc,
18};
1920use 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};
3132use crate::{
33 deserialized_responses::{DisplayName, MemberEvent, SyncOrStrippedState},
34 store::ambiguity_map::is_display_name_ambiguous,
35 MinimalRoomMemberEvent,
36};
3738/// A member of a room.
39#[derive(Clone, Debug)]
40pub struct RoomMember {
41pub(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.
45pub(crate) profile: Arc<Option<MinimalRoomMemberEvent>>,
46#[allow(dead_code)]
47pub(crate) presence: Arc<Option<PresenceEvent>>,
48pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
49pub(crate) max_power_level: i64,
50pub(crate) is_room_creator: bool,
51pub(crate) display_name_ambiguous: bool,
52pub(crate) is_ignored: bool,
53}
5455impl RoomMember {
56pub(crate) fn from_parts(
57 event: MemberEvent,
58 profile: Option<MinimalRoomMemberEvent>,
59 presence: Option<PresenceEvent>,
60 room_info: &MemberRoomInfo<'_>,
61 ) -> Self {
62let MemberRoomInfo {
63 power_levels,
64 max_power_level,
65 room_creator,
66 users_display_names,
67 ignored_users,
68 } = room_info;
6970let is_room_creator = room_creator.as_deref() == Some(event.user_id());
71let display_name = event.display_name();
72let display_name_ambiguous = users_display_names
73 .get(&display_name)
74 .is_some_and(|s| is_display_name_ambiguous(&display_name, s));
75let is_ignored = ignored_users.as_ref().is_some_and(|s| s.contains(event.user_id()));
7677Self {
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 }
8889/// Get the unique user id of this member.
90pub fn user_id(&self) -> &UserId {
91self.event.user_id()
92 }
9394/// Get the original member event
95pub fn event(&self) -> &Arc<MemberEvent> {
96&self.event
97 }
9899/// Get the display name of the member if there is one.
100pub fn display_name(&self) -> Option<&str> {
101if let Some(p) = self.profile.as_ref() {
102 p.as_original().and_then(|e| e.content.displayname.as_deref())
103 } else {
104self.event.original_content()?.displayname.as_deref()
105 }
106 }
107108/// 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.
112pub fn name(&self) -> &str {
113if let Some(d) = self.display_name() {
114 d
115 } else {
116self.user_id().localpart()
117 }
118 }
119120/// Get the avatar url of the member, if there is one.
121pub fn avatar_url(&self) -> Option<&MxcUri> {
122if let Some(p) = self.profile.as_ref() {
123 p.as_original().and_then(|e| e.content.avatar_url.as_deref())
124 } else {
125self.event.original_content()?.avatar_url.as_deref()
126 }
127 }
128129/// 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.
134pub fn normalized_power_level(&self) -> i64 {
135if self.max_power_level > 0 {
136 (self.power_level() * 100) / self.max_power_level
137 } else {
138self.power_level()
139 }
140 }
141142/// Get the power level of this member.
143pub 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 }
149150/// Whether this user can ban other users based on the power levels.
151 ///
152 /// Same as `member.can_do(PowerLevelAction::Ban)`.
153pub fn can_ban(&self) -> bool {
154self.can_do_impl(|pls| pls.user_can_ban(self.user_id()))
155 }
156157/// Whether this user can invite other users based on the power levels.
158 ///
159 /// Same as `member.can_do(PowerLevelAction::Invite)`.
160pub fn can_invite(&self) -> bool {
161self.can_do_impl(|pls| pls.user_can_invite(self.user_id()))
162 }
163164/// Whether this user can kick other users based on the power levels.
165 ///
166 /// Same as `member.can_do(PowerLevelAction::Kick)`.
167pub fn can_kick(&self) -> bool {
168self.can_do_impl(|pls| pls.user_can_kick(self.user_id()))
169 }
170171/// Whether this user can redact their own events based on the power levels.
172 ///
173 /// Same as `member.can_do(PowerLevelAction::RedactOwn)`.
174pub fn can_redact_own(&self) -> bool {
175self.can_do_impl(|pls| pls.user_can_redact_own_event(self.user_id()))
176 }
177178/// Whether this user can redact events of other users based on the power
179 /// levels.
180 ///
181 /// Same as `member.can_do(PowerLevelAction::RedactOther)`.
182pub fn can_redact_other(&self) -> bool {
183self.can_do_impl(|pls| pls.user_can_redact_event_of_other(self.user_id()))
184 }
185186/// Whether this user can send message events based on the power levels.
187 ///
188 /// Same as `member.can_do(PowerLevelAction::SendMessage(msg_type))`.
189pub fn can_send_message(&self, msg_type: MessageLikeEventType) -> bool {
190self.can_do_impl(|pls| pls.user_can_send_message(self.user_id(), msg_type))
191 }
192193/// Whether this user can send state events based on the power levels.
194 ///
195 /// Same as `member.can_do(PowerLevelAction::SendState(state_type))`.
196pub fn can_send_state(&self, state_type: StateEventType) -> bool {
197self.can_do_impl(|pls| pls.user_can_send_state(self.user_id(), state_type))
198 }
199200/// Whether this user can pin or unpin events based on the power levels.
201pub fn can_pin_or_unpin_event(&self) -> bool {
202self.can_send_state(StateEventType::RoomPinnedEvents)
203 }
204205/// 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))`.
210pub fn can_trigger_room_notification(&self) -> bool {
211self.can_do_impl(|pls| pls.user_can_trigger_room_notification(self.user_id()))
212 }
213214/// Whether this user can do the given action based on the power
215 /// levels.
216pub fn can_do(&self, action: PowerLevelAction) -> bool {
217self.can_do_impl(|pls| pls.user_can_do(self.user_id(), action))
218 }
219220fn can_do_impl(&self, f: impl FnOnce(RoomPowerLevels) -> bool) -> bool {
221match &*self.power_levels {
222Some(event) => f(event.power_levels()),
223None => self.is_room_creator,
224 }
225 }
226227/// 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.
231pub fn name_ambiguous(&self) -> bool {
232self.display_name_ambiguous
233 }
234235/// Get the membership state of this member.
236pub fn membership(&self) -> &MembershipState {
237self.event.membership()
238 }
239240/// Is the room member ignored by the current account user
241pub fn is_ignored(&self) -> bool {
242self.is_ignored
243 }
244}
245246// Information about the room a member is in.
247pub(crate) struct MemberRoomInfo<'a> {
248pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
249pub(crate) max_power_level: i64,
250pub(crate) room_creator: Option<OwnedUserId>,
251pub(crate) users_display_names: HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>,
252pub(crate) ignored_users: Option<BTreeSet<OwnedUserId>>,
253}