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 super::ReactionsByKeyBySender;
89use crate::timeline::{
90 controller::ActiveCallInfo,
91 event_handler::{HandleAggregationKind, TimelineAction},
92};
93
94#[allow(clippy::large_enum_variant)]
96#[derive(Clone, Debug)]
97pub enum TimelineItemContent {
98 MsgLike(MsgLikeContent),
99
100 MembershipChange(RoomMembershipChange),
102
103 ProfileChange(MemberProfileChange),
105
106 OtherState(OtherState),
108
109 FailedToParseMessageLike {
111 event_type: MessageLikeEventType,
113
114 error: Arc<serde_json::Error>,
116 },
117
118 FailedToParseState {
120 event_type: StateEventType,
122
123 state_key: String,
125
126 error: Arc<serde_json::Error>,
128 },
129
130 CallInvite,
132
133 RtcNotification {
135 call_intent: Option<CallIntent>,
137 declined_by: Vec<OwnedUserId>,
139 active_call_info: Option<ActiveCallInfo>,
142 },
143}
144
145impl TimelineItemContent {
146 pub fn event_type_str(&self) -> Option<String> {
150 match self {
151 Self::MsgLike(msg) => Some(match &msg.kind {
152 MsgLikeKind::Message(_) => MessageLikeEventType::RoomMessage.to_string(),
153 MsgLikeKind::Sticker(_) => MessageLikeEventType::Sticker.to_string(),
154 MsgLikeKind::Poll(_) => MessageLikeEventType::PollStart.to_string(),
155 MsgLikeKind::Redacted => return None,
156 MsgLikeKind::UnableToDecrypt(_) => MessageLikeEventType::RoomEncrypted.to_string(),
157 MsgLikeKind::Other(other) => other.event_type().to_string(),
158 MsgLikeKind::LiveLocation(_) => StateEventType::BeaconInfo.to_string(),
159 }),
160 Self::MembershipChange(_) | Self::ProfileChange(_) => {
161 Some(StateEventType::RoomMember.to_string())
162 }
163 Self::OtherState(state) => Some(state.content().event_type().to_string()),
164 Self::FailedToParseMessageLike { event_type, .. } => Some(event_type.to_string()),
165 Self::FailedToParseState { event_type, .. } => Some(event_type.to_string()),
166 Self::CallInvite => Some(MessageLikeEventType::CallInvite.to_string()),
167 Self::RtcNotification { .. } => Some(MessageLikeEventType::RtcNotification.to_string()),
168 }
169 }
170
171 pub async fn from_event(room: &Room, timeline_event: TimelineEvent) -> Option<Self> {
175 let (utd_info, raw_event) = match timeline_event.kind {
176 TimelineEventKind::UnableToDecrypt { utd_info, event } => (Some(utd_info), event),
177 _ => (None, timeline_event.into_raw()),
178 };
179
180 let deserialized_event = raw_event.deserialize().ok()?;
181
182 let actions = TimelineAction::from_event(
183 deserialized_event,
184 &raw_event,
185 room,
186 utd_info.map(|utd_info| (utd_info, None)),
187 None,
188 None,
189 None,
190 )
191 .await;
192 match actions.as_slice() {
193 [TimelineAction::AddItem { content }] => Some(content.clone()),
194 [
195 TimelineAction::AddItem { content },
196 TimelineAction::HandleAggregation {
197 kind: HandleAggregationKind::BeaconStop { .. },
198 ..
199 },
200 ] => {
201 if content.is_live_location_state() {
202 Some(content.clone())
203 } else {
204 warn!(
205 "Unexpected [AddItem, BeaconStop] actions with a non-live-location \
206 AddItem; ignoring event"
207 );
208 None
209 }
210 }
211 [
213 TimelineAction::HandleAggregation {
214 kind: HandleAggregationKind::BeaconStop { content, .. },
215 ..
216 },
217 ] => Some(TimelineItemContent::MsgLike(MsgLikeContent {
218 kind: MsgLikeKind::LiveLocation(LiveLocationState::new(content.clone())),
219 reactions: Default::default(),
220 thread_root: None,
221 in_reply_to: None,
222 thread_summary: None,
223 })),
224 [
225 TimelineAction::HandleAggregation {
226 kind:
227 HandleAggregationKind::Edit { replacement: Replacement { new_content, .. } },
228 ..
229 },
230 ] => {
231 match TimelineAction::from_content(
233 AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::new(
234 new_content.msgtype.clone(),
235 )),
236 None,
237 None,
238 None,
239 ) {
240 TimelineAction::AddItem { content } => Some(content),
241 _ => None,
242 }
243 }
244 [] => {
245 warn!("No action for event content processing");
246 None
247 }
248 [_, _, ..] => {
249 warn!("Ignoring event that produced multiple timeline actions");
251 None
252 }
253 _ => None,
254 }
255 }
256
257 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
258 as_variant!(self, TimelineItemContent::MsgLike)
259 }
260
261 pub fn as_live_location_state(&self) -> Option<&LiveLocationState> {
265 as_variant!(self, Self::MsgLike(MsgLikeContent {
266 kind: MsgLikeKind::LiveLocation(state),
267 ..
268 }) => state)
269 }
270
271 pub fn is_live_location_state(&self) -> bool {
274 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::LiveLocation(_), .. }))
275 }
276
277 pub fn as_message(&self) -> Option<&Message> {
280 as_variant!(self, Self::MsgLike(MsgLikeContent {
281 kind: MsgLikeKind::Message(message),
282 ..
283 }) => message)
284 }
285
286 pub fn is_message(&self) -> bool {
289 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
290 }
291
292 pub fn as_poll(&self) -> Option<&PollState> {
295 as_variant!(self, Self::MsgLike(MsgLikeContent {
296 kind: MsgLikeKind::Poll(poll_state),
297 ..
298 }) => poll_state)
299 }
300
301 pub fn is_poll(&self) -> bool {
304 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
305 }
306
307 pub fn as_sticker(&self) -> Option<&Sticker> {
308 as_variant!(
309 self,
310 Self::MsgLike(MsgLikeContent {
311 kind: MsgLikeKind::Sticker(sticker),
312 ..
313 }) => sticker
314 )
315 }
316
317 pub fn is_sticker(&self) -> bool {
320 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
321 }
322
323 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
326 as_variant!(
327 self,
328 Self::MsgLike(MsgLikeContent {
329 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
330 ..
331 }) => encrypted_message
332 )
333 }
334
335 pub fn is_unable_to_decrypt(&self) -> bool {
338 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
339 }
340
341 pub fn is_redacted(&self) -> bool {
342 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
343 }
344
345 pub(crate) fn message(
348 msgtype: MessageType,
349 mentions: Option<Mentions>,
350 reactions: ReactionsByKeyBySender,
351 thread_root: Option<OwnedEventId>,
352 in_reply_to: Option<InReplyToDetails>,
353 thread_summary: Option<ThreadSummary>,
354 ) -> Self {
355 let remove_reply_fallback =
356 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
357
358 Self::MsgLike(MsgLikeContent {
359 kind: MsgLikeKind::Message(Message::from_event(
360 msgtype,
361 mentions,
362 None,
363 remove_reply_fallback,
364 )),
365 reactions,
366 thread_root,
367 in_reply_to,
368 thread_summary,
369 })
370 }
371
372 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
374 match self {
375 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
376 TimelineItemContent::MembershipChange(_) => "a membership change",
377 TimelineItemContent::ProfileChange(_) => "a profile change",
378 TimelineItemContent::OtherState(_) => "a state event",
379 TimelineItemContent::FailedToParseMessageLike { .. }
380 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
381 TimelineItemContent::CallInvite => "a call invite",
382 TimelineItemContent::RtcNotification { .. } => "a call notification",
383 }
384 }
385
386 pub(crate) fn room_member(
387 user_id: OwnedUserId,
388 full_content: StateEventContentChange<RoomMemberEventContent>,
389 sender: OwnedUserId,
390 ) -> Self {
391 use ruma::events::room::member::MembershipChange as MChange;
392 match &full_content {
393 StateEventContentChange::Original { content, prev_content } => {
394 let membership_change = content.membership_change(
395 prev_content.as_ref().map(|c| c.details()),
396 &sender,
397 &user_id,
398 );
399
400 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
401 membership_change
402 {
403 Self::ProfileChange(MemberProfileChange {
404 user_id,
405 displayname_change: displayname_change.map(|c| Change {
406 new: c.new.map(ToOwned::to_owned),
407 old: c.old.map(ToOwned::to_owned),
408 }),
409 avatar_url_change: avatar_url_change.map(|c| Change {
410 new: c.new.map(ToOwned::to_owned),
411 old: c.old.map(ToOwned::to_owned),
412 }),
413 })
414 } else {
415 let change = match membership_change {
416 MChange::None => MembershipChange::None,
417 MChange::Error => MembershipChange::Error,
418 MChange::Joined => MembershipChange::Joined,
419 MChange::Left => MembershipChange::Left,
420 MChange::Banned => MembershipChange::Banned,
421 MChange::Unbanned => MembershipChange::Unbanned,
422 MChange::Kicked => MembershipChange::Kicked,
423 MChange::Invited => MembershipChange::Invited,
424 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
425 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
426 MChange::InvitationRejected => MembershipChange::InvitationRejected,
427 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
428 MChange::Knocked => MembershipChange::Knocked,
429 MChange::KnockAccepted => MembershipChange::KnockAccepted,
430 MChange::KnockRetracted => MembershipChange::KnockRetracted,
431 MChange::KnockDenied => MembershipChange::KnockDenied,
432 MChange::ProfileChanged { .. } => unreachable!(),
433 _ => MembershipChange::NotImplemented,
434 };
435
436 Self::MembershipChange(RoomMembershipChange {
437 user_id,
438 content: full_content,
439 change: Some(change),
440 })
441 }
442 }
443 StateEventContentChange::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
444 user_id,
445 content: full_content,
446 change: None,
447 }),
448 }
449 }
450
451 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
452 match self {
453 Self::MsgLike(msglike) => TimelineItemContent::MsgLike(MsgLikeContent {
454 kind: MsgLikeKind::Redacted,
455 reactions: Default::default(),
456 in_reply_to: None,
457 ..msglike.clone()
458 }),
459 Self::CallInvite | Self::RtcNotification { .. } => {
460 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
461 }
462 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
463 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
464 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
465 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
466 }
467 }
468
469 pub fn thread_root(&self) -> Option<OwnedEventId> {
471 as_variant!(self, Self::MsgLike)?.thread_root.clone()
472 }
473
474 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
476 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
477 }
478
479 pub(crate) fn relation(&self) -> Option<Relation<RoomMessageEventContentWithoutRelation>> {
482 if let Some(thread_root) = self.thread_root() {
483 Some(Relation::Thread(match self.in_reply_to() {
484 Some(details) => Thread::reply(thread_root, details.event_id),
485 None => Thread::plain(thread_root.clone(), thread_root),
486 }))
487 } else {
488 self.in_reply_to()
489 .map(|details| Relation::Reply(Reply::with_event_id(details.event_id)))
490 }
491 }
492
493 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
496 match self {
497 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
498
499 TimelineItemContent::MembershipChange(..)
500 | TimelineItemContent::ProfileChange(..)
501 | TimelineItemContent::OtherState(..)
502 | TimelineItemContent::FailedToParseMessageLike { .. }
503 | TimelineItemContent::FailedToParseState { .. }
504 | TimelineItemContent::CallInvite
505 | TimelineItemContent::RtcNotification { .. } => {
506 None
508 }
509 }
510 }
511
512 pub fn thread_summary(&self) -> Option<ThreadSummary> {
514 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
515 }
516
517 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
521 match self {
522 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
523
524 TimelineItemContent::MembershipChange(..)
525 | TimelineItemContent::ProfileChange(..)
526 | TimelineItemContent::OtherState(..)
527 | TimelineItemContent::FailedToParseMessageLike { .. }
528 | TimelineItemContent::FailedToParseState { .. }
529 | TimelineItemContent::CallInvite
530 | TimelineItemContent::RtcNotification { .. } => {
531 None
533 }
534 }
535 }
536
537 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
538 let mut cloned = self.clone();
539 if let Some(r) = cloned.reactions_mut() {
540 *r = reactions;
541 }
542 cloned
543 }
544}
545
546#[derive(Clone, Debug)]
548pub enum EncryptedMessage {
549 OlmV1Curve25519AesSha2 {
552 sender_key: String,
554 },
555 MegolmV1AesSha2 {
557 #[deprecated = "this field should still be sent but should not be used when received"]
559 #[doc(hidden)] sender_key: Option<String>,
561
562 #[deprecated = "this field should still be sent but should not be used when received"]
564 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
566
567 session_id: String,
569
570 cause: UtdCause,
573 },
574 Unknown,
576}
577
578impl EncryptedMessage {
579 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
580 match content.scheme {
581 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
582 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
583 }
584 #[allow(deprecated)]
585 EncryptedEventScheme::MegolmV1AesSha2(s) => {
586 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
587
588 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
589 }
590 _ => Self::Unknown,
591 }
592 }
593
594 pub(crate) fn session_id(&self) -> Option<&str> {
597 match self {
598 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
599 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
600 EncryptedMessage::Unknown => None,
601 }
602 }
603}
604
605#[derive(Clone, Debug)]
607pub struct Sticker {
608 pub(in crate::timeline) content: StickerEventContent,
609}
610
611impl Sticker {
612 pub fn content(&self) -> &StickerEventContent {
614 &self.content
615 }
616}
617
618#[derive(Clone, Debug)]
620pub struct RoomMembershipChange {
621 pub(in crate::timeline) user_id: OwnedUserId,
622 pub(in crate::timeline) content: StateEventContentChange<RoomMemberEventContent>,
623 pub(in crate::timeline) change: Option<MembershipChange>,
624}
625
626impl RoomMembershipChange {
627 pub fn user_id(&self) -> &UserId {
629 &self.user_id
630 }
631
632 pub fn content(&self) -> &StateEventContentChange<RoomMemberEventContent> {
634 &self.content
635 }
636
637 pub fn display_name(&self) -> Option<String> {
640 if let StateEventContentChange::Original { content, prev_content } = &self.content {
641 content
642 .displayname
643 .as_ref()
644 .or_else(|| {
645 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
646 })
647 .cloned()
648 } else {
649 None
650 }
651 }
652
653 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
656 if let StateEventContentChange::Original { content, prev_content } = &self.content {
657 content
658 .avatar_url
659 .as_ref()
660 .or_else(|| {
661 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
662 })
663 .cloned()
664 } else {
665 None
666 }
667 }
668
669 pub fn change(&self) -> Option<MembershipChange> {
677 self.change
678 }
679
680 fn redact(&self, rules: &RedactionRules) -> Self {
681 Self {
682 user_id: self.user_id.clone(),
683 content: StateEventContentChange::Redacted(self.content.clone().redact(rules)),
684 change: self.change,
685 }
686 }
687}
688
689#[derive(Clone, Copy, Debug, PartialEq, Eq)]
691pub enum MembershipChange {
692 None,
694
695 Error,
697
698 Joined,
700
701 Left,
703
704 Banned,
706
707 Unbanned,
709
710 Kicked,
712
713 Invited,
715
716 KickedAndBanned,
718
719 InvitationAccepted,
721
722 InvitationRejected,
724
725 InvitationRevoked,
727
728 Knocked,
730
731 KnockAccepted,
733
734 KnockRetracted,
736
737 KnockDenied,
739
740 NotImplemented,
742}
743
744#[derive(Clone, Debug)]
749pub struct MemberProfileChange {
750 pub(in crate::timeline) user_id: OwnedUserId,
751 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
752 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
753}
754
755impl MemberProfileChange {
756 pub fn user_id(&self) -> &UserId {
758 &self.user_id
759 }
760
761 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
763 self.displayname_change.as_ref()
764 }
765
766 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
768 self.avatar_url_change.as_ref()
769 }
770
771 fn redact(&self) -> Self {
772 Self {
773 user_id: self.user_id.clone(),
774 displayname_change: None,
779 avatar_url_change: None,
780 }
781 }
782}
783
784#[derive(Clone, Debug)]
787pub enum AnyOtherStateEventContentChange {
788 PolicyRuleRoom(StateEventContentChange<PolicyRuleRoomEventContent>),
790
791 PolicyRuleServer(StateEventContentChange<PolicyRuleServerEventContent>),
793
794 PolicyRuleUser(StateEventContentChange<PolicyRuleUserEventContent>),
796
797 RoomAvatar(StateEventContentChange<RoomAvatarEventContent>),
799
800 RoomCanonicalAlias(StateEventContentChange<RoomCanonicalAliasEventContent>),
802
803 RoomCreate(StateEventContentChange<RoomCreateEventContent>),
805
806 RoomEncryption(StateEventContentChange<RoomEncryptionEventContent>),
808
809 RoomGuestAccess(StateEventContentChange<RoomGuestAccessEventContent>),
811
812 RoomHistoryVisibility(StateEventContentChange<RoomHistoryVisibilityEventContent>),
814
815 RoomJoinRules(StateEventContentChange<RoomJoinRulesEventContent>),
817
818 RoomName(StateEventContentChange<RoomNameEventContent>),
820
821 RoomPinnedEvents(StateEventContentChange<RoomPinnedEventsEventContent>),
823
824 RoomPowerLevels(StateEventContentChange<RoomPowerLevelsEventContent>),
826
827 RoomServerAcl(StateEventContentChange<RoomServerAclEventContent>),
829
830 RoomThirdPartyInvite(StateEventContentChange<RoomThirdPartyInviteEventContent>),
832
833 RoomTombstone(StateEventContentChange<RoomTombstoneEventContent>),
835
836 RoomTopic(StateEventContentChange<RoomTopicEventContent>),
838
839 SpaceChild(StateEventContentChange<SpaceChildEventContent>),
841
842 SpaceParent(StateEventContentChange<SpaceParentEventContent>),
844
845 #[doc(hidden)]
846 _Custom { event_type: String },
847}
848
849impl AnyOtherStateEventContentChange {
850 pub(crate) fn with_event_content(content: AnyStateEventContentChange) -> Self {
856 let event_type = content.event_type();
857
858 match content {
859 AnyStateEventContentChange::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
860 AnyStateEventContentChange::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
861 AnyStateEventContentChange::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
862 AnyStateEventContentChange::RoomAvatar(c) => Self::RoomAvatar(c),
863 AnyStateEventContentChange::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
864 AnyStateEventContentChange::RoomCreate(c) => Self::RoomCreate(c),
865 AnyStateEventContentChange::RoomEncryption(c) => Self::RoomEncryption(c),
866 AnyStateEventContentChange::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
867 AnyStateEventContentChange::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
868 AnyStateEventContentChange::RoomJoinRules(c) => Self::RoomJoinRules(c),
869 AnyStateEventContentChange::RoomName(c) => Self::RoomName(c),
870 AnyStateEventContentChange::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
871 AnyStateEventContentChange::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
872 AnyStateEventContentChange::RoomServerAcl(c) => Self::RoomServerAcl(c),
873 AnyStateEventContentChange::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
874 AnyStateEventContentChange::RoomTombstone(c) => Self::RoomTombstone(c),
875 AnyStateEventContentChange::RoomTopic(c) => Self::RoomTopic(c),
876 AnyStateEventContentChange::SpaceChild(c) => Self::SpaceChild(c),
877 AnyStateEventContentChange::SpaceParent(c) => Self::SpaceParent(c),
878 AnyStateEventContentChange::RoomMember(_) => unreachable!(),
879 _ => Self::_Custom { event_type: event_type.to_string() },
880 }
881 }
882
883 pub fn event_type(&self) -> StateEventType {
885 match self {
886 Self::PolicyRuleRoom(c) => c.event_type(),
887 Self::PolicyRuleServer(c) => c.event_type(),
888 Self::PolicyRuleUser(c) => c.event_type(),
889 Self::RoomAvatar(c) => c.event_type(),
890 Self::RoomCanonicalAlias(c) => c.event_type(),
891 Self::RoomCreate(c) => c.event_type(),
892 Self::RoomEncryption(c) => c.event_type(),
893 Self::RoomGuestAccess(c) => c.event_type(),
894 Self::RoomHistoryVisibility(c) => c.event_type(),
895 Self::RoomJoinRules(c) => c.event_type(),
896 Self::RoomName(c) => c.event_type(),
897 Self::RoomPinnedEvents(c) => c.event_type(),
898 Self::RoomPowerLevels(c) => c.event_type(),
899 Self::RoomServerAcl(c) => c.event_type(),
900 Self::RoomThirdPartyInvite(c) => c.event_type(),
901 Self::RoomTombstone(c) => c.event_type(),
902 Self::RoomTopic(c) => c.event_type(),
903 Self::SpaceChild(c) => c.event_type(),
904 Self::SpaceParent(c) => c.event_type(),
905 Self::_Custom { event_type } => event_type.as_str().into(),
906 }
907 }
908
909 fn redact(&self, rules: &RedactionRules) -> Self {
910 match self {
911 Self::PolicyRuleRoom(c) => {
912 Self::PolicyRuleRoom(StateEventContentChange::Redacted(c.clone().redact(rules)))
913 }
914 Self::PolicyRuleServer(c) => {
915 Self::PolicyRuleServer(StateEventContentChange::Redacted(c.clone().redact(rules)))
916 }
917 Self::PolicyRuleUser(c) => {
918 Self::PolicyRuleUser(StateEventContentChange::Redacted(c.clone().redact(rules)))
919 }
920 Self::RoomAvatar(c) => {
921 Self::RoomAvatar(StateEventContentChange::Redacted(c.clone().redact(rules)))
922 }
923 Self::RoomCanonicalAlias(c) => {
924 Self::RoomCanonicalAlias(StateEventContentChange::Redacted(c.clone().redact(rules)))
925 }
926 Self::RoomCreate(c) => {
927 Self::RoomCreate(StateEventContentChange::Redacted(c.clone().redact(rules)))
928 }
929 Self::RoomEncryption(c) => {
930 Self::RoomEncryption(StateEventContentChange::Redacted(c.clone().redact(rules)))
931 }
932 Self::RoomGuestAccess(c) => {
933 Self::RoomGuestAccess(StateEventContentChange::Redacted(c.clone().redact(rules)))
934 }
935 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
936 StateEventContentChange::Redacted(c.clone().redact(rules)),
937 ),
938 Self::RoomJoinRules(c) => {
939 Self::RoomJoinRules(StateEventContentChange::Redacted(c.clone().redact(rules)))
940 }
941 Self::RoomName(c) => {
942 Self::RoomName(StateEventContentChange::Redacted(c.clone().redact(rules)))
943 }
944 Self::RoomPinnedEvents(c) => {
945 Self::RoomPinnedEvents(StateEventContentChange::Redacted(c.clone().redact(rules)))
946 }
947 Self::RoomPowerLevels(c) => {
948 Self::RoomPowerLevels(StateEventContentChange::Redacted(c.clone().redact(rules)))
949 }
950 Self::RoomServerAcl(c) => {
951 Self::RoomServerAcl(StateEventContentChange::Redacted(c.clone().redact(rules)))
952 }
953 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
954 StateEventContentChange::Redacted(c.clone().redact(rules)),
955 ),
956 Self::RoomTombstone(c) => {
957 Self::RoomTombstone(StateEventContentChange::Redacted(c.clone().redact(rules)))
958 }
959 Self::RoomTopic(c) => {
960 Self::RoomTopic(StateEventContentChange::Redacted(c.clone().redact(rules)))
961 }
962 Self::SpaceChild(c) => {
963 Self::SpaceChild(StateEventContentChange::Redacted(c.clone().redact(rules)))
964 }
965 Self::SpaceParent(c) => {
966 Self::SpaceParent(StateEventContentChange::Redacted(c.clone().redact(rules)))
967 }
968 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
969 }
970 }
971}
972
973#[derive(Clone, Debug)]
975pub struct OtherState {
976 pub(in crate::timeline) state_key: String,
977 pub(in crate::timeline) content: AnyOtherStateEventContentChange,
978}
979
980impl OtherState {
981 pub fn state_key(&self) -> &str {
983 &self.state_key
984 }
985
986 pub fn content(&self) -> &AnyOtherStateEventContentChange {
988 &self.content
989 }
990
991 fn redact(&self, rules: &RedactionRules) -> Self {
992 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
993 }
994}
995
996#[cfg(test)]
997mod tests {
998 use assert_matches2::assert_let;
999 use matrix_sdk_test::ALICE;
1000 use ruma::{
1001 assign,
1002 events::{
1003 StateEventContentChange,
1004 room::member::{
1005 MembershipState, PossiblyRedactedRoomMemberEventContent, RoomMemberEventContent,
1006 },
1007 },
1008 room_version_rules::RedactionRules,
1009 };
1010
1011 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1012
1013 #[test]
1014 fn redact_membership_change() {
1015 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1016 user_id: ALICE.to_owned(),
1017 content: StateEventContentChange::Original {
1018 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1019 reason: Some("🤬".to_owned()),
1020 }),
1021 prev_content: Some(PossiblyRedactedRoomMemberEventContent::new(
1022 MembershipState::Join,
1023 )),
1024 },
1025 change: Some(MembershipChange::Banned),
1026 });
1027
1028 let redacted = content.redact(&RedactionRules::V11);
1029 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1030 assert_eq!(inner.change, Some(MembershipChange::Banned));
1031 assert_let!(StateEventContentChange::Redacted(inner_content_redacted) = inner.content);
1032 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1033 }
1034}