matrix_sdk/room/messages.rs
1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16
17use futures_util::future::join_all;
18use matrix_sdk_common::{debug::DebugStructExt as _, deserialized_responses::TimelineEvent};
19use ruma::{
20 OwnedEventId, RoomId, UInt,
21 api::{
22 Direction,
23 client::{
24 filter::RoomEventFilter,
25 message::get_message_events,
26 relations,
27 threads::get_threads::{self, v1::IncludeThreads},
28 },
29 },
30 assign,
31 events::{AnyStateEvent, TimelineEventType, relation::RelationType},
32 serde::Raw,
33 uint,
34};
35
36use super::Room;
37use crate::Result;
38
39/// Options for [`messages`][super::Room::messages].
40///
41/// See that method and
42/// <https://spec.matrix.org/v1.3/client-server-api/#get_matrixclientv3roomsroomidmessages>
43/// for details.
44#[non_exhaustive]
45pub struct MessagesOptions {
46 /// The token to start returning events from.
47 ///
48 /// This token can be obtained from a `prev_batch` token returned for each
49 /// room from the sync API, or from a start or end token returned by a
50 /// previous `messages` call.
51 ///
52 /// If `from` isn't provided the homeserver shall return a list of messages
53 /// from the first or last (per the value of the dir parameter) visible
54 /// event in the room history for the requesting user.
55 pub from: Option<String>,
56
57 /// The token to stop returning events at.
58 ///
59 /// This token can be obtained from a `prev_batch` token returned for each
60 /// room by the sync API, or from a start or end token returned by a
61 /// previous `messages` call.
62 pub to: Option<String>,
63
64 /// The direction to return events in.
65 pub dir: Direction,
66
67 /// The maximum number of events to return.
68 ///
69 /// Default: 10.
70 pub limit: UInt,
71
72 /// A [`RoomEventFilter`] to filter returned events with.
73 pub filter: RoomEventFilter,
74}
75
76impl MessagesOptions {
77 /// Creates `MessagesOptions` with the given direction.
78 ///
79 /// All other parameters will be defaulted.
80 pub fn new(dir: Direction) -> Self {
81 Self { from: None, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() }
82 }
83
84 /// Creates `MessagesOptions` with `dir` set to `Backward`.
85 ///
86 /// If no `from` token is set afterwards, pagination will start at the
87 /// end of (the accessible part of) the room timeline.
88 pub fn backward() -> Self {
89 Self::new(Direction::Backward)
90 }
91
92 /// Creates `MessagesOptions` with `dir` set to `Forward`.
93 ///
94 /// If no `from` token is set afterwards, pagination will start at the
95 /// beginning of (the accessible part of) the room timeline.
96 pub fn forward() -> Self {
97 Self::new(Direction::Forward)
98 }
99
100 /// Creates a new `MessagesOptions` from `self` with the `from` field set to
101 /// the given value.
102 ///
103 /// Since the field is public, you can also assign to it directly. This
104 /// method merely acts as a shorthand for that, because it is very
105 /// common to set this field.
106 pub fn from<'a>(self, from: impl Into<Option<&'a str>>) -> Self {
107 Self { from: from.into().map(ToOwned::to_owned), ..self }
108 }
109
110 pub(super) fn into_request(self, room_id: &RoomId) -> get_message_events::v3::Request {
111 assign!(get_message_events::v3::Request::new(room_id.to_owned(), self.dir), {
112 from: self.from,
113 to: self.to,
114 limit: self.limit,
115 filter: self.filter,
116 })
117 }
118}
119
120#[cfg(not(tarpaulin_include))]
121impl fmt::Debug for MessagesOptions {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 let Self { from, to, dir, limit, filter } = self;
124
125 let mut s = f.debug_struct("MessagesOptions");
126 s.maybe_field("from", from).maybe_field("to", to).field("dir", dir).field("limit", limit);
127 if !filter.is_empty() {
128 s.field("filter", filter);
129 }
130 s.finish()
131 }
132}
133
134/// The result of a [`super::Room::messages`] call.
135///
136/// In short, this is a possibly decrypted version of the response of a
137/// `room/messages` api call.
138#[derive(Debug, Default)]
139pub struct Messages {
140 /// The token the pagination starts from.
141 pub start: String,
142
143 /// The token the pagination ends at.
144 pub end: Option<String>,
145
146 /// A list of room events.
147 pub chunk: Vec<TimelineEvent>,
148
149 /// A list of state events relevant to showing the `chunk`.
150 pub state: Vec<Raw<AnyStateEvent>>,
151}
152
153/// The result of a [`super::Room::event_with_context`] query.
154///
155/// This is a wrapper around
156/// [`ruma::api::client::context::get_context::v3::Response`], with events
157/// decrypted if needs be.
158#[derive(Debug, Default)]
159pub struct EventWithContextResponse {
160 /// The event targeted by the /context query.
161 pub event: Option<TimelineEvent>,
162
163 /// Events before the target event, if a non-zero context size was
164 /// requested.
165 ///
166 /// Like the corresponding Ruma response, these are in reverse chronological
167 /// order.
168 pub events_before: Vec<TimelineEvent>,
169
170 /// Events after the target event, if a non-zero context size was requested.
171 ///
172 /// Like the corresponding Ruma response, these are in chronological order.
173 pub events_after: Vec<TimelineEvent>,
174
175 /// Token to paginate backwards, aka "start" token.
176 pub prev_batch_token: Option<String>,
177
178 /// Token to paginate forwards, aka "end" token.
179 pub next_batch_token: Option<String>,
180
181 /// State events related to the request.
182 ///
183 /// If lazy-loading of members was requested, this may contain room
184 /// membership events.
185 pub state: Vec<Raw<AnyStateEvent>>,
186}
187
188/// Options for [super::Room::list_threads].
189#[derive(Debug, Default)]
190pub struct ListThreadsOptions {
191 /// An extra filter to select which threads should be returned.
192 pub include_threads: IncludeThreads,
193
194 /// The token to start returning events from.
195 ///
196 /// This token can be obtained from a [`ThreadRoots::prev_batch_token`]
197 /// returned by a previous call to [`super::Room::list_threads()`].
198 ///
199 /// If `from` isn't provided the homeserver shall return a list of thread
200 /// roots from end of the timeline history.
201 pub from: Option<String>,
202
203 /// The maximum number of events to return.
204 ///
205 /// Default: 10.
206 pub limit: Option<UInt>,
207}
208
209impl ListThreadsOptions {
210 /// Converts the thread options into a Ruma request.
211 pub(super) fn into_request(self, room_id: &RoomId) -> get_threads::v1::Request {
212 assign!(get_threads::v1::Request::new(room_id.to_owned()), {
213 from: self.from,
214 include: self.include_threads,
215 limit: self.limit,
216 })
217 }
218}
219
220/// The result of a [`super::Room::list_threads`] query.
221///
222/// This is a wrapper around the Ruma equivalent, with events decrypted if needs
223/// be.
224#[derive(Debug)]
225pub struct ThreadRoots {
226 /// The events that are thread roots in the current batch.
227 pub chunk: Vec<TimelineEvent>,
228
229 /// Token to paginate backwards in a subsequent query to
230 /// [`super::Room::list_threads`].
231 pub prev_batch_token: Option<String>,
232}
233
234/// What kind of relations should be included in a [`super::Room::relations`]
235/// query.
236#[derive(Clone, Debug, Default)]
237pub enum IncludeRelations {
238 /// Include all relations independently of their relation type.
239 #[default]
240 AllRelations,
241 /// Include all relations of a given relation type.
242 RelationsOfType(RelationType),
243 /// Include all relations of a given relation type and event type.
244 RelationsOfTypeAndEventType(RelationType, TimelineEventType),
245}
246
247/// Options for [`messages`][super::Room::relations].
248#[derive(Clone, Debug, Default)]
249pub struct RelationsOptions {
250 /// The token to start returning events from.
251 ///
252 /// This token can be obtained from a [`Relations::prev_batch_token`]
253 /// returned by a previous call to [`super::Room::relations()`].
254 ///
255 /// If `from` isn't provided the homeserver shall return a list of thread
256 /// roots from end of the timeline history.
257 pub from: Option<String>,
258
259 /// The direction to return events in.
260 ///
261 /// Defaults to backwards.
262 pub dir: Direction,
263
264 /// The maximum number of events to return.
265 ///
266 /// Default: 10.
267 pub limit: Option<UInt>,
268
269 /// Optional restrictions on the relations to include based on their type or
270 /// event type.
271 ///
272 /// Defaults to all relations.
273 pub include_relations: IncludeRelations,
274
275 /// Whether to include events which relate indirectly to the given event.
276 ///
277 /// These are events related to the given event via two or more direct
278 /// relationships.
279 pub recurse: bool,
280}
281
282impl RelationsOptions {
283 /// Converts this options object into a request, according to the filled
284 /// parameters, and returns a canonicalized response.
285 pub(super) async fn send(self, room: &Room, event: OwnedEventId) -> Result<Relations> {
286 macro_rules! fill_params {
287 ($request:expr) => {
288 assign! { $request, {
289 from: self.from,
290 dir: self.dir,
291 limit: self.limit,
292 recurse: self.recurse,
293 }}
294 };
295 }
296
297 // This match to common out the different `Response` types into a single one. It
298 // would've been nice that Ruma used the same response type for all the
299 // responses, but it is likely doing so to guard against possible future
300 // changes.
301 let (chunk, prev_batch, next_batch, recursion_depth) = match self.include_relations {
302 IncludeRelations::AllRelations => {
303 let request = fill_params!(relations::get_relating_events::v1::Request::new(
304 room.room_id().to_owned(),
305 event,
306 ));
307 let response = room.client.send(request).await?;
308 (response.chunk, response.prev_batch, response.next_batch, response.recursion_depth)
309 }
310
311 IncludeRelations::RelationsOfType(relation_type) => {
312 let request =
313 fill_params!(relations::get_relating_events_with_rel_type::v1::Request::new(
314 room.room_id().to_owned(),
315 event,
316 relation_type,
317 ));
318 let response = room.client.send(request).await?;
319 (response.chunk, response.prev_batch, response.next_batch, response.recursion_depth)
320 }
321
322 IncludeRelations::RelationsOfTypeAndEventType(relation_type, timeline_event_type) => {
323 let request = fill_params!(
324 relations::get_relating_events_with_rel_type_and_event_type::v1::Request::new(
325 room.room_id().to_owned(),
326 event,
327 relation_type,
328 timeline_event_type,
329 )
330 );
331 let response = room.client.send(request).await?;
332 (response.chunk, response.prev_batch, response.next_batch, response.recursion_depth)
333 }
334 };
335
336 let push_ctx = room.push_context().await?;
337 let chunk = join_all(chunk.into_iter().map(|ev| {
338 // Cast safety: an `AnyMessageLikeEvent` is a subset of an `AnyTimelineEvent`.
339 room.try_decrypt_event(ev.cast(), push_ctx.as_ref())
340 }))
341 .await;
342
343 Ok(Relations {
344 chunk,
345 prev_batch_token: prev_batch,
346 next_batch_token: next_batch,
347 recursion_depth,
348 })
349 }
350}
351
352/// The result of a [`super::Room::relations`] query.
353///
354/// This is a wrapper around the Ruma equivalents, with events decrypted if
355/// needs be.
356#[derive(Debug)]
357pub struct Relations {
358 /// The events related to the specified event from the request.
359 ///
360 /// Note: the events will be sorted according to the `dir` parameter:
361 /// - if the direction was backwards, then the events will be ordered in
362 /// reverse topological order.
363 /// - if the direction was forwards, then the events will be ordered in
364 /// topological order.
365 pub chunk: Vec<TimelineEvent>,
366
367 /// An opaque string representing a pagination token to retrieve the
368 /// previous batch of events.
369 pub prev_batch_token: Option<String>,
370
371 /// An opaque string representing a pagination token to retrieve the next
372 /// batch of events.
373 pub next_batch_token: Option<String>,
374
375 /// If [`RelationsOptions::recurse`] was set, the depth to which the server
376 /// recursed.
377 pub recursion_depth: Option<UInt>,
378}