matrix_sdk_base/response_processors/profiles.rs
1// Copyright 2025 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 ruma::{
16 RoomId,
17 events::{
18 SyncStateEvent,
19 room::member::{MembershipState, RoomMemberEventContent},
20 },
21};
22
23use super::Context;
24
25/// Decide whether the profile must be created, updated or deleted based on the
26/// [`RoomMemberEventContent`].
27pub fn upsert_or_delete(
28 context: &mut Context,
29 room_id: &RoomId,
30 event: &SyncStateEvent<RoomMemberEventContent>,
31) {
32 // Senders can fake the profile easily so we keep track of profiles that the
33 // member set themselves to avoid having confusing profile changes when a
34 // member gets kicked/banned.
35 // We don't want to update the profile if the member is leaving the room, as the
36 // server may return a dummy empty profile along the leave event. We want to
37 // keep the last known profile in that case.
38 if event.state_key() == event.sender() && *event.membership() != MembershipState::Leave {
39 context
40 .state_changes
41 .profiles
42 .entry(room_id.to_owned())
43 .or_default()
44 .insert(event.sender().to_owned(), event.into());
45 }
46
47 if matches!(*event.membership(), MembershipState::Invite | MembershipState::Ban) {
48 // Remove any profile previously stored for the invited/banned user.
49 //
50 // A room member could have joined the room and left it later; in that case, the
51 // server may return a dummy, empty profile along the `leave` event. We
52 // don't want to reuse that empty profile when the member has been
53 // re-invited, so we remove it from the database.
54 context
55 .state_changes
56 .profiles_to_delete
57 .entry(room_id.to_owned())
58 .or_default()
59 .push(event.state_key().clone());
60
61 // Address the edge case of "in-flight" profiles. If an earlier event in
62 // this same sync batch inserted a profile (for example user joined then got
63 // banned by an admin of a room), we MUST NOT reinsert it.
64 if let Some(room_profiles) = context.state_changes.profiles.get_mut(room_id) {
65 room_profiles.remove(event.state_key());
66 }
67 }
68}