tuwunel_api/client/membership/
members.rs1use axum::extract::State;
2use futures::{FutureExt, StreamExt, pin_mut};
3use ruma::{
4 api::client::membership::{
5 get_member_events::{self},
6 joined_members::{self, v3::RoomMember},
7 },
8 events::{
9 StateEventType,
10 room::{
11 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
12 member::{MembershipState, RoomMemberEventContent},
13 },
14 },
15};
16use tuwunel_core::{
17 Err, Result, at, is_equal_to, is_not_equal_to,
18 matrix::Event,
19 utils::{
20 future::{BoolExt, TryExtExt},
21 stream::ReadyExt,
22 },
23};
24
25use crate::Ruma;
26
27pub(crate) async fn get_member_events_route(
34 State(services): State<crate::State>,
35 body: Ruma<get_member_events::v3::Request>,
36) -> Result<get_member_events::v3::Response> {
37 if !services
38 .state_accessor
39 .user_can_see_state_events(body.sender_user(), &body.room_id)
40 .await
41 {
42 return Err!(Request(Forbidden(
43 "You aren't a member of the room and weren't previously a member of the room."
44 )));
45 }
46
47 let membership = body.membership.as_ref();
48 let not_membership = body.not_membership.as_ref();
49 let membership_filter = |content: &RoomMemberEventContent| {
50 membership.is_none_or(is_equal_to!(&content.membership))
51 && not_membership.is_none_or(is_not_equal_to!(&content.membership))
52 };
53
54 Ok(get_member_events::v3::Response {
55 chunk: services
56 .state_accessor
57 .room_state_full(&body.room_id)
58 .ready_filter_map(Result::ok)
59 .ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
60 .map(at!(1))
61 .ready_filter(|pdu| {
62 pdu.get_content::<RoomMemberEventContent>()
63 .as_ref()
64 .is_ok_and(membership_filter)
65 })
66 .map(Event::into_format)
67 .collect()
68 .boxed()
69 .await,
70 })
71}
72
73pub(crate) async fn joined_members_route(
80 State(services): State<crate::State>,
81 body: Ruma<joined_members::v3::Request>,
82) -> Result<joined_members::v3::Response> {
83 let is_joined = services
84 .state_cache
85 .is_joined(body.sender_user(), &body.room_id);
86
87 let is_world_readable = services
88 .state_accessor
89 .room_state_get_content(&body.room_id, &StateEventType::RoomHistoryVisibility, "")
90 .map_ok_or(false, |c: RoomHistoryVisibilityEventContent| {
91 c.history_visibility == HistoryVisibility::WorldReadable
92 });
93
94 pin_mut!(is_joined, is_world_readable);
95 if !is_joined.or(is_world_readable).await {
96 return Err!(Request(Forbidden("You aren't a member of the room.")));
97 }
98
99 Ok(joined_members::v3::Response {
100 joined: services
101 .state_accessor
102 .room_state_full(&body.room_id)
103 .ready_filter_map(Result::ok)
104 .ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
105 .map(at!(1))
106 .ready_filter_map(|pdu| {
107 let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
108
109 let matches = content.membership == MembershipState::Join;
110
111 matches.then(|| {
112 let sender = pdu.sender().to_owned();
113 let member = RoomMember {
114 display_name: content.displayname,
115 avatar_url: content.avatar_url,
116 };
117
118 (sender, member)
119 })
120 })
121 .collect()
122 .boxed()
123 .await,
124 })
125}