1#![deny(unreachable_pub)]
18
19#[cfg(feature = "experimental-encrypted-state-events")]
20use std::borrow::Borrow;
21use std::future::IntoFuture;
22
23use eyeball::SharedObservable;
24use matrix_sdk_common::boxed_into_future;
25use mime::Mime;
26#[cfg(doc)]
27use ruma::events::{MessageLikeUnsigned, SyncMessageLikeEvent};
28use ruma::{
29 api::client::message::send_message_event,
30 assign,
31 events::{AnyMessageLikeEventContent, MessageLikeEventContent},
32 serde::Raw,
33 OwnedTransactionId, TransactionId,
34};
35#[cfg(feature = "experimental-encrypted-state-events")]
36use ruma::{
37 api::client::state::send_state_event,
38 events::{AnyStateEventContent, StateEventContent},
39};
40use tracing::{info, trace, Instrument, Span};
41
42use super::Room;
43#[cfg(feature = "experimental-encrypted-state-events")]
44use crate::utils::IntoRawStateEventContent;
45use crate::{
46 attachment::AttachmentConfig, config::RequestConfig, utils::IntoRawMessageLikeEventContent,
47 Result, TransmissionProgress,
48};
49
50#[allow(missing_debug_implementations)]
52pub struct SendMessageLikeEvent<'a> {
53 room: &'a Room,
54 event_type: String,
55 content: serde_json::Result<serde_json::Value>,
56 transaction_id: Option<OwnedTransactionId>,
57 request_config: Option<RequestConfig>,
58}
59
60impl<'a> SendMessageLikeEvent<'a> {
61 pub(crate) fn new(room: &'a Room, content: impl MessageLikeEventContent) -> Self {
62 let event_type = content.event_type().to_string();
63 let content = serde_json::to_value(&content);
64 Self { room, event_type, content, transaction_id: None, request_config: None }
65 }
66
67 pub fn with_transaction_id(mut self, txn_id: OwnedTransactionId) -> Self {
85 self.transaction_id = Some(txn_id);
86 self
87 }
88
89 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
92 self.request_config = Some(request_config);
93 self
94 }
95}
96
97impl<'a> IntoFuture for SendMessageLikeEvent<'a> {
98 type Output = Result<send_message_event::v3::Response>;
99 boxed_into_future!(extra_bounds: 'a);
100
101 fn into_future(self) -> Self::IntoFuture {
102 let Self { room, event_type, content, transaction_id, request_config } = self;
103 Box::pin(async move {
104 let content = content?;
105 assign!(room.send_raw(&event_type, content), { transaction_id, request_config }).await
106 })
107 }
108}
109
110#[allow(missing_debug_implementations)]
112pub struct SendRawMessageLikeEvent<'a> {
113 room: &'a Room,
114 event_type: &'a str,
115 content: Raw<AnyMessageLikeEventContent>,
116 tracing_span: Span,
117 transaction_id: Option<OwnedTransactionId>,
118 request_config: Option<RequestConfig>,
119}
120
121impl<'a> SendRawMessageLikeEvent<'a> {
122 pub(crate) fn new(
123 room: &'a Room,
124 event_type: &'a str,
125 content: impl IntoRawMessageLikeEventContent,
126 ) -> Self {
127 let content = content.into_raw_message_like_event_content();
128 Self {
129 room,
130 event_type,
131 content,
132 tracing_span: Span::current(),
133 transaction_id: None,
134 request_config: None,
135 }
136 }
137
138 pub fn with_transaction_id(mut self, txn_id: &TransactionId) -> Self {
153 self.transaction_id = Some(txn_id.to_owned());
154 self
155 }
156
157 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
160 self.request_config = Some(request_config);
161 self
162 }
163}
164
165impl<'a> IntoFuture for SendRawMessageLikeEvent<'a> {
166 type Output = Result<send_message_event::v3::Response>;
167 boxed_into_future!(extra_bounds: 'a);
168
169 fn into_future(self) -> Self::IntoFuture {
170 #[cfg_attr(not(feature = "e2e-encryption"), allow(unused_mut))]
171 let Self {
172 room,
173 mut event_type,
174 mut content,
175 tracing_span,
176 transaction_id,
177 request_config,
178 } = self;
179
180 let fut = async move {
181 room.ensure_room_joined()?;
182
183 let txn_id = transaction_id.unwrap_or_else(TransactionId::new);
184 Span::current().record("transaction_id", tracing::field::debug(&txn_id));
185
186 #[cfg(not(feature = "e2e-encryption"))]
187 trace!("Sending plaintext event to room because we don't have encryption support.");
188
189 #[cfg(feature = "e2e-encryption")]
190 if room.latest_encryption_state().await?.is_encrypted() {
191 Span::current().record("is_room_encrypted", true);
192 if event_type == "m.reaction" {
195 trace!("Sending plaintext event because of the event type.");
196 } else {
197 trace!(
198 room_id = ?room.room_id(),
199 "Sending encrypted event because the room is encrypted.",
200 );
201
202 ensure_room_encryption_ready(room).await?;
203
204 let olm = room.client.olm_machine().await;
205 let olm = olm.as_ref().expect("Olm machine wasn't started");
206
207 content = olm
208 .encrypt_room_event_raw(room.room_id(), event_type, &content)
209 .await?
210 .cast();
211 event_type = "m.room.encrypted";
212 }
213 } else {
214 Span::current().record("is_room_encrypted", false);
215 trace!("Sending plaintext event because the room is NOT encrypted.");
216 }
217
218 let request = send_message_event::v3::Request::new_raw(
219 room.room_id().to_owned(),
220 txn_id,
221 event_type.into(),
222 content,
223 );
224
225 let response = room.client.send(request).with_request_config(request_config).await?;
226
227 Span::current().record("event_id", tracing::field::debug(&response.event_id));
228 info!("Sent event in room");
229
230 Ok(response)
231 };
232
233 Box::pin(fut.instrument(tracing_span))
234 }
235}
236
237#[allow(missing_debug_implementations)]
239pub struct SendAttachment<'a> {
240 room: &'a Room,
241 filename: String,
242 content_type: &'a Mime,
243 data: Vec<u8>,
244 config: AttachmentConfig,
245 tracing_span: Span,
246 send_progress: SharedObservable<TransmissionProgress>,
247 store_in_cache: bool,
248}
249
250impl<'a> SendAttachment<'a> {
251 pub(crate) fn new(
252 room: &'a Room,
253 filename: String,
254 content_type: &'a Mime,
255 data: Vec<u8>,
256 config: AttachmentConfig,
257 ) -> Self {
258 Self {
259 room,
260 filename,
261 content_type,
262 data,
263 config,
264 tracing_span: Span::current(),
265 send_progress: Default::default(),
266 store_in_cache: false,
267 }
268 }
269
270 pub fn with_send_progress_observable(
273 mut self,
274 send_progress: SharedObservable<TransmissionProgress>,
275 ) -> Self {
276 self.send_progress = send_progress;
277 self
278 }
279
280 pub fn store_in_cache(mut self) -> Self {
285 self.store_in_cache = true;
286 self
287 }
288}
289
290impl<'a> IntoFuture for SendAttachment<'a> {
291 type Output = Result<send_message_event::v3::Response>;
292 boxed_into_future!(extra_bounds: 'a);
293
294 fn into_future(self) -> Self::IntoFuture {
295 let Self {
296 room,
297 filename,
298 content_type,
299 data,
300 config,
301 tracing_span,
302 send_progress,
303 store_in_cache,
304 } = self;
305 let fut = async move {
306 room.prepare_and_send_attachment(
307 filename,
308 content_type,
309 data,
310 config,
311 send_progress,
312 store_in_cache,
313 )
314 .await
315 };
316
317 Box::pin(fut.instrument(tracing_span))
318 }
319}
320
321#[cfg(feature = "experimental-encrypted-state-events")]
323#[allow(missing_debug_implementations)]
324pub struct SendRawStateEvent<'a> {
325 room: &'a Room,
326 event_type: &'a str,
327 state_key: &'a str,
328 content: Raw<AnyStateEventContent>,
329 tracing_span: Span,
330 request_config: Option<RequestConfig>,
331}
332
333#[cfg(feature = "experimental-encrypted-state-events")]
334impl<'a> SendRawStateEvent<'a> {
335 pub(crate) fn new(
336 room: &'a Room,
337 event_type: &'a str,
338 state_key: &'a str,
339 content: impl IntoRawStateEventContent,
340 ) -> Self {
341 let content = content.into_raw_state_event_content();
342 Self {
343 room,
344 event_type,
345 state_key,
346 content,
347 tracing_span: Span::current(),
348 request_config: None,
349 }
350 }
351
352 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
355 self.request_config = Some(request_config);
356 self
357 }
358
359 fn should_encrypt(room: &Room, event_type: &str) -> bool {
373 if !room.encryption_state().is_state_encrypted() {
374 trace!("Sending plaintext event as the room does NOT support encrypted state events.");
375 return false;
376 }
377
378 if matches!(
380 event_type,
381 "m.room.create"
382 | "m.room.member"
383 | "m.room.join_rules"
384 | "m.room.power_levels"
385 | "m.room.third_party_invite"
386 | "m.room.history_visibility"
387 | "m.room.guest_access"
388 | "m.room.encryption"
389 | "m.space.child"
390 | "m.space.parent"
391 ) {
392 trace!("Sending plaintext event as its type is excluded from encryption.");
393 return false;
394 }
395
396 true
397 }
398}
399
400#[cfg(feature = "experimental-encrypted-state-events")]
401impl<'a> IntoFuture for SendRawStateEvent<'a> {
402 type Output = Result<send_state_event::v3::Response>;
403 boxed_into_future!(extra_bounds: 'a);
404
405 fn into_future(self) -> Self::IntoFuture {
406 let Self { room, mut event_type, state_key, mut content, tracing_span, request_config } =
407 self;
408
409 let fut = async move {
410 room.ensure_room_joined()?;
411
412 let mut state_key = state_key.to_owned();
413
414 if Self::should_encrypt(room, event_type) {
415 use tracing::debug;
416
417 Span::current().record("should_encrypt", true);
418 debug!(
419 room_id = ?room.room_id(),
420 "Sending encrypted event because the room is encrypted.",
421 );
422
423 ensure_room_encryption_ready(room).await?;
424
425 let olm = room.client.olm_machine().await;
426 let olm = olm.as_ref().expect("Olm machine wasn't started");
427
428 content = olm
429 .encrypt_state_event_raw(room.room_id(), event_type, &state_key, &content)
430 .await?
431 .cast_unchecked();
432
433 state_key = format!("{event_type}:{state_key}");
434 event_type = "m.room.encrypted";
435 } else {
436 Span::current().record("should_encrypt", false);
437 }
438
439 let request = send_state_event::v3::Request::new_raw(
440 room.room_id().to_owned(),
441 event_type.into(),
442 state_key.to_owned(),
443 content,
444 );
445
446 let response = room.client.send(request).with_request_config(request_config).await?;
447
448 Span::current().record("event_id", tracing::field::debug(&response.event_id));
449 info!("Sent event in room");
450
451 Ok(response)
452 };
453
454 Box::pin(fut.instrument(tracing_span))
455 }
456}
457
458#[allow(missing_debug_implementations)]
460#[cfg(feature = "experimental-encrypted-state-events")]
461pub struct SendStateEvent<'a> {
462 room: &'a Room,
463 event_type: String,
464 state_key: String,
465 content: serde_json::Result<serde_json::Value>,
466 request_config: Option<RequestConfig>,
467}
468
469#[cfg(feature = "experimental-encrypted-state-events")]
470impl<'a> SendStateEvent<'a> {
471 pub(crate) fn new<C, K>(room: &'a Room, state_key: &K, content: C) -> Self
472 where
473 C: StateEventContent,
474 C::StateKey: Borrow<K>,
475 K: AsRef<str> + ?Sized,
476 {
477 let event_type = content.event_type().to_string();
478 let state_key = state_key.as_ref().to_owned();
479 let content = serde_json::to_value(&content);
480 Self { room, event_type, state_key, content, request_config: None }
481 }
482
483 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
486 self.request_config = Some(request_config);
487 self
488 }
489}
490
491#[cfg(feature = "experimental-encrypted-state-events")]
492impl<'a> IntoFuture for SendStateEvent<'a> {
493 type Output = Result<send_state_event::v3::Response>;
494 boxed_into_future!(extra_bounds: 'a);
495
496 fn into_future(self) -> Self::IntoFuture {
497 let Self { room, state_key, event_type, content, request_config } = self;
498 Box::pin(async move {
499 let content = content?;
500 assign!(room.send_state_event_raw(&event_type, &state_key, content), { request_config })
501 .await
502 })
503 }
504}
505
506#[cfg(feature = "e2e-encryption")]
508async fn ensure_room_encryption_ready(room: &Room) -> Result<()> {
509 if !room.are_members_synced() {
510 room.sync_members().await?;
511 }
512
513 room.query_keys_for_untracked_or_dirty_users().await?;
519
520 room.preshare_room_key().await?;
521
522 Ok(())
523}