1use std::sync::Arc;
16
17use as_variant::as_variant;
18use matrix_sdk_base::crypto::types::events::UtdCause;
19use ruma::{
20 OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, UserId,
21 events::{
22 AnyStateEventContentChange, Mentions, MessageLikeEventType, StateEventContentChange,
23 StateEventType,
24 policy::rule::{
25 room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
26 user::PolicyRuleUserEventContent,
27 },
28 room::{
29 aliases::RoomAliasesEventContent,
30 avatar::RoomAvatarEventContent,
31 canonical_alias::RoomCanonicalAliasEventContent,
32 create::RoomCreateEventContent,
33 encrypted::{EncryptedEventScheme, MegolmV1AesSha2Content, RoomEncryptedEventContent},
34 encryption::RoomEncryptionEventContent,
35 guest_access::RoomGuestAccessEventContent,
36 history_visibility::RoomHistoryVisibilityEventContent,
37 join_rules::RoomJoinRulesEventContent,
38 member::{Change, RoomMemberEventContent},
39 message::MessageType,
40 name::RoomNameEventContent,
41 pinned_events::RoomPinnedEventsEventContent,
42 power_levels::RoomPowerLevelsEventContent,
43 server_acl::RoomServerAclEventContent,
44 third_party_invite::RoomThirdPartyInviteEventContent,
45 tombstone::RoomTombstoneEventContent,
46 topic::RoomTopicEventContent,
47 },
48 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
49 sticker::StickerEventContent,
50 },
51 html::RemoveReplyFallback,
52 room_version_rules::RedactionRules,
53};
54
55mod live_location;
56mod message;
57mod msg_like;
58pub(super) mod other;
59pub(crate) mod pinned_events;
60mod polls;
61mod reply;
62
63pub use pinned_events::RoomPinnedEventsChange;
64
65pub(in crate::timeline) use self::{
66 live_location::beacon_info_matches,
67 message::{
68 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
69 },
70};
71pub use self::{
72 live_location::{BeaconInfo, LiveLocationState},
73 message::Message,
74 msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary},
75 other::OtherMessageLike,
76 polls::{PollResult, PollState},
77 reply::{EmbeddedEvent, InReplyToDetails},
78};
79use super::ReactionsByKeyBySender;
80
81#[allow(clippy::large_enum_variant)]
83#[derive(Clone, Debug)]
84pub enum TimelineItemContent {
85 MsgLike(MsgLikeContent),
86
87 MembershipChange(RoomMembershipChange),
89
90 ProfileChange(MemberProfileChange),
92
93 OtherState(OtherState),
95
96 FailedToParseMessageLike {
98 event_type: MessageLikeEventType,
100
101 error: Arc<serde_json::Error>,
103 },
104
105 FailedToParseState {
107 event_type: StateEventType,
109
110 state_key: String,
112
113 error: Arc<serde_json::Error>,
115 },
116
117 CallInvite,
119
120 RtcNotification,
122}
123
124impl TimelineItemContent {
125 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
126 as_variant!(self, TimelineItemContent::MsgLike)
127 }
128
129 pub fn as_live_location_state(&self) -> Option<&LiveLocationState> {
133 as_variant!(self, Self::MsgLike(MsgLikeContent {
134 kind: MsgLikeKind::LiveLocation(state),
135 ..
136 }) => state)
137 }
138
139 pub(in crate::timeline) fn as_live_location_state_mut(
143 &mut self,
144 ) -> Option<&mut LiveLocationState> {
145 as_variant!(self, Self::MsgLike(MsgLikeContent {
146 kind: MsgLikeKind::LiveLocation(state),
147 ..
148 }) => state)
149 }
150
151 pub fn is_live_location(&self) -> bool {
154 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::LiveLocation(_), .. }))
155 }
156
157 pub fn as_message(&self) -> Option<&Message> {
160 as_variant!(self, Self::MsgLike(MsgLikeContent {
161 kind: MsgLikeKind::Message(message),
162 ..
163 }) => message)
164 }
165
166 pub fn is_message(&self) -> bool {
169 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
170 }
171
172 pub fn as_poll(&self) -> Option<&PollState> {
175 as_variant!(self, Self::MsgLike(MsgLikeContent {
176 kind: MsgLikeKind::Poll(poll_state),
177 ..
178 }) => poll_state)
179 }
180
181 pub fn is_poll(&self) -> bool {
184 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
185 }
186
187 pub fn as_sticker(&self) -> Option<&Sticker> {
188 as_variant!(
189 self,
190 Self::MsgLike(MsgLikeContent {
191 kind: MsgLikeKind::Sticker(sticker),
192 ..
193 }) => sticker
194 )
195 }
196
197 pub fn is_sticker(&self) -> bool {
200 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
201 }
202
203 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
206 as_variant!(
207 self,
208 Self::MsgLike(MsgLikeContent {
209 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
210 ..
211 }) => encrypted_message
212 )
213 }
214
215 pub fn is_unable_to_decrypt(&self) -> bool {
218 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
219 }
220
221 pub fn is_redacted(&self) -> bool {
222 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
223 }
224
225 pub(crate) fn message(
228 msgtype: MessageType,
229 mentions: Option<Mentions>,
230 reactions: ReactionsByKeyBySender,
231 thread_root: Option<OwnedEventId>,
232 in_reply_to: Option<InReplyToDetails>,
233 thread_summary: Option<ThreadSummary>,
234 ) -> Self {
235 let remove_reply_fallback =
236 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
237
238 Self::MsgLike(MsgLikeContent {
239 kind: MsgLikeKind::Message(Message::from_event(
240 msgtype,
241 mentions,
242 None,
243 remove_reply_fallback,
244 )),
245 reactions,
246 thread_root,
247 in_reply_to,
248 thread_summary,
249 })
250 }
251
252 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
254 match self {
255 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
256 TimelineItemContent::MembershipChange(_) => "a membership change",
257 TimelineItemContent::ProfileChange(_) => "a profile change",
258 TimelineItemContent::OtherState(_) => "a state event",
259 TimelineItemContent::FailedToParseMessageLike { .. }
260 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
261 TimelineItemContent::CallInvite => "a call invite",
262 TimelineItemContent::RtcNotification => "a call notification",
263 }
264 }
265
266 pub(crate) fn room_member(
267 user_id: OwnedUserId,
268 full_content: StateEventContentChange<RoomMemberEventContent>,
269 sender: OwnedUserId,
270 ) -> Self {
271 use ruma::events::room::member::MembershipChange as MChange;
272 match &full_content {
273 StateEventContentChange::Original { content, prev_content } => {
274 let membership_change = content.membership_change(
275 prev_content.as_ref().map(|c| c.details()),
276 &sender,
277 &user_id,
278 );
279
280 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
281 membership_change
282 {
283 Self::ProfileChange(MemberProfileChange {
284 user_id,
285 displayname_change: displayname_change.map(|c| Change {
286 new: c.new.map(ToOwned::to_owned),
287 old: c.old.map(ToOwned::to_owned),
288 }),
289 avatar_url_change: avatar_url_change.map(|c| Change {
290 new: c.new.map(ToOwned::to_owned),
291 old: c.old.map(ToOwned::to_owned),
292 }),
293 })
294 } else {
295 let change = match membership_change {
296 MChange::None => MembershipChange::None,
297 MChange::Error => MembershipChange::Error,
298 MChange::Joined => MembershipChange::Joined,
299 MChange::Left => MembershipChange::Left,
300 MChange::Banned => MembershipChange::Banned,
301 MChange::Unbanned => MembershipChange::Unbanned,
302 MChange::Kicked => MembershipChange::Kicked,
303 MChange::Invited => MembershipChange::Invited,
304 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
305 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
306 MChange::InvitationRejected => MembershipChange::InvitationRejected,
307 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
308 MChange::Knocked => MembershipChange::Knocked,
309 MChange::KnockAccepted => MembershipChange::KnockAccepted,
310 MChange::KnockRetracted => MembershipChange::KnockRetracted,
311 MChange::KnockDenied => MembershipChange::KnockDenied,
312 MChange::ProfileChanged { .. } => unreachable!(),
313 _ => MembershipChange::NotImplemented,
314 };
315
316 Self::MembershipChange(RoomMembershipChange {
317 user_id,
318 content: full_content,
319 change: Some(change),
320 })
321 }
322 }
323 StateEventContentChange::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
324 user_id,
325 content: full_content,
326 change: None,
327 }),
328 }
329 }
330
331 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
332 match self {
333 Self::MsgLike(_) | Self::CallInvite | Self::RtcNotification => {
334 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
335 }
336 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
337 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
338 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
339 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
340 }
341 }
342
343 pub fn thread_root(&self) -> Option<OwnedEventId> {
345 as_variant!(self, Self::MsgLike)?.thread_root.clone()
346 }
347
348 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
350 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
351 }
352
353 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
356 match self {
357 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
358
359 TimelineItemContent::MembershipChange(..)
360 | TimelineItemContent::ProfileChange(..)
361 | TimelineItemContent::OtherState(..)
362 | TimelineItemContent::FailedToParseMessageLike { .. }
363 | TimelineItemContent::FailedToParseState { .. }
364 | TimelineItemContent::CallInvite
365 | TimelineItemContent::RtcNotification => {
366 None
368 }
369 }
370 }
371
372 pub fn thread_summary(&self) -> Option<ThreadSummary> {
374 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
375 }
376
377 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
381 match self {
382 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
383
384 TimelineItemContent::MembershipChange(..)
385 | TimelineItemContent::ProfileChange(..)
386 | TimelineItemContent::OtherState(..)
387 | TimelineItemContent::FailedToParseMessageLike { .. }
388 | TimelineItemContent::FailedToParseState { .. }
389 | TimelineItemContent::CallInvite
390 | TimelineItemContent::RtcNotification => {
391 None
393 }
394 }
395 }
396
397 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
398 let mut cloned = self.clone();
399 if let Some(r) = cloned.reactions_mut() {
400 *r = reactions;
401 }
402 cloned
403 }
404}
405
406#[derive(Clone, Debug)]
408pub enum EncryptedMessage {
409 OlmV1Curve25519AesSha2 {
412 sender_key: String,
414 },
415 MegolmV1AesSha2 {
417 #[deprecated = "this field should still be sent but should not be used when received"]
419 #[doc(hidden)] sender_key: Option<String>,
421
422 #[deprecated = "this field should still be sent but should not be used when received"]
424 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
426
427 session_id: String,
429
430 cause: UtdCause,
433 },
434 Unknown,
436}
437
438impl EncryptedMessage {
439 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
440 match content.scheme {
441 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
442 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
443 }
444 #[allow(deprecated)]
445 EncryptedEventScheme::MegolmV1AesSha2(s) => {
446 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
447
448 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
449 }
450 _ => Self::Unknown,
451 }
452 }
453
454 pub(crate) fn session_id(&self) -> Option<&str> {
457 match self {
458 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
459 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
460 EncryptedMessage::Unknown => None,
461 }
462 }
463}
464
465#[derive(Clone, Debug)]
467pub struct Sticker {
468 pub(in crate::timeline) content: StickerEventContent,
469}
470
471impl Sticker {
472 pub fn content(&self) -> &StickerEventContent {
474 &self.content
475 }
476}
477
478#[derive(Clone, Debug)]
480pub struct RoomMembershipChange {
481 pub(in crate::timeline) user_id: OwnedUserId,
482 pub(in crate::timeline) content: StateEventContentChange<RoomMemberEventContent>,
483 pub(in crate::timeline) change: Option<MembershipChange>,
484}
485
486impl RoomMembershipChange {
487 pub fn user_id(&self) -> &UserId {
489 &self.user_id
490 }
491
492 pub fn content(&self) -> &StateEventContentChange<RoomMemberEventContent> {
494 &self.content
495 }
496
497 pub fn display_name(&self) -> Option<String> {
500 if let StateEventContentChange::Original { content, prev_content } = &self.content {
501 content
502 .displayname
503 .as_ref()
504 .or_else(|| {
505 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
506 })
507 .cloned()
508 } else {
509 None
510 }
511 }
512
513 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
516 if let StateEventContentChange::Original { content, prev_content } = &self.content {
517 content
518 .avatar_url
519 .as_ref()
520 .or_else(|| {
521 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
522 })
523 .cloned()
524 } else {
525 None
526 }
527 }
528
529 pub fn change(&self) -> Option<MembershipChange> {
537 self.change
538 }
539
540 fn redact(&self, rules: &RedactionRules) -> Self {
541 Self {
542 user_id: self.user_id.clone(),
543 content: StateEventContentChange::Redacted(self.content.clone().redact(rules)),
544 change: self.change,
545 }
546 }
547}
548
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
551pub enum MembershipChange {
552 None,
554
555 Error,
557
558 Joined,
560
561 Left,
563
564 Banned,
566
567 Unbanned,
569
570 Kicked,
572
573 Invited,
575
576 KickedAndBanned,
578
579 InvitationAccepted,
581
582 InvitationRejected,
584
585 InvitationRevoked,
587
588 Knocked,
590
591 KnockAccepted,
593
594 KnockRetracted,
596
597 KnockDenied,
599
600 NotImplemented,
602}
603
604#[derive(Clone, Debug)]
609pub struct MemberProfileChange {
610 pub(in crate::timeline) user_id: OwnedUserId,
611 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
612 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
613}
614
615impl MemberProfileChange {
616 pub fn user_id(&self) -> &UserId {
618 &self.user_id
619 }
620
621 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
623 self.displayname_change.as_ref()
624 }
625
626 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
628 self.avatar_url_change.as_ref()
629 }
630
631 fn redact(&self) -> Self {
632 Self {
633 user_id: self.user_id.clone(),
634 displayname_change: None,
639 avatar_url_change: None,
640 }
641 }
642}
643
644#[derive(Clone, Debug)]
647pub enum AnyOtherStateEventContentChange {
648 PolicyRuleRoom(StateEventContentChange<PolicyRuleRoomEventContent>),
650
651 PolicyRuleServer(StateEventContentChange<PolicyRuleServerEventContent>),
653
654 PolicyRuleUser(StateEventContentChange<PolicyRuleUserEventContent>),
656
657 RoomAliases(StateEventContentChange<RoomAliasesEventContent>),
659
660 RoomAvatar(StateEventContentChange<RoomAvatarEventContent>),
662
663 RoomCanonicalAlias(StateEventContentChange<RoomCanonicalAliasEventContent>),
665
666 RoomCreate(StateEventContentChange<RoomCreateEventContent>),
668
669 RoomEncryption(StateEventContentChange<RoomEncryptionEventContent>),
671
672 RoomGuestAccess(StateEventContentChange<RoomGuestAccessEventContent>),
674
675 RoomHistoryVisibility(StateEventContentChange<RoomHistoryVisibilityEventContent>),
677
678 RoomJoinRules(StateEventContentChange<RoomJoinRulesEventContent>),
680
681 RoomName(StateEventContentChange<RoomNameEventContent>),
683
684 RoomPinnedEvents(StateEventContentChange<RoomPinnedEventsEventContent>),
686
687 RoomPowerLevels(StateEventContentChange<RoomPowerLevelsEventContent>),
689
690 RoomServerAcl(StateEventContentChange<RoomServerAclEventContent>),
692
693 RoomThirdPartyInvite(StateEventContentChange<RoomThirdPartyInviteEventContent>),
695
696 RoomTombstone(StateEventContentChange<RoomTombstoneEventContent>),
698
699 RoomTopic(StateEventContentChange<RoomTopicEventContent>),
701
702 SpaceChild(StateEventContentChange<SpaceChildEventContent>),
704
705 SpaceParent(StateEventContentChange<SpaceParentEventContent>),
707
708 #[doc(hidden)]
709 _Custom { event_type: String },
710}
711
712impl AnyOtherStateEventContentChange {
713 pub(crate) fn with_event_content(content: AnyStateEventContentChange) -> Self {
719 let event_type = content.event_type();
720
721 match content {
722 AnyStateEventContentChange::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
723 AnyStateEventContentChange::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
724 AnyStateEventContentChange::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
725 AnyStateEventContentChange::RoomAliases(c) => Self::RoomAliases(c),
726 AnyStateEventContentChange::RoomAvatar(c) => Self::RoomAvatar(c),
727 AnyStateEventContentChange::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
728 AnyStateEventContentChange::RoomCreate(c) => Self::RoomCreate(c),
729 AnyStateEventContentChange::RoomEncryption(c) => Self::RoomEncryption(c),
730 AnyStateEventContentChange::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
731 AnyStateEventContentChange::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
732 AnyStateEventContentChange::RoomJoinRules(c) => Self::RoomJoinRules(c),
733 AnyStateEventContentChange::RoomName(c) => Self::RoomName(c),
734 AnyStateEventContentChange::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
735 AnyStateEventContentChange::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
736 AnyStateEventContentChange::RoomServerAcl(c) => Self::RoomServerAcl(c),
737 AnyStateEventContentChange::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
738 AnyStateEventContentChange::RoomTombstone(c) => Self::RoomTombstone(c),
739 AnyStateEventContentChange::RoomTopic(c) => Self::RoomTopic(c),
740 AnyStateEventContentChange::SpaceChild(c) => Self::SpaceChild(c),
741 AnyStateEventContentChange::SpaceParent(c) => Self::SpaceParent(c),
742 AnyStateEventContentChange::RoomMember(_) => unreachable!(),
743 _ => Self::_Custom { event_type: event_type.to_string() },
744 }
745 }
746
747 pub fn event_type(&self) -> StateEventType {
749 match self {
750 Self::PolicyRuleRoom(c) => c.event_type(),
751 Self::PolicyRuleServer(c) => c.event_type(),
752 Self::PolicyRuleUser(c) => c.event_type(),
753 Self::RoomAliases(c) => c.event_type(),
754 Self::RoomAvatar(c) => c.event_type(),
755 Self::RoomCanonicalAlias(c) => c.event_type(),
756 Self::RoomCreate(c) => c.event_type(),
757 Self::RoomEncryption(c) => c.event_type(),
758 Self::RoomGuestAccess(c) => c.event_type(),
759 Self::RoomHistoryVisibility(c) => c.event_type(),
760 Self::RoomJoinRules(c) => c.event_type(),
761 Self::RoomName(c) => c.event_type(),
762 Self::RoomPinnedEvents(c) => c.event_type(),
763 Self::RoomPowerLevels(c) => c.event_type(),
764 Self::RoomServerAcl(c) => c.event_type(),
765 Self::RoomThirdPartyInvite(c) => c.event_type(),
766 Self::RoomTombstone(c) => c.event_type(),
767 Self::RoomTopic(c) => c.event_type(),
768 Self::SpaceChild(c) => c.event_type(),
769 Self::SpaceParent(c) => c.event_type(),
770 Self::_Custom { event_type } => event_type.as_str().into(),
771 }
772 }
773
774 fn redact(&self, rules: &RedactionRules) -> Self {
775 match self {
776 Self::PolicyRuleRoom(c) => {
777 Self::PolicyRuleRoom(StateEventContentChange::Redacted(c.clone().redact(rules)))
778 }
779 Self::PolicyRuleServer(c) => {
780 Self::PolicyRuleServer(StateEventContentChange::Redacted(c.clone().redact(rules)))
781 }
782 Self::PolicyRuleUser(c) => {
783 Self::PolicyRuleUser(StateEventContentChange::Redacted(c.clone().redact(rules)))
784 }
785 Self::RoomAliases(c) => {
786 Self::RoomAliases(StateEventContentChange::Redacted(c.clone().redact(rules)))
787 }
788 Self::RoomAvatar(c) => {
789 Self::RoomAvatar(StateEventContentChange::Redacted(c.clone().redact(rules)))
790 }
791 Self::RoomCanonicalAlias(c) => {
792 Self::RoomCanonicalAlias(StateEventContentChange::Redacted(c.clone().redact(rules)))
793 }
794 Self::RoomCreate(c) => {
795 Self::RoomCreate(StateEventContentChange::Redacted(c.clone().redact(rules)))
796 }
797 Self::RoomEncryption(c) => {
798 Self::RoomEncryption(StateEventContentChange::Redacted(c.clone().redact(rules)))
799 }
800 Self::RoomGuestAccess(c) => {
801 Self::RoomGuestAccess(StateEventContentChange::Redacted(c.clone().redact(rules)))
802 }
803 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
804 StateEventContentChange::Redacted(c.clone().redact(rules)),
805 ),
806 Self::RoomJoinRules(c) => {
807 Self::RoomJoinRules(StateEventContentChange::Redacted(c.clone().redact(rules)))
808 }
809 Self::RoomName(c) => {
810 Self::RoomName(StateEventContentChange::Redacted(c.clone().redact(rules)))
811 }
812 Self::RoomPinnedEvents(c) => {
813 Self::RoomPinnedEvents(StateEventContentChange::Redacted(c.clone().redact(rules)))
814 }
815 Self::RoomPowerLevels(c) => {
816 Self::RoomPowerLevels(StateEventContentChange::Redacted(c.clone().redact(rules)))
817 }
818 Self::RoomServerAcl(c) => {
819 Self::RoomServerAcl(StateEventContentChange::Redacted(c.clone().redact(rules)))
820 }
821 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
822 StateEventContentChange::Redacted(c.clone().redact(rules)),
823 ),
824 Self::RoomTombstone(c) => {
825 Self::RoomTombstone(StateEventContentChange::Redacted(c.clone().redact(rules)))
826 }
827 Self::RoomTopic(c) => {
828 Self::RoomTopic(StateEventContentChange::Redacted(c.clone().redact(rules)))
829 }
830 Self::SpaceChild(c) => {
831 Self::SpaceChild(StateEventContentChange::Redacted(c.clone().redact(rules)))
832 }
833 Self::SpaceParent(c) => {
834 Self::SpaceParent(StateEventContentChange::Redacted(c.clone().redact(rules)))
835 }
836 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
837 }
838 }
839}
840
841#[derive(Clone, Debug)]
843pub struct OtherState {
844 pub(in crate::timeline) state_key: String,
845 pub(in crate::timeline) content: AnyOtherStateEventContentChange,
846}
847
848impl OtherState {
849 pub fn state_key(&self) -> &str {
851 &self.state_key
852 }
853
854 pub fn content(&self) -> &AnyOtherStateEventContentChange {
856 &self.content
857 }
858
859 fn redact(&self, rules: &RedactionRules) -> Self {
860 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use assert_matches2::assert_let;
867 use matrix_sdk_test::ALICE;
868 use ruma::{
869 assign,
870 events::{
871 StateEventContentChange,
872 room::member::{
873 MembershipState, PossiblyRedactedRoomMemberEventContent, RoomMemberEventContent,
874 },
875 },
876 room_version_rules::RedactionRules,
877 };
878
879 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
880
881 #[test]
882 fn redact_membership_change() {
883 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
884 user_id: ALICE.to_owned(),
885 content: StateEventContentChange::Original {
886 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
887 reason: Some("🤬".to_owned()),
888 }),
889 prev_content: Some(PossiblyRedactedRoomMemberEventContent::new(
890 MembershipState::Join,
891 )),
892 },
893 change: Some(MembershipChange::Banned),
894 });
895
896 let redacted = content.redact(&RedactionRules::V11);
897 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
898 assert_eq!(inner.change, Some(MembershipChange::Banned));
899 assert_let!(StateEventContentChange::Redacted(inner_content_redacted) = inner.content);
900 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
901 }
902}