tuwunel_api/client/sync/v5/rooms/
heroes.rs1use 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}