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