Skip to main content

tuwunel_api/client/sync/v5/rooms/
heroes.rs

1use std::cmp::Ordering;
2
3use futures::{StreamExt, future::join};
4use ruma::{
5	MxcUri, OwnedMxcUri, RoomId, UserId,
6	api::client::sync::sync_events::v5::{DisplayName, response, response::Heroes},
7};
8use tuwunel_core::utils::{BoolExt, ReadyExt, TryFutureExtExt, stream::BroadbandExt};
9use tuwunel_service::Services;
10
11const MAX_HEROES: usize = 5;
12
13#[tracing::instrument(name = "heroes", level = "trace", skip_all)]
14pub(super) async fn calculate_heroes(
15	services: &Services,
16	sender_user: &UserId,
17	room_id: &RoomId,
18	room_name: Option<&DisplayName>,
19	room_avatar: Option<&MxcUri>,
20) -> (Option<Heroes>, Option<DisplayName>, Option<OwnedMxcUri>) {
21	let heroes: Heroes = services
22		.state_cache
23		.room_members(room_id)
24		.ready_filter(|&member| member != sender_user)
25		.ready_filter_map(|member| room_name.is_none().then_some(member))
26		.map(ToOwned::to_owned)
27		.broadn_filter_map(MAX_HEROES, async |user_id| {
28			let content = services
29				.state_accessor
30				.get_member(room_id, &user_id)
31				.await
32				.ok()?;
33
34			let name = content
35				.displayname
36				.is_none()
37				.then_async(|| services.users.displayname(&user_id).ok());
38
39			let avatar = content
40				.avatar_url
41				.is_none()
42				.then_async(|| services.users.avatar_url(&user_id).ok());
43
44			let (name, avatar) = join(name, avatar).await;
45			let hero = response::Hero {
46				user_id,
47				avatar: avatar.unwrap_or(content.avatar_url),
48				name: name
49					.unwrap_or(content.displayname)
50					.map(Into::into),
51			};
52
53			Some(hero)
54		})
55		.take(MAX_HEROES)
56		.collect()
57		.await;
58
59	let hero_name = match heroes.len().cmp(&(1_usize)) {
60		| Ordering::Less => None,
61		| Ordering::Equal => Some(
62			heroes[0]
63				.name
64				.clone()
65				.unwrap_or_else(|| heroes[0].user_id.as_str().into()),
66		),
67		| Ordering::Greater => {
68			let firsts = heroes[1..]
69				.iter()
70				.map(|h| {
71					h.name
72						.clone()
73						.unwrap_or_else(|| h.user_id.as_str().into())
74				})
75				.collect::<Vec<_>>()
76				.join(", ");
77
78			let last = heroes[0]
79				.name
80				.clone()
81				.unwrap_or_else(|| heroes[0].user_id.as_str().into());
82
83			Some(format!("{firsts} and {last}")).map(Into::into)
84		},
85	};
86
87	let heroes_avatar = (room_avatar.is_none() && room_name.is_none())
88		.then(|| {
89			heroes
90				.first()
91				.and_then(|hero| hero.avatar.clone())
92		})
93		.flatten();
94
95	(Some(heroes), hero_name, heroes_avatar)
96}