Skip to main content

tuwunel_api/client/
relations.rs

1use std::iter::once;
2
3use axum::extract::State;
4use futures::{
5	FutureExt, StreamExt, TryFutureExt,
6	future::try_join3,
7	stream::{select_all, unfold},
8};
9use ruma::{
10	EventId, RoomId, UInt, UserId,
11	api::{
12		Direction,
13		client::relations::{
14			get_relating_events, get_relating_events_with_rel_type,
15			get_relating_events_with_rel_type_and_event_type,
16		},
17	},
18	events::{TimelineEventType, relation::RelationType},
19};
20use tuwunel_core::{
21	Err, Error, Result, at, err,
22	matrix::{
23		event::{Event, RelationTypeEqual},
24		pdu::{PduCount, PduId},
25	},
26	utils::{
27		BoolExt,
28		math::usize_from_ruma_bounded,
29		result::FlatOk,
30		stream::{ReadyExt, WidebandExt},
31	},
32};
33use tuwunel_service::Services;
34
35use crate::{Ruma, client::is_ignored_pdu};
36
37/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
38pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
39	State(services): State<crate::State>,
40	body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
41) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
42	paginate_relations_with_filter(
43		&services,
44		body.sender_user(),
45		&body.room_id,
46		&body.event_id,
47		body.event_type.clone().into(),
48		body.rel_type.clone().into(),
49		body.from.as_deref(),
50		body.to.as_deref(),
51		body.limit,
52		body.recurse,
53		body.dir,
54	)
55	.await
56	.map(|res| get_relating_events_with_rel_type_and_event_type::v1::Response {
57		chunk: res.chunk,
58		next_batch: res.next_batch,
59		prev_batch: res.prev_batch,
60		recursion_depth: res.recursion_depth,
61	})
62}
63
64/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}`
65pub(crate) async fn get_relating_events_with_rel_type_route(
66	State(services): State<crate::State>,
67	body: Ruma<get_relating_events_with_rel_type::v1::Request>,
68) -> Result<get_relating_events_with_rel_type::v1::Response> {
69	paginate_relations_with_filter(
70		&services,
71		body.sender_user(),
72		&body.room_id,
73		&body.event_id,
74		None,
75		body.rel_type.clone().into(),
76		body.from.as_deref(),
77		body.to.as_deref(),
78		body.limit,
79		body.recurse,
80		body.dir,
81	)
82	.await
83	.map(|res| get_relating_events_with_rel_type::v1::Response {
84		chunk: res.chunk,
85		next_batch: res.next_batch,
86		prev_batch: res.prev_batch,
87		recursion_depth: res.recursion_depth,
88	})
89}
90
91/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}`
92pub(crate) async fn get_relating_events_route(
93	State(services): State<crate::State>,
94	body: Ruma<get_relating_events::v1::Request>,
95) -> Result<get_relating_events::v1::Response> {
96	paginate_relations_with_filter(
97		&services,
98		body.sender_user(),
99		&body.room_id,
100		&body.event_id,
101		None,
102		None,
103		body.from.as_deref(),
104		body.to.as_deref(),
105		body.limit,
106		body.recurse,
107		body.dir,
108	)
109	.await
110}
111
112#[expect(clippy::too_many_arguments)]
113#[tracing::instrument(
114	name = "relations",
115	level = "debug",
116	skip_all,
117	fields(room_id, target, from, to, dir, limit, recurse)
118)]
119async fn paginate_relations_with_filter(
120	services: &Services,
121	sender_user: &UserId,
122	room_id: &RoomId,
123	target: &EventId,
124	filter_event_type: Option<TimelineEventType>,
125	filter_rel_type: Option<RelationType>,
126	from: Option<&str>,
127	to: Option<&str>,
128	limit: Option<UInt>,
129	recurse: bool,
130	dir: Direction,
131) -> Result<get_relating_events::v1::Response> {
132	let from: Option<PduCount> = from.map(str::parse).transpose()?;
133
134	let to: Option<PduCount> = to.map(str::parse).flat_ok();
135
136	// Spec (v1.10) recommends depth of at least 3
137	let max_depth: usize = if recurse { 3 } else { 0 };
138
139	let limit = limit.map_or(30, |limit| usize_from_ruma_bounded(limit, 30, 100));
140
141	let target_event_id: &EventId = target;
142
143	let target = services
144		.timeline
145		.get_pdu_id(target)
146		.map_ok(PduId::from)
147		.map_ok(Ok::<_, Error>);
148
149	let visible = services
150		.state_accessor
151		.user_can_see_state_events(sender_user, room_id)
152		.map(|visible| {
153			visible
154				.into_option()
155				.ok_or_else(|| err!(Request(Forbidden("You cannot view this room."))))
156		});
157
158	let shortroomid = services.short.get_shortroomid(room_id);
159
160	let (shortroomid, target, ()) = try_join3(shortroomid, target, visible).await?;
161
162	let Ok(target) = target else {
163		return Ok(get_relating_events::v1::Response::new(Vec::new()));
164	};
165
166	if shortroomid != target.shortroomid {
167		return Err!(Request(NotFound("Event not found in room.")));
168	}
169
170	if let PduCount::Backfilled(_) = target.count {
171		return Ok(get_relating_events::v1::Response::new(Vec::new()));
172	}
173
174	if let Ok(target_pdu) = services.timeline.get_pdu(target_event_id).await
175		&& is_ignored_pdu(services, &target_pdu, sender_user).await
176	{
177		return Err!(HttpJson(NOT_FOUND, {
178			"errcode": "M_SENDER_IGNORED",
179			"error": "You have ignored the user that sent this event",
180			"sender": target_pdu.sender().as_str(),
181		}));
182	}
183
184	let fetch = |depth: usize, count: PduCount| {
185		services
186			.pdu_metadata
187			.get_relations(shortroomid, count, from, dir, Some(sender_user))
188			.map(move |(count, pdu)| (depth, count, pdu))
189			.ready_filter(|(_, count, _)| matches!(count, PduCount::Normal(_)))
190			.boxed()
191	};
192
193	let events = unfold(select_all(once(fetch(0, target.count))), async |mut relations| {
194		let (depth, count, pdu) = relations.next().await?;
195
196		if depth < max_depth {
197			relations.push(fetch(depth.saturating_add(1), count));
198		}
199
200		Some(((depth, count, pdu), relations))
201	})
202	.ready_take_while(|&(_, count, _)| Some(count) != to)
203	.ready_filter(|(_, _, pdu)| {
204		filter_event_type
205			.as_ref()
206			.is_none_or(|kind| kind == pdu.kind())
207	})
208	.ready_filter(|(_, _, pdu)| {
209		filter_rel_type
210			.as_ref()
211			.is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
212	})
213	.wide_filter_map(async |(depth, count, pdu)| {
214		services
215			.state_accessor
216			.user_can_see_event(sender_user, &pdu)
217			.await
218			.then_some((depth, count, pdu))
219	})
220	.take(limit)
221	.wide_then(async |(depth, count, pdu)| {
222		let pdu = services
223			.pdu_metadata
224			.bundle_aggregations(sender_user, pdu)
225			.await;
226
227		(depth, count, pdu)
228	})
229	.collect::<Vec<_>>()
230	.await;
231
232	Ok(get_relating_events::v1::Response {
233		recursion_depth: max_depth
234			.gt(&0)
235			.then(|| events.iter().map(at!(0)))
236			.into_iter()
237			.flatten()
238			.max()
239			.map(TryInto::try_into)
240			.transpose()?,
241
242		next_batch: events
243			.last()
244			.map(at!(1))
245			.as_ref()
246			.map(ToString::to_string),
247
248		prev_batch: events
249			.first()
250			.map(at!(1))
251			.or(from)
252			.as_ref()
253			.map(ToString::to_string),
254
255		chunk: events
256			.into_iter()
257			.map(at!(2))
258			.map(Event::into_format)
259			.collect(),
260	})
261}