1#![deny(unreachable_pub)]
18
19#[cfg(feature = "experimental-encrypted-state-events")]
20use std::borrow::Borrow;
21use std::{future::IntoFuture, time::Duration};
22
23use eyeball::SharedObservable;
24use matrix_sdk_base::deserialized_responses::EncryptionInfo;
25use matrix_sdk_common::boxed_into_future;
26use mime::Mime;
27#[cfg(feature = "e2e-encryption")]
28use ruma::events::MessageLikeEventType;
29#[cfg(feature = "unstable-msc4354")]
30use ruma::events::sticky::StickyDurationMs;
31#[cfg(doc)]
32use ruma::events::{MessageLikeUnsigned, SyncMessageLikeEvent};
33#[cfg(feature = "experimental-encrypted-state-events")]
34use ruma::events::{StateEventContent, StateEventType};
35use ruma::{
36 OwnedTransactionId, TransactionId,
37 api::client::{
38 delayed_events::send_delayed_event, message::send_message_event, state::send_state_event,
39 },
40 assign,
41 events::{AnyMessageLikeEventContent, AnyStateEventContent, MessageLikeEventContent},
42 serde::Raw,
43};
44use tracing::{Instrument, Span, info, trace};
45
46use super::Room;
47use crate::{
48 Result, TransmissionProgress,
49 attachment::AttachmentConfig,
50 config::RequestConfig,
51 utils::{IntoRawMessageLikeEventContent, IntoRawStateEventContent},
52};
53
54#[derive(Debug)]
56pub struct SendMessageLikeEventResult {
57 pub response: send_message_event::v3::Response,
59 pub encryption_info: Option<EncryptionInfo>,
61}
62
63#[allow(missing_debug_implementations)]
65pub struct SendMessageLikeEvent<'a> {
66 room: &'a Room,
67 event_type: String,
68 content: serde_json::Result<serde_json::Value>,
69 transaction_id: Option<OwnedTransactionId>,
70 request_config: Option<RequestConfig>,
71 #[cfg(feature = "unstable-msc4354")]
72 sticky_duration: Option<StickyDurationMs>,
73}
74
75impl<'a> SendMessageLikeEvent<'a> {
76 pub(crate) fn new(room: &'a Room, content: impl MessageLikeEventContent) -> Self {
77 let event_type = content.event_type().to_string();
78 let content = serde_json::to_value(&content);
79 Self {
80 room,
81 event_type,
82 content,
83 transaction_id: None,
84 request_config: None,
85 #[cfg(feature = "unstable-msc4354")]
86 sticky_duration: None,
87 }
88 }
89
90 #[cfg(feature = "unstable-msc4354")]
98 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
99 self.sticky_duration = Some(crate::utils::sticky_duration_ms(duration));
100 self
101 }
102
103 pub fn with_transaction_id(mut self, txn_id: OwnedTransactionId) -> Self {
121 self.transaction_id = Some(txn_id);
122 self
123 }
124
125 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
128 self.request_config = Some(request_config);
129 self
130 }
131}
132
133impl<'a> IntoFuture for SendMessageLikeEvent<'a> {
134 type Output = Result<SendMessageLikeEventResult>;
135 boxed_into_future!(extra_bounds: 'a);
136
137 fn into_future(self) -> Self::IntoFuture {
138 let Self {
139 room,
140 event_type,
141 content,
142 transaction_id,
143 request_config,
144 #[cfg(feature = "unstable-msc4354")]
145 sticky_duration,
146 } = self;
147 Box::pin(async move {
148 let content = content?;
149 let future =
150 assign!(room.send_raw(&event_type, content), { transaction_id, request_config });
151 #[cfg(feature = "unstable-msc4354")]
152 let future = assign!(future, { sticky_duration });
153 future.await
154 })
155 }
156}
157
158#[allow(missing_debug_implementations)]
160pub struct SendRawMessageLikeEvent<'a> {
161 room: &'a Room,
162 event_type: &'a str,
163 content: Raw<AnyMessageLikeEventContent>,
164 tracing_span: Span,
165 transaction_id: Option<OwnedTransactionId>,
166 request_config: Option<RequestConfig>,
167 #[cfg(feature = "unstable-msc4354")]
168 sticky_duration: Option<StickyDurationMs>,
169}
170
171impl<'a> SendRawMessageLikeEvent<'a> {
172 pub(crate) fn new(
173 room: &'a Room,
174 event_type: &'a str,
175 content: impl IntoRawMessageLikeEventContent,
176 ) -> Self {
177 let content = content.into_raw_message_like_event_content();
178 Self {
179 room,
180 event_type,
181 content,
182 tracing_span: Span::current(),
183 transaction_id: None,
184 request_config: None,
185 #[cfg(feature = "unstable-msc4354")]
186 sticky_duration: None,
187 }
188 }
189
190 #[cfg(feature = "unstable-msc4354")]
198 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
199 self.sticky_duration = Some(crate::utils::sticky_duration_ms(duration));
200 self
201 }
202
203 pub fn with_transaction_id(mut self, txn_id: &TransactionId) -> Self {
218 self.transaction_id = Some(txn_id.to_owned());
219 self
220 }
221
222 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
225 self.request_config = Some(request_config);
226 self
227 }
228
229 pub fn into_delayed(self, delay: Duration) -> SendDelayedRawMessageLikeEvent<'a> {
252 SendDelayedRawMessageLikeEvent { inner: self, delay }
253 }
254}
255
256impl<'a> IntoFuture for SendRawMessageLikeEvent<'a> {
257 type Output = Result<SendMessageLikeEventResult>;
258 boxed_into_future!(extra_bounds: 'a);
259
260 fn into_future(self) -> Self::IntoFuture {
261 let Self {
262 room,
263 event_type,
264 content,
265 tracing_span,
266 transaction_id,
267 request_config,
268 #[cfg(feature = "unstable-msc4354")]
269 sticky_duration,
270 } = self;
271
272 let fut = async move {
273 let PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info } =
274 prepare_message_like_event(room, event_type, content, transaction_id).await?;
275
276 let request = send_message_event::v3::Request::new_raw(
277 room.room_id().to_owned(),
278 txn_id,
279 event_type.into(),
280 content,
281 );
282 #[cfg(feature = "unstable-msc4354")]
283 let request = assign!(request, { sticky_duration_ms: sticky_duration });
284
285 let response = room.client.send(request).with_request_config(request_config).await?;
286
287 Span::current().record("event_id", tracing::field::debug(&response.event_id));
288 info!("Sent event in room");
289
290 Ok(SendMessageLikeEventResult { response, encryption_info })
291 };
292
293 Box::pin(fut.instrument(tracing_span))
294 }
295}
296
297#[allow(missing_debug_implementations)]
299pub struct SendDelayedRawMessageLikeEvent<'a> {
300 inner: SendRawMessageLikeEvent<'a>,
301 delay: Duration,
302}
303
304impl<'a> SendDelayedRawMessageLikeEvent<'a> {
305 pub fn with_transaction_id(mut self, txn_id: &TransactionId) -> Self {
309 self.inner = self.inner.with_transaction_id(txn_id);
310 self
311 }
312
313 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
316 self.inner = self.inner.with_request_config(request_config);
317 self
318 }
319}
320
321impl<'a> IntoFuture for SendDelayedRawMessageLikeEvent<'a> {
322 type Output = Result<send_delayed_event::msc4140::Response>;
323 boxed_into_future!(extra_bounds: 'a);
324
325 fn into_future(self) -> Self::IntoFuture {
326 let Self {
327 inner:
328 SendRawMessageLikeEvent {
329 room,
330 event_type,
331 content,
332 tracing_span,
333 transaction_id,
334 request_config,
335 #[cfg(feature = "unstable-msc4354")]
336 sticky_duration,
337 },
338 delay,
339 } = self;
340
341 let fut = async move {
342 let PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info: _ } =
343 prepare_message_like_event(room, event_type, content, transaction_id).await?;
344
345 let request = send_delayed_event::msc4140::Request::new_raw(
346 event_type.into(),
347 room.room_id().to_owned(),
348 txn_id,
349 delay,
350 None,
351 content.cast(),
352 )?;
353 #[cfg(feature = "unstable-msc4354")]
354 let request = assign!(request, { sticky_duration_ms: sticky_duration });
355
356 let response = room.client.send(request).with_request_config(request_config).await?;
357
358 info!(delay_id = response.delay_id, "Scheduled delayed event in room");
359
360 Ok(response)
361 };
362
363 Box::pin(fut.instrument(tracing_span))
364 }
365}
366
367struct PreparedMessageLikeEvent<'a> {
369 txn_id: OwnedTransactionId,
370 event_type: &'a str,
372 content: Raw<AnyMessageLikeEventContent>,
373 encryption_info: Option<EncryptionInfo>,
375}
376
377#[cfg_attr(not(feature = "e2e-encryption"), allow(unused_mut, clippy::unused_async))]
381async fn prepare_message_like_event<'a>(
382 room: &Room,
383 mut event_type: &'a str,
384 mut content: Raw<AnyMessageLikeEventContent>,
385 transaction_id: Option<OwnedTransactionId>,
386) -> Result<PreparedMessageLikeEvent<'a>> {
387 room.ensure_room_joined()?;
388
389 let txn_id = transaction_id.unwrap_or_else(TransactionId::new);
390 Span::current().record("transaction_id", tracing::field::debug(&txn_id));
391
392 #[cfg(not(feature = "e2e-encryption"))]
393 trace!("Sending plaintext event to room because we don't have encryption support.");
394
395 #[cfg(feature = "e2e-encryption")]
396 let mut encryption_info: Option<EncryptionInfo> = None;
397 #[cfg(not(feature = "e2e-encryption"))]
398 let encryption_info: Option<EncryptionInfo> = None;
399
400 #[cfg(feature = "e2e-encryption")]
401 if room.latest_encryption_state().await?.is_encrypted() {
402 Span::current().record("is_room_encrypted", true);
403 if MessageLikeEventType::from(event_type) == MessageLikeEventType::Reaction {
406 trace!("Sending plaintext event because of the event type.");
407 } else {
408 trace!(
409 room_id = ?room.room_id(),
410 "Sending encrypted event because the room is encrypted.",
411 );
412
413 ensure_room_encryption_ready(room).await?;
414
415 let olm = room.client.olm_machine().await;
416 let olm = olm.as_ref().expect("Olm machine wasn't started");
417
418 let result = olm.encrypt_room_event_raw(room.room_id(), event_type, &content).await?;
419 content = result.content.cast();
420 encryption_info = Some(result.encryption_info);
421 event_type = "m.room.encrypted";
422 }
423 } else {
424 Span::current().record("is_room_encrypted", false);
425 trace!("Sending plaintext event because the room is NOT encrypted.");
426 }
427
428 Ok(PreparedMessageLikeEvent { txn_id, event_type, content, encryption_info })
429}
430
431#[allow(missing_debug_implementations)]
433pub struct SendAttachment<'a> {
434 room: &'a Room,
435 filename: String,
436 content_type: &'a Mime,
437 data: Vec<u8>,
438 config: AttachmentConfig,
439 tracing_span: Span,
440 send_progress: SharedObservable<TransmissionProgress>,
441 store_in_cache: bool,
442}
443
444impl<'a> SendAttachment<'a> {
445 pub(crate) fn new(
446 room: &'a Room,
447 filename: String,
448 content_type: &'a Mime,
449 data: Vec<u8>,
450 config: AttachmentConfig,
451 ) -> Self {
452 Self {
453 room,
454 filename,
455 content_type,
456 data,
457 config,
458 tracing_span: Span::current(),
459 send_progress: Default::default(),
460 store_in_cache: false,
461 }
462 }
463
464 pub fn with_send_progress_observable(
467 mut self,
468 send_progress: SharedObservable<TransmissionProgress>,
469 ) -> Self {
470 self.send_progress = send_progress;
471 self
472 }
473
474 pub fn store_in_cache(mut self) -> Self {
479 self.store_in_cache = true;
480 self
481 }
482}
483
484impl<'a> IntoFuture for SendAttachment<'a> {
485 type Output = Result<send_message_event::v3::Response>;
486 boxed_into_future!(extra_bounds: 'a);
487
488 fn into_future(self) -> Self::IntoFuture {
489 let Self {
490 room,
491 filename,
492 content_type,
493 data,
494 config,
495 tracing_span,
496 send_progress,
497 store_in_cache,
498 } = self;
499 let fut = async move {
500 room.prepare_and_send_attachment(
501 filename,
502 content_type,
503 data,
504 config,
505 send_progress,
506 store_in_cache,
507 )
508 .await
509 };
510
511 Box::pin(fut.instrument(tracing_span))
512 }
513}
514
515#[allow(missing_debug_implementations)]
517pub struct SendRawStateEvent<'a> {
518 room: &'a Room,
519 event_type: &'a str,
520 state_key: &'a str,
521 content: Raw<AnyStateEventContent>,
522 tracing_span: Span,
523 request_config: Option<RequestConfig>,
524}
525
526impl<'a> SendRawStateEvent<'a> {
527 pub(crate) fn new(
528 room: &'a Room,
529 event_type: &'a str,
530 state_key: &'a str,
531 content: impl IntoRawStateEventContent,
532 ) -> Self {
533 let content = content.into_raw_state_event_content();
534 Self {
535 room,
536 event_type,
537 state_key,
538 content,
539 tracing_span: Span::current(),
540 request_config: None,
541 }
542 }
543
544 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
547 self.request_config = Some(request_config);
548 self
549 }
550
551 pub fn into_delayed(self, delay: Duration) -> SendDelayedRawStateEvent<'a> {
567 SendDelayedRawStateEvent { inner: self, delay }
568 }
569
570 #[cfg(feature = "experimental-encrypted-state-events")]
585 fn should_encrypt(room: &Room, event_type: &str) -> bool {
586 if !room.encryption_state().is_state_encrypted() {
587 trace!("Sending plaintext event as the room does NOT support encrypted state events.");
588 return false;
589 }
590
591 if matches!(
593 StateEventType::from(event_type),
594 StateEventType::RoomCreate
595 | StateEventType::RoomMember
596 | StateEventType::RoomJoinRules
597 | StateEventType::RoomPowerLevels
598 | StateEventType::RoomThirdPartyInvite
599 | StateEventType::RoomHistoryVisibility
600 | StateEventType::RoomGuestAccess
601 | StateEventType::RoomEncryption
602 | StateEventType::SpaceChild
603 | StateEventType::SpaceParent
604 ) {
605 trace!("Sending plaintext event as its type is excluded from encryption.");
606 return false;
607 }
608
609 true
610 }
611}
612
613impl<'a> IntoFuture for SendRawStateEvent<'a> {
614 type Output = Result<send_state_event::v3::Response>;
615 boxed_into_future!(extra_bounds: 'a);
616
617 fn into_future(self) -> Self::IntoFuture {
618 let Self { room, event_type, state_key, content, tracing_span, request_config } = self;
619
620 let fut = async move {
621 let PreparedStateEvent { event_type, state_key, content } =
622 prepare_state_event(room, event_type, state_key, content).await?;
623
624 let request = send_state_event::v3::Request::new_raw(
625 room.room_id().to_owned(),
626 event_type.into(),
627 state_key,
628 content,
629 );
630
631 let response = room.client.send(request).with_request_config(request_config).await?;
632
633 Span::current().record("event_id", tracing::field::debug(&response.event_id));
634 info!("Sent event in room");
635
636 Ok(response)
637 };
638
639 Box::pin(fut.instrument(tracing_span))
640 }
641}
642
643#[allow(missing_debug_implementations)]
645pub struct SendDelayedRawStateEvent<'a> {
646 inner: SendRawStateEvent<'a>,
647 delay: Duration,
648}
649
650impl<'a> SendDelayedRawStateEvent<'a> {
651 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
654 self.inner = self.inner.with_request_config(request_config);
655 self
656 }
657}
658
659impl<'a> IntoFuture for SendDelayedRawStateEvent<'a> {
660 type Output = Result<send_delayed_event::msc4140::Response>;
661 boxed_into_future!(extra_bounds: 'a);
662
663 fn into_future(self) -> Self::IntoFuture {
664 let Self {
665 inner:
666 SendRawStateEvent { room, event_type, state_key, content, tracing_span, request_config },
667 delay,
668 } = self;
669
670 let fut = async move {
671 let PreparedStateEvent { event_type, state_key, content } =
672 prepare_state_event(room, event_type, state_key, content).await?;
673
674 let request = send_delayed_event::msc4140::Request::new_raw(
675 event_type.into(),
676 room.room_id().to_owned(),
677 TransactionId::new(),
678 delay,
679 Some(state_key),
680 content.cast(),
681 )?;
682
683 let response = room.client.send(request).with_request_config(request_config).await?;
684
685 info!(delay_id = response.delay_id, "Scheduled delayed state event in room");
686
687 Ok(response)
688 };
689
690 Box::pin(fut.instrument(tracing_span))
691 }
692}
693
694struct PreparedStateEvent<'a> {
696 event_type: &'a str,
698 state_key: String,
701 content: Raw<AnyStateEventContent>,
702}
703
704#[cfg_attr(
707 not(feature = "experimental-encrypted-state-events"),
708 allow(unused_mut, clippy::unused_async)
709)]
710async fn prepare_state_event<'a>(
711 room: &Room,
712 mut event_type: &'a str,
713 state_key: &str,
714 mut content: Raw<AnyStateEventContent>,
715) -> Result<PreparedStateEvent<'a>> {
716 room.ensure_room_joined()?;
717
718 let mut state_key = state_key.to_owned();
719
720 #[cfg(feature = "experimental-encrypted-state-events")]
721 if SendRawStateEvent::should_encrypt(room, event_type) {
722 use tracing::debug;
723
724 Span::current().record("should_encrypt", true);
725 debug!(
726 room_id = ?room.room_id(),
727 "Sending encrypted event because the room is encrypted.",
728 );
729
730 ensure_room_encryption_ready(room).await?;
731
732 let olm = room.client.olm_machine().await;
733 let olm = olm.as_ref().expect("Olm machine wasn't started");
734
735 content = olm
736 .encrypt_state_event_raw(room.room_id(), event_type, &state_key, &content)
737 .await?
738 .cast_unchecked();
739
740 state_key = format!("{event_type}:{state_key}");
741 event_type = "m.room.encrypted";
742 } else {
743 Span::current().record("should_encrypt", false);
744 }
745
746 Ok(PreparedStateEvent { event_type, state_key, content })
747}
748
749#[allow(missing_debug_implementations)]
751#[cfg(feature = "experimental-encrypted-state-events")]
752pub struct SendStateEvent<'a> {
753 room: &'a Room,
754 event_type: String,
755 state_key: String,
756 content: serde_json::Result<serde_json::Value>,
757 request_config: Option<RequestConfig>,
758}
759
760#[cfg(feature = "experimental-encrypted-state-events")]
761impl<'a> SendStateEvent<'a> {
762 pub(crate) fn new<C, K>(room: &'a Room, state_key: &K, content: C) -> Self
763 where
764 C: StateEventContent,
765 C::StateKey: Borrow<K>,
766 K: AsRef<str> + ?Sized,
767 {
768 let event_type = content.event_type().to_string();
769 let state_key = state_key.as_ref().to_owned();
770 let content = serde_json::to_value(&content);
771 Self { room, event_type, state_key, content, request_config: None }
772 }
773
774 pub fn with_request_config(mut self, request_config: RequestConfig) -> Self {
777 self.request_config = Some(request_config);
778 self
779 }
780}
781
782#[cfg(feature = "experimental-encrypted-state-events")]
783impl<'a> IntoFuture for SendStateEvent<'a> {
784 type Output = Result<send_state_event::v3::Response>;
785 boxed_into_future!(extra_bounds: 'a);
786
787 fn into_future(self) -> Self::IntoFuture {
788 let Self { room, state_key, event_type, content, request_config } = self;
789 Box::pin(async move {
790 let content = content?;
791 assign!(room.send_state_event_raw(&event_type, &state_key, content), { request_config })
792 .await
793 })
794 }
795}
796
797#[cfg(feature = "e2e-encryption")]
799async fn ensure_room_encryption_ready(room: &Room) -> Result<()> {
800 if !room.are_members_synced() {
801 room.sync_members().await?;
802 }
803
804 room.query_keys_for_untracked_or_dirty_users().await?;
810
811 room.preshare_room_key().await?;
812
813 Ok(())
814}