Skip to main content

ruma_client_api/delayed_events/
send_delayed_event.rs

1//! `PUT /_matrix/client/*/rooms/{roomId}/delayed_event/{eventType}/{txnId}`
2//!
3//! Send a delayed event (a scheduled message) to a room.
4
5pub mod unstable {
6    //! `msc4140` ([MSC])
7    //!
8    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140
9
10    use std::time::Duration;
11
12    use ruma_common::{
13        OwnedRoomId, OwnedTransactionId,
14        api::{auth_scheme::AccessToken, request, response},
15        metadata,
16        serde::Raw,
17    };
18    #[cfg(feature = "unstable-msc4354")]
19    use ruma_events::sticky::StickyDurationMs;
20    use ruma_events::{AnyTimelineEventContent, TimelineEventType};
21
22    metadata! {
23        method: PUT,
24        rate_limited: true,
25        authentication: AccessToken,
26        history: {
27            unstable("org.matrix.msc4140") => "/_matrix/client/unstable/org.matrix.msc4140/rooms/{room_id}/delayed_event/{event_type}/{txn_id}",
28        }
29    }
30    /// Request type for the [`send_delayed_event`](crate::delayed_events::send_delayed_event)
31    /// endpoint.
32    #[request]
33    pub struct Request {
34        /// The room to send the event to.
35        #[ruma_api(path)]
36        pub room_id: OwnedRoomId,
37
38        /// The type of event to send.
39        #[ruma_api(path)]
40        pub event_type: TimelineEventType,
41
42        /// The transaction ID for this event.
43        ///
44        /// Clients should generate a unique ID across requests within the
45        /// same session. A session is identified by an access token, and
46        /// persists when the [access token is refreshed].
47        ///
48        /// It will be used by the server to ensure idempotency of requests.
49        ///
50        /// [access token is refreshed]: https://spec.matrix.org/v1.19/client-server-api/#refreshing-access-tokens
51        #[ruma_api(path)]
52        pub txn_id: OwnedTransactionId,
53
54        /// The duration that the server should wait before sending this event
55        #[serde(with = "ruma_common::serde::duration::ms")]
56        pub delay: Duration,
57
58        /// The duration to stick the delayed event.
59        ///
60        /// Caller must first check that the server supports sticky events (via `/versions`),
61        /// or it will be no-op.
62        ///
63        /// See [MSC4354 sticky events](https://github.com/matrix-org/matrix-spec-proposals/pull/4354).
64        #[cfg(feature = "unstable-msc4354")]
65        #[ruma_api(query)]
66        #[serde(
67            skip_serializing_if = "Option::is_none",
68            rename = "org.matrix.msc4354.sticky_duration_ms"
69        )]
70        pub sticky_duration_ms: Option<StickyDurationMs>,
71
72        /// The State Key if the event is a state event, nothing otherwise
73        #[serde(skip_serializing_if = "Option::is_none")]
74        pub state_key: Option<String>,
75
76        /// The event content to send.
77        pub content: Raw<AnyTimelineEventContent>,
78    }
79
80    /// Response type for the
81    /// [`send_delayed_event`](crate::delayed_events::send_delayed_event) endpoint.
82    #[response]
83    pub struct Response {
84        /// The `delay_id` generated for this delayed event. Used to interact with delayed events.
85        pub delay_id: String,
86    }
87
88    impl Request {
89        /// Creates a new `Request` with the given room id, transaction id, `delay_parameters` and
90        /// event content.
91        ///
92        /// # Errors
93        ///
94        /// Since `Request` stores the request body in serialized form, this function can fail if
95        /// `T`s [`::serde::Serialize`] implementation can fail.
96        pub fn new(
97            room_id: OwnedRoomId,
98            txn_id: OwnedTransactionId,
99            delay: Duration,
100            state_key: Option<String>,
101            content: &AnyTimelineEventContent,
102        ) -> serde_json::Result<Self> {
103            Ok(Self {
104                room_id,
105                txn_id,
106                event_type: content.event_type(),
107                state_key,
108                delay,
109                #[cfg(feature = "unstable-msc4354")]
110                sticky_duration_ms: None,
111                content: Raw::new(content)?,
112            })
113        }
114
115        /// Creates a new `Request` with the given room id, transaction id, event type,
116        /// `delay_parameters` and raw event content.
117        pub fn new_raw(
118            event_type: TimelineEventType,
119            room_id: OwnedRoomId,
120            txn_id: OwnedTransactionId,
121            delay: Duration,
122            state_key: Option<String>,
123            content: Raw<AnyTimelineEventContent>,
124        ) -> serde_json::Result<Self> {
125            Ok(Self {
126                room_id,
127                txn_id,
128                event_type,
129                state_key,
130                delay,
131                #[cfg(feature = "unstable-msc4354")]
132                sticky_duration_ms: None,
133                content,
134            })
135        }
136    }
137
138    impl Response {
139        /// Creates a new `Response` with the tokens required to control the delayed event using the
140        /// [`crate::delayed_events::update_delayed_event::unstable_v2::Request`] request.
141        pub fn new(delay_id: String) -> Self {
142            Self { delay_id }
143        }
144    }
145
146    #[cfg(all(test, feature = "client"))]
147    mod client_tests {
148        use std::borrow::Cow;
149
150        use ruma_common::{
151            api::{
152                MatrixVersion, OutgoingRequestExt as _, SupportedVersions,
153                auth_scheme::SendAccessToken,
154            },
155            owned_room_id,
156        };
157        use ruma_events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent};
158        use serde_json::{Value as JsonValue, json};
159        use web_time::Duration;
160
161        use super::Request;
162
163        #[test]
164        fn serialize_send_delayed_event_request() {
165            let room_id = owned_room_id!("!roomid:example.org");
166            let supported = SupportedVersions {
167                versions: [MatrixVersion::V1_1].into(),
168                features: Default::default(),
169            };
170
171            let req = Request::new(
172                room_id,
173                "1234".into(),
174                Duration::from_millis(103),
175                None,
176                &AnyMessageLikeEventContent::from(RoomMessageEventContent::text_plain("test"))
177                    .into(),
178            )
179            .unwrap();
180            let request: http::Request<Vec<u8>> = req
181                .try_into_http_request(
182                    "https://homeserver.tld",
183                    SendAccessToken::IfRequired("auth_tok"),
184                    Cow::Owned(supported),
185                )
186                .unwrap();
187            let (parts, body) = request.into_parts();
188            assert_eq!(
189                "https://homeserver.tld/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/1234",
190                parts.uri.to_string()
191            );
192            assert_eq!("PUT", parts.method.to_string());
193            assert_eq!(
194                json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 103}),
195                serde_json::from_str::<JsonValue>(std::str::from_utf8(&body).unwrap()).unwrap()
196            );
197        }
198
199        #[cfg(feature = "unstable-msc4354")]
200        #[test]
201        fn serialize_send_delayed_sticky_event_request() {
202            use ruma_events::sticky::StickyDurationMs;
203
204            let supported = SupportedVersions {
205                versions: [MatrixVersion::V1_1].into(),
206                features: Default::default(),
207            };
208
209            let mut req = Request::new(
210                owned_room_id!("!roomid:example.org"),
211                "1234".into(),
212                Duration::from_millis(30_000),
213                None,
214                &AnyMessageLikeEventContent::from(RoomMessageEventContent::text_plain("test"))
215                    .into(),
216            )
217            .unwrap();
218            req.sticky_duration_ms = Some(StickyDurationMs::new_clamped(300_000_u32));
219
220            let request: http::Request<Vec<u8>> = req
221                .try_into_http_request(
222                    "https://homeserver.tld",
223                    SendAccessToken::IfRequired("auth_tok"),
224                    Cow::Owned(supported),
225                )
226                .unwrap();
227            let (parts, body) = request.into_parts();
228            assert_eq!(
229                "https://homeserver.tld/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/1234?org.matrix.msc4354.sticky_duration_ms=300000",
230                parts.uri.to_string()
231            );
232            assert_eq!(
233                json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 30000}),
234                serde_json::from_str::<JsonValue>(std::str::from_utf8(&body).unwrap()).unwrap()
235            );
236        }
237    }
238
239    #[cfg(all(test, feature = "server"))]
240    mod server_tests {
241
242        use std::time::Duration;
243
244        use ruma_common::{OwnedTransactionId, api::IncomingRequest, owned_room_id};
245        use serde_json::json;
246
247        use super::Request;
248
249        #[test]
250        fn deserialize_send_delayed_events_request() {
251            let uri = http::Uri::builder()
252                .scheme("https")
253                .authority("matrix.org")
254                .path_and_query(
255                    "/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/5678",
256                )
257                .build()
258                .unwrap();
259
260            let body = json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 103});
261
262            let req = Request::try_from_http_request(
263                http::Request::builder().method("PUT").uri(uri).body(body.to_string()).unwrap(),
264                &["!roomid:example.org", "m.room.message", "5678"],
265            )
266            .unwrap();
267
268            assert_eq!(req.room_id, owned_room_id!("!roomid:example.org"));
269            assert_eq!(req.event_type, "m.room.message".into());
270            assert_eq!(req.txn_id, OwnedTransactionId::from("5678"));
271            assert_eq!(req.delay, Duration::from_millis(103));
272            assert_eq!(req.state_key, None);
273            assert_eq!(
274                serde_json::from_str::<serde_json::Value>(req.content.json().get()).unwrap(),
275                json!({"msgtype":"m.text","body":"test"}),
276            );
277        }
278
279        /// Without the query parameter the delayed event is scheduled as a regular, non-sticky
280        /// event.
281        #[cfg(feature = "unstable-msc4354")]
282        #[test]
283        fn deserialize_send_delayed_event_request_without_sticky() {
284            let uri = http::Uri::builder()
285                .scheme("https")
286                .authority("matrix.org")
287                .path_and_query(
288                    "/_matrix/client/unstable/org.matrix.msc4140/rooms/!roomid:example.org/delayed_event/m.room.message/5678",
289                )
290                .build()
291                .unwrap();
292
293            let body = json!({"content":{"msgtype":"m.text","body":"test"}, "delay": 103});
294
295            let req = Request::try_from_http_request(
296                http::Request::builder().method("PUT").uri(uri).body(body.to_string()).unwrap(),
297                &["!roomid:example.org", "m.room.message", "5678"],
298            )
299            .unwrap();
300
301            assert_eq!(req.sticky_duration_ms, None);
302        }
303    }
304}