Skip to content
124 changes: 119 additions & 5 deletions crates/matrix-sdk-base/src/room/members.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,13 @@ impl Room {
.transpose()?
.map(|e| e.content.ignored_users.into_keys().collect());

let active_users = self.store.get_user_ids(self.room_id(), RoomMemberships::ACTIVE).await?;

Ok(MemberRoomInfo {
power_levels: power_levels.into(),
max_power_level,
users_display_names,
active_users,
ignored_users,
})
}
Expand Down Expand Up @@ -211,13 +214,24 @@ impl RoomMember {
presence: Option<PresenceEvent>,
room_info: &MemberRoomInfo<'_>,
) -> Self {
let MemberRoomInfo { power_levels, max_power_level, users_display_names, ignored_users } =
room_info;
let MemberRoomInfo {
power_levels,
max_power_level,
users_display_names,
ignored_users,
active_users,
} = room_info;

let display_name = event.display_name();
let display_name_ambiguous = users_display_names
.get(&display_name)
.is_some_and(|s| is_display_name_ambiguous(&display_name, s));

let display_name_ambiguous = users_display_names.get(&display_name).is_some_and(|s| {
// s.filter(|n| )
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, taking care of that now.

Copy link
Copy Markdown
Contributor Author

@multisme multisme Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I looked back into it, and the reason I didn't remove it was that I would need to make is_display_name_ambiguous accept the list of current active users, which imply a lot of changes in the matrix-sdk-base/src/store/ambiguity_map.rs, so I wanted to confirm before starting that work.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can as new commits and see where it goes. If it's too complex, we will take another path. Thanks for asking.

if !is_display_name_ambiguous(&display_name, s) {
return false;
}
//We check of many active_users with the same surname exist
active_users.iter().filter(|u| s.contains(*u)).count() > 1
});
let is_ignored = ignored_users.as_ref().is_some_and(|s| s.contains(event.user_id()));

Self {
Expand Down Expand Up @@ -390,6 +404,7 @@ pub(crate) struct MemberRoomInfo<'a> {
pub(crate) max_power_level: i64,
pub(crate) users_display_names: HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>,
pub(crate) ignored_users: Option<BTreeSet<OwnedUserId>>,
pub(crate) active_users: Vec<OwnedUserId>,
}

/// The kind of room member updates that just happened.
Expand Down Expand Up @@ -478,9 +493,23 @@ pub fn normalize_power_level(power_level: Int, max_power_level: i64) -> Int {

#[cfg(test)]
mod tests {
use std::sync::Arc;

use matrix_sdk_test::{async_test, event_factory::EventFactory};
use proptest::prelude::*;
use ruma::{room_id, user_id};

use super::*;
use crate::{RoomState, StateChanges, StateStore, store::MemoryStore};

fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
let store = Arc::new(MemoryStore::new());
let user_id = user_id!("@me:example.org");
let room_id = room_id!("!test:localhost");
let (sender, _receiver) = tokio::sync::broadcast::channel(1);

(store.clone(), Room::new(user_id, store, room_id, room_type, sender))
}

prop_compose! {
fn arb_int()(id in any::<i64>()) -> Int {
Expand Down Expand Up @@ -526,4 +555,89 @@ mod tests {
assert!(normalized >= 0);
assert!(normalized <= 100);
}

#[async_test]
async fn test_room_member_from_parts() {
let (store, room) = make_room_test_helper(RoomState::Joined);

let carol = user_id!("@carol:example.org");
let denis = user_id!("@denis:example.org");
let erica = user_id!("@erica:example.org");
let fred = user_id!("@fred:example.org");
let fredo = user_id!("@fredo:example.org");
let bob = user_id!("@bob:example.org");
let julie = user_id!("@julie:example.org");
let me = user_id!("@me:example.org");
let mewto = user_id!("@mewto:example.org");

let mut changes = StateChanges::new("".to_owned());

let f = EventFactory::new().room(room_id!("!test:localhost"));

{
let members = changes
.state
.entry(room.room_id().to_owned())
.or_default()
.entry(StateEventType::RoomMember)
.or_default();

let ambiguity_maps =
changes.ambiguity_maps.entry(room.room_id().to_owned()).or_default();

let display_name = DisplayName::new("Carol");
members.insert(carol.into(), f.member(carol).display_name("Carol").into());
ambiguity_maps.entry(display_name).or_default().insert(carol.to_owned());

let display_name = DisplayName::new("Fred");
members.insert(fred.into(), f.member(fred).display_name("Fred").into());
ambiguity_maps.entry(display_name.clone()).or_default().insert(fred.to_owned());
members.insert(
fredo.into(),
f.member(fredo).display_name("Fred").membership(MembershipState::Knock).into(),
);
ambiguity_maps.entry(display_name.clone()).or_default().insert(fredo.to_owned());
members.insert(
denis.into(),
f.member(denis).display_name("Fred").membership(MembershipState::Leave).into(),
);
ambiguity_maps.entry(display_name.clone()).or_default().insert(erica.to_owned());
members.insert(
erica.into(),
f.member(erica).display_name("Fred").membership(MembershipState::Ban).into(),
);

let display_name = DisplayName::new("Bob");
members.insert(
bob.into(),
f.member(bob).display_name("Bob").membership(MembershipState::Invite).into(),
);
ambiguity_maps.entry(display_name.clone()).or_default().insert(bob.to_owned());
members.insert(julie.into(), f.member(me).display_name("Bob").into());
ambiguity_maps.entry(display_name.clone()).or_default().insert(julie.to_owned());

let display_name = DisplayName::new("Me");
members.insert(me.into(), f.member(me).display_name("Me").into());
ambiguity_maps.entry(display_name.clone()).or_default().insert(me.to_owned());
members.insert(mewto.into(), f.member(mewto).display_name("Me").into());
ambiguity_maps.entry(display_name.clone()).or_default().insert(mewto.to_owned());

store.save_changes(&changes).await.unwrap();
}

assert!(!room.get_member(carol).await.unwrap().expect("Carol user").name_ambiguous());

assert!(!room.get_member(fred).await.unwrap().expect("Fred user").name_ambiguous());
assert!(!room.get_member(fredo).await.unwrap().expect("Fredo user").name_ambiguous());
assert!(!room.get_member(denis).await.unwrap().expect("Denis user").name_ambiguous());
assert!(!room.get_member(erica).await.unwrap().expect("Erica user").name_ambiguous());

assert!(!room.get_member(bob).await.unwrap().expect("Bob user").name_ambiguous());

assert!(!room.get_member(julie).await.unwrap().expect("Julie user").name_ambiguous());
assert!(!room.get_member(bob).await.unwrap().expect("Bob user").name_ambiguous());

assert!(room.get_member(me).await.unwrap().expect("Me user").name_ambiguous());
assert!(room.get_member(mewto).await.unwrap().expect("Mewto user").name_ambiguous());
}
}
Loading