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}