1use axum::extract::State;
2use futures::{FutureExt, StreamExt, TryFutureExt, future::Either, pin_mut};
3use ruma::{
4 DeviceId, RoomId, UInt, UserId,
5 api::{
6 Direction,
7 client::{filter::RoomEventFilter, message::get_message_events},
8 },
9 events::{
10 AnyStateEvent, StateEventType, TimelineEventType, TimelineEventType::*,
11 relation::RelationType,
12 },
13 serde::Raw,
14};
15use tuwunel_core::{
16 Err, PduId, Result, at,
17 matrix::{
18 event::{Event, Matches},
19 pdu::{PduCount, PduEvent},
20 },
21 ref_at,
22 smallvec::SmallVec,
23 utils::{
24 BoolExt, IterStream, ReadyExt,
25 result::{FlatOk, LogErr},
26 stream::{BroadbandExt, TryIgnore, WidebandExt},
27 },
28};
29use tuwunel_service::{
30 Services,
31 rooms::{
32 lazy_loading,
33 lazy_loading::{Options, Witness},
34 short::ShortRoomId,
35 timeline::PdusIterItem,
36 },
37};
38
39use crate::Ruma;
40
41pub(crate) struct MessagesArgs<'a> {
44 pub room_id: &'a RoomId,
45 pub sender_user: &'a UserId,
46 pub sender_device: Option<&'a DeviceId>,
47 pub from: Option<&'a str>,
48 pub to: Option<&'a str>,
49 pub dir: Direction,
50 pub limit: Option<UInt>,
51 pub filter: &'a RoomEventFilter,
52
53 pub bypass_visibility: bool,
56}
57
58const IGNORED_MESSAGE_TYPES: &[TimelineEventType] = &[
61 CallInvite, KeyVerificationStart, Location, PollStart, Reaction, RoomEncrypted, RoomMessage, Sticker, Audio, Emote, File, Image, Video, Voice, UnstablePollStart, Beacon, CallNotify, ];
79
80type RelTypes = SmallVec<[RelationType; 1]>;
82
83const LIMIT_MAX: usize = 1000;
84const LIMIT_DEFAULT: usize = 10;
85
86pub(crate) async fn get_message_events_route(
93 State(services): State<crate::State>,
94 body: Ruma<get_message_events::v3::Request>,
95) -> Result<get_message_events::v3::Response> {
96 get_messages(&services, MessagesArgs {
97 room_id: &body.room_id,
98 sender_user: body.sender_user(),
99 sender_device: body.sender_device.as_deref(),
100 from: body.from.as_deref(),
101 to: body.to.as_deref(),
102 dir: body.dir,
103 limit: Some(body.limit),
104 filter: &body.filter,
105 bypass_visibility: false,
106 })
107 .await
108}
109
110pub(crate) async fn get_messages(
114 services: &Services,
115 args: MessagesArgs<'_>,
116) -> Result<get_message_events::v3::Response> {
117 let MessagesArgs {
118 room_id,
119 sender_user,
120 sender_device,
121 from,
122 to,
123 dir,
124 limit,
125 filter,
126 bypass_visibility,
127 } = args;
128
129 if !services.metadata.exists(room_id).await {
130 return Err!(Request(Forbidden("Room does not exist to this server")));
131 }
132
133 if !bypass_visibility
134 && !services
135 .state_accessor
136 .user_can_see_room(sender_user, room_id)
137 .await
138 {
139 return Err!(Request(Forbidden("You don't have permission to view this room.")));
140 }
141
142 let from: PduCount = from
143 .map(str::parse)
144 .transpose()?
145 .unwrap_or_else(|| match dir {
146 | Direction::Forward => PduCount::min(),
147 | Direction::Backward => PduCount::max(),
148 });
149
150 let to: Option<PduCount> = to.map(str::parse).flat_ok();
151
152 let limit: usize = limit
153 .and_then(|limit| limit.try_into().ok())
154 .unwrap_or(LIMIT_DEFAULT)
155 .min(LIMIT_MAX);
156
157 if matches!(dir, Direction::Backward) {
158 services
159 .timeline
160 .backfill_if_required(room_id, from)
161 .await
162 .log_err()
163 .ok();
164 }
165
166 let it = match dir {
167 | Direction::Forward => Either::Left(
168 services
169 .timeline
170 .pdus(Some(sender_user), room_id, Some(from))
171 .ignore_err(),
172 ),
173 | Direction::Backward => Either::Right(
174 services
175 .timeline
176 .pdus_rev(Some(sender_user), room_id, Some(from))
177 .ignore_err(),
178 ),
179 };
180
181 let encrypted = services
182 .state_accessor
183 .is_encrypted_room(room_id)
184 .await;
185
186 let shortroomid = services.short.get_shortroomid(room_id).await?;
187
188 let events: Vec<_> = it
189 .ready_take_while(|(count, _)| Some(*count) != to)
190 .ready_filter_map(|item| event_filter(item, filter))
191 .wide_filter_map(|item| related_by_filter(services, shortroomid, filter, item))
192 .wide_filter_map(|item| event_filters(services, sender_user, item, bypass_visibility))
193 .take(limit)
194 .wide_then(|item| add_membership_unsigned(services, item, sender_user, encrypted))
195 .wide_then(async |(count, pdu)| {
196 let pdu = services
197 .pdu_metadata
198 .bundle_aggregations(sender_user, pdu)
199 .await;
200
201 (count, pdu)
202 })
203 .collect()
204 .await;
205
206 let lazy_loading_context = lazy_loading::Context {
207 user_id: sender_user,
208 device_id: sender_device,
209 room_id,
210 token: Some(from.into_unsigned()),
211 options: Some(&filter.lazy_load_options),
212 mode: lazy_loading::Mode::Update,
213 };
214
215 let witness = filter
216 .lazy_load_options
217 .is_enabled()
218 .then_async(|| lazy_loading_witness(services, &lazy_loading_context, events.iter()));
219
220 let state = witness
221 .map(Option::into_iter)
222 .map(|option| option.flat_map(Witness::into_iter))
223 .map(IterStream::stream)
224 .into_stream()
225 .flatten()
226 .broad_filter_map(async |user_id| get_member_event(services, room_id, &user_id).await)
227 .collect()
228 .await;
229
230 let next_token = events.last().map(at!(0));
231
232 let chunk = events
233 .into_iter()
234 .map(at!(1))
235 .map(Event::into_format)
236 .collect();
237
238 Ok(get_message_events::v3::Response {
239 start: from.to_string(),
240 end: next_token.as_ref().map(ToString::to_string),
241 chunk,
242 state,
243 })
244}
245
246pub(crate) async fn lazy_loading_witness<'a, I>(
247 services: &Services,
248 lazy_loading_context: &lazy_loading::Context<'_>,
249 events: I,
250) -> Witness
251where
252 I: Iterator<Item = &'a PdusIterItem> + Clone + Send,
253{
254 let oldest = events
255 .clone()
256 .map(|(count, _)| count)
257 .copied()
258 .min()
259 .unwrap_or_else(PduCount::max);
260
261 let newest = events
262 .clone()
263 .map(|(count, _)| count)
264 .copied()
265 .max()
266 .unwrap_or_else(PduCount::max);
267
268 let receipts = services.read_receipt.readreceipts_since(
269 lazy_loading_context.room_id,
270 oldest.into_unsigned(),
271 Some(newest.into_unsigned()),
272 );
273
274 pin_mut!(receipts);
275 let witness: Witness = events
276 .stream()
277 .map(ref_at!(1))
278 .map(Event::sender)
279 .map(ToOwned::to_owned)
280 .chain(
281 receipts
282 .ready_take_while(|(_, c, _)| *c <= newest.into_unsigned())
283 .map(|(user_id, ..)| user_id.to_owned()),
284 )
285 .collect()
286 .await;
287
288 services
289 .lazy_loading
290 .witness_retain(witness, lazy_loading_context)
291 .await
292}
293
294async fn get_member_event(
295 services: &Services,
296 room_id: &RoomId,
297 user_id: &UserId,
298) -> Option<Raw<AnyStateEvent>> {
299 services
300 .state_accessor
301 .room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
302 .map_ok(Event::into_format)
303 .await
304 .ok()
305}
306
307pub(crate) async fn event_filters(
308 services: &Services,
309 user_id: &UserId,
310 item: PdusIterItem,
311 bypass_visibility: bool,
312) -> Option<PdusIterItem> {
313 if bypass_visibility {
314 return Some(item);
315 }
316
317 let item = ignored_filter(services, item, user_id).await?;
318 let item = visibility_filter(services, item, user_id).await?;
319
320 Some(item)
321}
322
323pub(crate) async fn related_by_filter(
327 services: &Services,
328 shortroomid: ShortRoomId,
329 filter: &RoomEventFilter,
330 item: PdusIterItem,
331) -> Option<PdusIterItem> {
332 if filter.related_by_senders.is_empty() && filter.related_by_rel_types.is_empty() {
333 return Some(item);
334 }
335
336 let rel_types: RelTypes = filter
337 .related_by_rel_types
338 .iter()
339 .map(String::as_str)
340 .map(RelationType::from)
341 .collect();
342
343 let (count, _) = &item;
344 let target = PduId { shortroomid, count: *count };
345
346 services
347 .pdu_metadata
348 .has_incoming_relation(target, &filter.related_by_senders, &rel_types)
349 .await
350 .then_some(item)
351}
352
353#[inline]
354pub(crate) async fn ignored_filter(
355 services: &Services,
356 item: PdusIterItem,
357 user_id: &UserId,
358) -> Option<PdusIterItem> {
359 let (_, ref pdu) = item;
360
361 is_ignored_pdu(services, pdu, user_id)
362 .await
363 .is_false()
364 .then_some(item)
365}
366
367#[inline]
368pub(crate) async fn is_ignored_pdu<Pdu>(
369 services: &Services,
370 event: &Pdu,
371 user_id: &UserId,
372) -> bool
373where
374 Pdu: Event,
375{
376 if event.kind().to_cow_str() == "org.matrix.dummy_event" {
379 return true;
380 }
381
382 if IGNORED_MESSAGE_TYPES
383 .binary_search(event.kind())
384 .is_err()
385 {
386 return false;
387 }
388
389 let ignored_server = services
390 .config
391 .is_forbidden_remote_server_name(event.sender().server_name());
392
393 ignored_server
394 || services
395 .users
396 .user_is_ignored(event.sender(), user_id)
397 .await
398}
399
400#[inline]
401pub(crate) async fn visibility_filter(
402 services: &Services,
403 item: PdusIterItem,
404 user_id: &UserId,
405) -> Option<PdusIterItem> {
406 let (_, pdu) = &item;
407
408 services
409 .state_accessor
410 .user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
411 .await
412 .then_some(item)
413}
414
415#[inline]
416pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Option<PdusIterItem> {
417 let (_, pdu) = &item;
418 filter.matches(pdu).then_some(item)
419}
420
421#[inline]
426pub(crate) async fn annotate_membership(
427 services: &Services,
428 pdu: &mut PduEvent,
429 user_id: &UserId,
430 encrypted: bool,
431) {
432 if !encrypted {
433 return;
434 }
435
436 let membership = services
437 .state_accessor
438 .user_membership_at_pdu(user_id, pdu)
439 .await;
440
441 pdu.add_membership(&membership).log_err().ok();
442}
443
444#[inline]
446pub(crate) async fn with_membership(
447 services: &Services,
448 mut pdu: PduEvent,
449 user_id: &UserId,
450 encrypted: bool,
451) -> PduEvent {
452 annotate_membership(services, &mut pdu, user_id, encrypted).await;
453 pdu
454}
455
456#[inline]
458pub(crate) async fn add_membership_unsigned(
459 services: &Services,
460 (count, pdu): PdusIterItem,
461 user_id: &UserId,
462 encrypted: bool,
463) -> PdusIterItem {
464 (count, with_membership(services, pdu, user_id, encrypted).await)
465}
466
467#[cfg_attr(debug_assertions, tuwunel_core::ctor(unsafe))]
468fn _is_sorted() {
469 debug_assert!(
470 IGNORED_MESSAGE_TYPES.is_sorted(),
471 "IGNORED_MESSAGE_TYPES must be sorted by the developer"
472 );
473}