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