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}