1use std::sync::Arc;
16
17use as_variant::as_variant;
18use matrix_sdk::{
19 Room,
20 deserialized_responses::{TimelineEvent, TimelineEventKind},
21};
22use matrix_sdk_base::crypto::types::events::UtdCause;
23use ruma::{
24 OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, UserId,
25 events::{
26 AnyMessageLikeEventContent, AnyStateEventContentChange, Mentions, MessageLikeEventType,
27 StateEventContentChange, StateEventType,
28 policy::rule::{
29 room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
30 user::PolicyRuleUserEventContent,
31 },
32 relation::{Replacement, Reply, Thread},
33 room::{
34 avatar::RoomAvatarEventContent,
35 canonical_alias::RoomCanonicalAliasEventContent,
36 create::RoomCreateEventContent,
37 encrypted::{EncryptedEventScheme, MegolmV1AesSha2Content, RoomEncryptedEventContent},
38 encryption::RoomEncryptionEventContent,
39 guest_access::RoomGuestAccessEventContent,
40 history_visibility::RoomHistoryVisibilityEventContent,
41 join_rules::RoomJoinRulesEventContent,
42 member::{Change, RoomMemberEventContent},
43 message::{
44 MessageType, Relation, RoomMessageEventContent,
45 RoomMessageEventContentWithoutRelation,
46 },
47 name::RoomNameEventContent,
48 pinned_events::RoomPinnedEventsEventContent,
49 power_levels::RoomPowerLevelsEventContent,
50 server_acl::RoomServerAclEventContent,
51 third_party_invite::RoomThirdPartyInviteEventContent,
52 tombstone::RoomTombstoneEventContent,
53 topic::RoomTopicEventContent,
54 },
55 rtc::notification::CallIntent,
56 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
57 sticker::StickerEventContent,
58 },
59 html::RemoveReplyFallback,
60 room_version_rules::RedactionRules,
61};
62use tracing::warn;
63
64mod live_location;
65mod message;
66mod msg_like;
67pub(super) mod other;
68pub(crate) mod pinned_events;
69mod polls;
70mod reply;
71
72pub use pinned_events::RoomPinnedEventsChange;
73
74pub(in crate::timeline) use self::{
75 live_location::beacon_info_matches,
76 message::{
77 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
78 },
79};
80pub use self::{
81 live_location::{BeaconInfo, LiveLocationState},
82 message::Message,
83 msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary},
84 other::OtherMessageLike,
85 polls::{PollResult, PollState},
86 reply::{EmbeddedEvent, InReplyToDetails},
87};
88use crate::timeline::{
89 controller::ActiveCallInfo,
90 event_handler::{HandleAggregationKind, TimelineAction},
91};
92
93#[allow(clippy::large_enum_variant)]
95#[derive(Clone, Debug)]
96pub enum TimelineItemContent {
97 MsgLike(MsgLikeContent),
98
99 MembershipChange(RoomMembershipChange),
101
102 ProfileChange(MemberProfileChange),
104
105 OtherState(OtherState),
107
108 FailedToParseMessageLike {
110 event_type: MessageLikeEventType,
112
113 error: Arc<serde_json::Error>,
115 },
116
117 FailedToParseState {
119 event_type: StateEventType,
121
122 state_key: String,
124
125 error: Arc<serde_json::Error>,
127 },
128
129 CallInvite,
131
132 RtcNotification {
134 call_intent: Option<CallIntent>,
136 declined_by: Vec<OwnedUserId>,
138 active_call_info: Option<ActiveCallInfo>,
141 },
142}
143
144impl TimelineItemContent {
145 pub fn event_type_str(&self) -> Option<String> {
148 match self {
149 Self::MsgLike(msg) => Some(match &msg.kind {
150 MsgLikeKind::Message(_) => MessageLikeEventType::RoomMessage.to_string(),
151 MsgLikeKind::Sticker(_) => MessageLikeEventType::Sticker.to_string(),
152 MsgLikeKind::Poll(_) => MessageLikeEventType::PollStart.to_string(),
153 MsgLikeKind::Redacted => return None,
154 MsgLikeKind::UnableToDecrypt(_) => MessageLikeEventType::RoomEncrypted.to_string(),
155 MsgLikeKind::Other(other) => other.event_type().to_string(),
156 MsgLikeKind::LiveLocation(_) => StateEventType::BeaconInfo.to_string(),
157 }),
158 Self::MembershipChange(_) | Self::ProfileChange(_) => {
159 Some(StateEventType::RoomMember.to_string())
160 }
161 Self::OtherState(state) => Some(state.content().event_type().to_string()),
162 Self::FailedToParseMessageLike { event_type, .. } => Some(event_type.to_string()),
163 Self::FailedToParseState { event_type, .. } => Some(event_type.to_string()),
164 Self::CallInvite => Some(MessageLikeEventType::CallInvite.to_string()),
165 Self::RtcNotification { .. } => Some(MessageLikeEventType::RtcNotification.to_string()),
166 }
167 }
168
169 pub async fn from_event(room: &Room, timeline_event: TimelineEvent) -> Option<Self> {
173 let (utd_info, raw_event) = match timeline_event.kind {
174 TimelineEventKind::UnableToDecrypt { utd_info, event } => (Some(utd_info), event),
175 _ => (None, timeline_event.into_raw()),
176 };
177
178 let deserialized_event = raw_event.deserialize().ok()?;
179
180 let actions = TimelineAction::from_event(
181 deserialized_event,
182 &raw_event,
183 room,
184 utd_info.map(|utd_info| (utd_info, None)),
185 None,
186 None,
187 None,
188 )
189 .await;
190 match actions.as_slice() {
191 [TimelineAction::AddItem { content }] => Some(content.clone()),
192 [
193 TimelineAction::AddItem { content },
194 TimelineAction::HandleAggregation {
195 kind: HandleAggregationKind::BeaconStop { .. },
196 ..
197 },
198 ] => {
199 if content.is_live_location_state() {
200 Some(content.clone())
201 } else {
202 warn!(
203 "Unexpected [AddItem, BeaconStop] actions with a non-live-location \
204 AddItem; ignoring event"
205 );
206 None
207 }
208 }
209 [
211 TimelineAction::HandleAggregation {
212 kind: HandleAggregationKind::BeaconStop { content, .. },
213 ..
214 },
215 ] => Some(TimelineItemContent::MsgLike(MsgLikeContent {
216 kind: MsgLikeKind::LiveLocation(LiveLocationState::new(content.clone())),
217 thread_root: None,
218 in_reply_to: None,
219 thread_summary: None,
220 })),
221 [
222 TimelineAction::HandleAggregation {
223 kind:
224 HandleAggregationKind::Edit { replacement: Replacement { new_content, .. } },
225 ..
226 },
227 ] => {
228 match TimelineAction::from_content(
230 AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::new(
231 new_content.msgtype.clone(),
232 )),
233 None,
234 None,
235 None,
236 ) {
237 TimelineAction::AddItem { content } => Some(content),
238 _ => None,
239 }
240 }
241 [] => {
242 warn!("No action for event content processing");
243 None
244 }
245 [_, _, ..] => {
246 warn!("Ignoring event that produced multiple timeline actions");
249 None
250 }
251 _ => None,
252 }
253 }
254
255 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
256 as_variant!(self, TimelineItemContent::MsgLike)
257 }
258
259 pub fn as_live_location_state(&self) -> Option<&LiveLocationState> {
263 as_variant!(self, Self::MsgLike(MsgLikeContent {
264 kind: MsgLikeKind::LiveLocation(state),
265 ..
266 }) => state)
267 }
268
269 pub fn is_live_location_state(&self) -> bool {
272 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::LiveLocation(_), .. }))
273 }
274
275 pub fn as_message(&self) -> Option<&Message> {
278 as_variant!(self, Self::MsgLike(MsgLikeContent {
279 kind: MsgLikeKind::Message(message),
280 ..
281 }) => message)
282 }
283
284 pub fn is_message(&self) -> bool {
287 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
288 }
289
290 pub fn as_poll(&self) -> Option<&PollState> {
293 as_variant!(self, Self::MsgLike(MsgLikeContent {
294 kind: MsgLikeKind::Poll(poll_state),
295 ..
296 }) => poll_state)
297 }
298
299 pub fn is_poll(&self) -> bool {
302 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
303 }
304
305 pub fn as_sticker(&self) -> Option<&Sticker> {
306 as_variant!(
307 self,
308 Self::MsgLike(MsgLikeContent {
309 kind: MsgLikeKind::Sticker(sticker),
310 ..
311 }) => sticker
312 )
313 }
314
315 pub fn is_sticker(&self) -> bool {
318 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
319 }
320
321 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
324 as_variant!(
325 self,
326 Self::MsgLike(MsgLikeContent {
327 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
328 ..
329 }) => encrypted_message
330 )
331 }
332
333 pub fn is_unable_to_decrypt(&self) -> bool {
336 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
337 }
338
339 pub fn is_redacted(&self) -> bool {
340 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
341 }
342
343 pub(crate) fn message(
346 msgtype: MessageType,
347 mentions: Option<Mentions>,
348 thread_root: Option<OwnedEventId>,
349 in_reply_to: Option<InReplyToDetails>,
350 thread_summary: Option<ThreadSummary>,
351 ) -> Self {
352 let remove_reply_fallback =
353 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
354
355 Self::MsgLike(MsgLikeContent {
356 kind: MsgLikeKind::Message(Message::from_event(
357 msgtype,
358 mentions,
359 None,
360 remove_reply_fallback,
361 )),
362 thread_root,
363 in_reply_to,
364 thread_summary,
365 })
366 }
367
368 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
370 match self {
371 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
372 TimelineItemContent::MembershipChange(_) => "a membership change",
373 TimelineItemContent::ProfileChange(_) => "a profile change",
374 TimelineItemContent::OtherState(_) => "a state event",
375 TimelineItemContent::FailedToParseMessageLike { .. }
376 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
377 TimelineItemContent::CallInvite => "a call invite",
378 TimelineItemContent::RtcNotification { .. } => "a call notification",
379 }
380 }
381
382 pub(crate) fn room_member(
383 user_id: OwnedUserId,
384 full_content: StateEventContentChange<RoomMemberEventContent>,
385 sender: OwnedUserId,
386 ) -> Self {
387 use ruma::events::room::member::MembershipChange as MChange;
388 match &full_content {
389 StateEventContentChange::Original { content, prev_content } => {
390 let membership_change = content.membership_change(
391 prev_content.as_ref().map(|c| c.details()),
392 &sender,
393 &user_id,
394 );
395
396 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
397 membership_change
398 {
399 Self::ProfileChange(MemberProfileChange {
400 user_id,
401 displayname_change: displayname_change.map(|c| Change {
402 new: c.new.map(ToOwned::to_owned),
403 old: c.old.map(ToOwned::to_owned),
404 }),
405 avatar_url_change: avatar_url_change.map(|c| Change {
406 new: c.new.map(ToOwned::to_owned),
407 old: c.old.map(ToOwned::to_owned),
408 }),
409 })
410 } else {
411 let change = match membership_change {
412 MChange::None => MembershipChange::None,
413 MChange::Error => MembershipChange::Error,
414 MChange::Joined => MembershipChange::Joined,
415 MChange::Left => MembershipChange::Left,
416 MChange::Banned => MembershipChange::Banned,
417 MChange::Unbanned => MembershipChange::Unbanned,
418 MChange::Kicked => MembershipChange::Kicked,
419 MChange::Invited => MembershipChange::Invited,
420 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
421 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
422 MChange::InvitationRejected => MembershipChange::InvitationRejected,
423 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
424 MChange::Knocked => MembershipChange::Knocked,
425 MChange::KnockAccepted => MembershipChange::KnockAccepted,
426 MChange::KnockRetracted => MembershipChange::KnockRetracted,
427 MChange::KnockDenied => MembershipChange::KnockDenied,
428 MChange::ProfileChanged { .. } => unreachable!(),
429 _ => MembershipChange::NotImplemented,
430 };
431
432 Self::MembershipChange(RoomMembershipChange {
433 user_id,
434 content: full_content,
435 change: Some(change),
436 })
437 }
438 }
439 StateEventContentChange::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
440 user_id,
441 content: full_content,
442 change: None,
443 }),
444 }
445 }
446
447 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
448 match self {
449 Self::MsgLike(msglike) => TimelineItemContent::MsgLike(MsgLikeContent {
450 kind: MsgLikeKind::Redacted,
451 in_reply_to: None,
452 ..msglike.clone()
453 }),
454 Self::CallInvite | Self::RtcNotification { .. } => {
455 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
456 }
457 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
458 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
459 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
460 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
461 }
462 }
463
464 pub fn thread_root(&self) -> Option<OwnedEventId> {
466 as_variant!(self, Self::MsgLike)?.thread_root.clone()
467 }
468
469 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
471 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
472 }
473
474 pub(crate) fn relation(&self) -> Option<Relation<RoomMessageEventContentWithoutRelation>> {
477 if let Some(thread_root) = self.thread_root() {
478 Some(Relation::Thread(match self.in_reply_to() {
479 Some(details) => Thread::reply(thread_root, details.event_id),
480 None => Thread::plain(thread_root.clone(), thread_root),
481 }))
482 } else {
483 self.in_reply_to()
484 .map(|details| Relation::Reply(Reply::with_event_id(details.event_id)))
485 }
486 }
487
488 pub fn thread_summary(&self) -> Option<ThreadSummary> {
490 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
491 }
492}
493
494#[derive(Clone, Debug)]
496pub enum EncryptedMessage {
497 OlmV1Curve25519AesSha2 {
500 sender_key: String,
502 },
503 MegolmV1AesSha2 {
505 #[deprecated = "this field should still be sent but should not be used when received"]
507 #[doc(hidden)] sender_key: Option<String>,
509
510 #[deprecated = "this field should still be sent but should not be used when received"]
512 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
514
515 session_id: String,
517
518 cause: UtdCause,
521 },
522 Unknown,
524}
525
526impl EncryptedMessage {
527 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
528 match content.scheme {
529 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
530 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
531 }
532 #[allow(deprecated)]
533 EncryptedEventScheme::MegolmV1AesSha2(s) => {
534 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
535
536 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
537 }
538 _ => Self::Unknown,
539 }
540 }
541
542 pub(crate) fn session_id(&self) -> Option<&str> {
545 match self {
546 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
547 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
548 EncryptedMessage::Unknown => None,
549 }
550 }
551
552 pub fn utd_cause(&self) -> Option<UtdCause> {
555 match self {
556 EncryptedMessage::MegolmV1AesSha2 { cause, .. } => Some(*cause),
557 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } | EncryptedMessage::Unknown => None,
558 }
559 }
560}
561
562#[derive(Clone, Debug)]
564pub struct Sticker {
565 pub(in crate::timeline) content: StickerEventContent,
566}
567
568impl Sticker {
569 pub fn content(&self) -> &StickerEventContent {
571 &self.content
572 }
573}
574
575#[derive(Clone, Debug)]
577pub struct RoomMembershipChange {
578 pub(in crate::timeline) user_id: OwnedUserId,
579 pub(in crate::timeline) content: StateEventContentChange<RoomMemberEventContent>,
580 pub(in crate::timeline) change: Option<MembershipChange>,
581}
582
583impl RoomMembershipChange {
584 pub fn user_id(&self) -> &UserId {
586 &self.user_id
587 }
588
589 pub fn content(&self) -> &StateEventContentChange<RoomMemberEventContent> {
591 &self.content
592 }
593
594 pub fn display_name(&self) -> Option<String> {
597 if let StateEventContentChange::Original { content, prev_content } = &self.content {
598 content
599 .displayname
600 .as_ref()
601 .or_else(|| {
602 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
603 })
604 .cloned()
605 } else {
606 None
607 }
608 }
609
610 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
613 if let StateEventContentChange::Original { content, prev_content } = &self.content {
614 content
615 .avatar_url
616 .as_ref()
617 .or_else(|| {
618 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
619 })
620 .cloned()
621 } else {
622 None
623 }
624 }
625
626 pub fn change(&self) -> Option<MembershipChange> {
634 self.change
635 }
636
637 fn redact(&self, rules: &RedactionRules) -> Self {
638 Self {
639 user_id: self.user_id.clone(),
640 content: StateEventContentChange::Redacted(self.content.clone().redact(rules)),
641 change: self.change,
642 }
643 }
644}
645
646#[derive(Clone, Copy, Debug, PartialEq, Eq)]
648pub enum MembershipChange {
649 None,
651
652 Error,
654
655 Joined,
657
658 Left,
660
661 Banned,
663
664 Unbanned,
666
667 Kicked,
669
670 Invited,
672
673 KickedAndBanned,
675
676 InvitationAccepted,
678
679 InvitationRejected,
681
682 InvitationRevoked,
684
685 Knocked,
687
688 KnockAccepted,
690
691 KnockRetracted,
693
694 KnockDenied,
696
697 NotImplemented,
699}
700
701#[derive(Clone, Debug)]
706pub struct MemberProfileChange {
707 pub(in crate::timeline) user_id: OwnedUserId,
708 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
709 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
710}
711
712impl MemberProfileChange {
713 pub fn user_id(&self) -> &UserId {
715 &self.user_id
716 }
717
718 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
720 self.displayname_change.as_ref()
721 }
722
723 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
725 self.avatar_url_change.as_ref()
726 }
727
728 fn redact(&self) -> Self {
729 Self {
730 user_id: self.user_id.clone(),
731 displayname_change: None,
736 avatar_url_change: None,
737 }
738 }
739}
740
741#[derive(Clone, Debug)]
744pub enum AnyOtherStateEventContentChange {
745 PolicyRuleRoom(StateEventContentChange<PolicyRuleRoomEventContent>),
747
748 PolicyRuleServer(StateEventContentChange<PolicyRuleServerEventContent>),
750
751 PolicyRuleUser(StateEventContentChange<PolicyRuleUserEventContent>),
753
754 RoomAvatar(StateEventContentChange<RoomAvatarEventContent>),
756
757 RoomCanonicalAlias(StateEventContentChange<RoomCanonicalAliasEventContent>),
759
760 RoomCreate(StateEventContentChange<RoomCreateEventContent>),
762
763 RoomEncryption(StateEventContentChange<RoomEncryptionEventContent>),
765
766 RoomGuestAccess(StateEventContentChange<RoomGuestAccessEventContent>),
768
769 RoomHistoryVisibility(StateEventContentChange<RoomHistoryVisibilityEventContent>),
771
772 RoomJoinRules(StateEventContentChange<RoomJoinRulesEventContent>),
774
775 RoomName(StateEventContentChange<RoomNameEventContent>),
777
778 RoomPinnedEvents(StateEventContentChange<RoomPinnedEventsEventContent>),
780
781 RoomPowerLevels(StateEventContentChange<RoomPowerLevelsEventContent>),
783
784 RoomServerAcl(StateEventContentChange<RoomServerAclEventContent>),
786
787 RoomThirdPartyInvite(StateEventContentChange<RoomThirdPartyInviteEventContent>),
789
790 RoomTombstone(StateEventContentChange<RoomTombstoneEventContent>),
792
793 RoomTopic(StateEventContentChange<RoomTopicEventContent>),
795
796 SpaceChild(StateEventContentChange<SpaceChildEventContent>),
798
799 SpaceParent(StateEventContentChange<SpaceParentEventContent>),
801
802 #[doc(hidden)]
803 _Custom { event_type: String },
804}
805
806impl AnyOtherStateEventContentChange {
807 pub(crate) fn with_event_content(content: AnyStateEventContentChange) -> Self {
814 let event_type = content.event_type();
815
816 match content {
817 AnyStateEventContentChange::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
818 AnyStateEventContentChange::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
819 AnyStateEventContentChange::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
820 AnyStateEventContentChange::RoomAvatar(c) => Self::RoomAvatar(c),
821 AnyStateEventContentChange::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
822 AnyStateEventContentChange::RoomCreate(c) => Self::RoomCreate(c),
823 AnyStateEventContentChange::RoomEncryption(c) => Self::RoomEncryption(c),
824 AnyStateEventContentChange::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
825 AnyStateEventContentChange::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
826 AnyStateEventContentChange::RoomJoinRules(c) => Self::RoomJoinRules(c),
827 AnyStateEventContentChange::RoomName(c) => Self::RoomName(c),
828 AnyStateEventContentChange::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
829 AnyStateEventContentChange::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
830 AnyStateEventContentChange::RoomServerAcl(c) => Self::RoomServerAcl(c),
831 AnyStateEventContentChange::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
832 AnyStateEventContentChange::RoomTombstone(c) => Self::RoomTombstone(c),
833 AnyStateEventContentChange::RoomTopic(c) => Self::RoomTopic(c),
834 AnyStateEventContentChange::SpaceChild(c) => Self::SpaceChild(c),
835 AnyStateEventContentChange::SpaceParent(c) => Self::SpaceParent(c),
836 AnyStateEventContentChange::RoomMember(_) => unreachable!(),
837 _ => Self::_Custom { event_type: event_type.to_string() },
838 }
839 }
840
841 pub fn event_type(&self) -> StateEventType {
843 match self {
844 Self::PolicyRuleRoom(c) => c.event_type(),
845 Self::PolicyRuleServer(c) => c.event_type(),
846 Self::PolicyRuleUser(c) => c.event_type(),
847 Self::RoomAvatar(c) => c.event_type(),
848 Self::RoomCanonicalAlias(c) => c.event_type(),
849 Self::RoomCreate(c) => c.event_type(),
850 Self::RoomEncryption(c) => c.event_type(),
851 Self::RoomGuestAccess(c) => c.event_type(),
852 Self::RoomHistoryVisibility(c) => c.event_type(),
853 Self::RoomJoinRules(c) => c.event_type(),
854 Self::RoomName(c) => c.event_type(),
855 Self::RoomPinnedEvents(c) => c.event_type(),
856 Self::RoomPowerLevels(c) => c.event_type(),
857 Self::RoomServerAcl(c) => c.event_type(),
858 Self::RoomThirdPartyInvite(c) => c.event_type(),
859 Self::RoomTombstone(c) => c.event_type(),
860 Self::RoomTopic(c) => c.event_type(),
861 Self::SpaceChild(c) => c.event_type(),
862 Self::SpaceParent(c) => c.event_type(),
863 Self::_Custom { event_type } => event_type.as_str().into(),
864 }
865 }
866
867 fn redact(&self, rules: &RedactionRules) -> Self {
868 match self {
869 Self::PolicyRuleRoom(c) => {
870 Self::PolicyRuleRoom(StateEventContentChange::Redacted(c.clone().redact(rules)))
871 }
872 Self::PolicyRuleServer(c) => {
873 Self::PolicyRuleServer(StateEventContentChange::Redacted(c.clone().redact(rules)))
874 }
875 Self::PolicyRuleUser(c) => {
876 Self::PolicyRuleUser(StateEventContentChange::Redacted(c.clone().redact(rules)))
877 }
878 Self::RoomAvatar(c) => {
879 Self::RoomAvatar(StateEventContentChange::Redacted(c.clone().redact(rules)))
880 }
881 Self::RoomCanonicalAlias(c) => {
882 Self::RoomCanonicalAlias(StateEventContentChange::Redacted(c.clone().redact(rules)))
883 }
884 Self::RoomCreate(c) => {
885 Self::RoomCreate(StateEventContentChange::Redacted(c.clone().redact(rules)))
886 }
887 Self::RoomEncryption(c) => {
888 Self::RoomEncryption(StateEventContentChange::Redacted(c.clone().redact(rules)))
889 }
890 Self::RoomGuestAccess(c) => {
891 Self::RoomGuestAccess(StateEventContentChange::Redacted(c.clone().redact(rules)))
892 }
893 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
894 StateEventContentChange::Redacted(c.clone().redact(rules)),
895 ),
896 Self::RoomJoinRules(c) => {
897 Self::RoomJoinRules(StateEventContentChange::Redacted(c.clone().redact(rules)))
898 }
899 Self::RoomName(c) => {
900 Self::RoomName(StateEventContentChange::Redacted(c.clone().redact(rules)))
901 }
902 Self::RoomPinnedEvents(c) => {
903 Self::RoomPinnedEvents(StateEventContentChange::Redacted(c.clone().redact(rules)))
904 }
905 Self::RoomPowerLevels(c) => {
906 Self::RoomPowerLevels(StateEventContentChange::Redacted(c.clone().redact(rules)))
907 }
908 Self::RoomServerAcl(c) => {
909 Self::RoomServerAcl(StateEventContentChange::Redacted(c.clone().redact(rules)))
910 }
911 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
912 StateEventContentChange::Redacted(c.clone().redact(rules)),
913 ),
914 Self::RoomTombstone(c) => {
915 Self::RoomTombstone(StateEventContentChange::Redacted(c.clone().redact(rules)))
916 }
917 Self::RoomTopic(c) => {
918 Self::RoomTopic(StateEventContentChange::Redacted(c.clone().redact(rules)))
919 }
920 Self::SpaceChild(c) => {
921 Self::SpaceChild(StateEventContentChange::Redacted(c.clone().redact(rules)))
922 }
923 Self::SpaceParent(c) => {
924 Self::SpaceParent(StateEventContentChange::Redacted(c.clone().redact(rules)))
925 }
926 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
927 }
928 }
929}
930
931#[derive(Clone, Debug)]
933pub struct OtherState {
934 pub(in crate::timeline) state_key: String,
935 pub(in crate::timeline) content: AnyOtherStateEventContentChange,
936}
937
938impl OtherState {
939 pub fn state_key(&self) -> &str {
941 &self.state_key
942 }
943
944 pub fn content(&self) -> &AnyOtherStateEventContentChange {
946 &self.content
947 }
948
949 fn redact(&self, rules: &RedactionRules) -> Self {
950 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
951 }
952}
953
954#[cfg(test)]
955mod tests {
956 use matrix_sdk_base::crypto::types::events::UtdCause;
957 use matrix_sdk_test::ALICE;
958 use ruma::{
959 assign,
960 events::{
961 StateEventContentChange,
962 room::member::{
963 MembershipState, PossiblyRedactedRoomMemberEventContent, RoomMemberEventContent,
964 },
965 },
966 room_version_rules::RedactionRules,
967 };
968 use strass::assert_let;
969
970 use super::{EncryptedMessage, MembershipChange, RoomMembershipChange, TimelineItemContent};
971
972 #[test]
973 fn utd_cause_only_available_for_megolm() {
974 let olm = EncryptedMessage::OlmV1Curve25519AesSha2 { sender_key: "key".to_owned() };
975 assert_eq!(olm.utd_cause(), None);
976 assert_eq!(EncryptedMessage::Unknown.utd_cause(), None);
977
978 let megolm = EncryptedMessage::MegolmV1AesSha2 {
979 sender_key: None,
980 device_id: None,
981 session_id: "session".to_owned(),
982 cause: UtdCause::SentBeforeWeJoined,
983 };
984 assert_eq!(megolm.utd_cause(), Some(UtdCause::SentBeforeWeJoined));
985 }
986
987 #[test]
988 fn redact_membership_change() {
989 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
990 user_id: ALICE.to_owned(),
991 content: StateEventContentChange::Original {
992 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
993 reason: Some("🤬".to_owned()),
994 }),
995 prev_content: Some(PossiblyRedactedRoomMemberEventContent::new(
996 MembershipState::Join,
997 )),
998 },
999 change: Some(MembershipChange::Banned),
1000 });
1001
1002 let redacted = content.redact(&RedactionRules::V11);
1003 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1004 assert_eq!(inner.change, Some(MembershipChange::Banned));
1005 assert_let!(StateEventContentChange::Redacted(inner_content_redacted) = inner.content);
1006 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1007 }
1008}