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 AnyFullStateEventContent, FullStateEventContent, Mentions, MessageLikeEventType,
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 message;
56mod msg_like;
57pub(super) mod other;
58pub(crate) mod pinned_events;
59mod polls;
60mod reply;
61
62pub use pinned_events::RoomPinnedEventsChange;
63
64pub(in crate::timeline) use self::message::{
65 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
66};
67pub use self::{
68 message::Message,
69 msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary},
70 other::OtherMessageLike,
71 polls::{PollResult, PollState},
72 reply::{EmbeddedEvent, InReplyToDetails},
73};
74use super::ReactionsByKeyBySender;
75
76#[derive(Clone, Debug)]
78pub enum TimelineItemContent {
79 MsgLike(MsgLikeContent),
80
81 MembershipChange(RoomMembershipChange),
83
84 ProfileChange(MemberProfileChange),
86
87 OtherState(OtherState),
89
90 FailedToParseMessageLike {
92 event_type: MessageLikeEventType,
94
95 error: Arc<serde_json::Error>,
97 },
98
99 FailedToParseState {
101 event_type: StateEventType,
103
104 state_key: String,
106
107 error: Arc<serde_json::Error>,
109 },
110
111 CallInvite,
113
114 RtcNotification,
116}
117
118impl TimelineItemContent {
119 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
120 as_variant!(self, TimelineItemContent::MsgLike)
121 }
122
123 pub fn as_message(&self) -> Option<&Message> {
126 as_variant!(self, Self::MsgLike(MsgLikeContent {
127 kind: MsgLikeKind::Message(message),
128 ..
129 }) => message)
130 }
131
132 pub fn is_message(&self) -> bool {
135 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
136 }
137
138 pub fn as_poll(&self) -> Option<&PollState> {
141 as_variant!(self, Self::MsgLike(MsgLikeContent {
142 kind: MsgLikeKind::Poll(poll_state),
143 ..
144 }) => poll_state)
145 }
146
147 pub fn is_poll(&self) -> bool {
150 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
151 }
152
153 pub fn as_sticker(&self) -> Option<&Sticker> {
154 as_variant!(
155 self,
156 Self::MsgLike(MsgLikeContent {
157 kind: MsgLikeKind::Sticker(sticker),
158 ..
159 }) => sticker
160 )
161 }
162
163 pub fn is_sticker(&self) -> bool {
166 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
167 }
168
169 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
172 as_variant!(
173 self,
174 Self::MsgLike(MsgLikeContent {
175 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
176 ..
177 }) => encrypted_message
178 )
179 }
180
181 pub fn is_unable_to_decrypt(&self) -> bool {
184 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
185 }
186
187 pub fn is_redacted(&self) -> bool {
188 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
189 }
190
191 pub(crate) fn message(
194 msgtype: MessageType,
195 mentions: Option<Mentions>,
196 reactions: ReactionsByKeyBySender,
197 thread_root: Option<OwnedEventId>,
198 in_reply_to: Option<InReplyToDetails>,
199 thread_summary: Option<ThreadSummary>,
200 ) -> Self {
201 let remove_reply_fallback =
202 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
203
204 Self::MsgLike(MsgLikeContent {
205 kind: MsgLikeKind::Message(Message::from_event(
206 msgtype,
207 mentions,
208 None,
209 remove_reply_fallback,
210 )),
211 reactions,
212 thread_root,
213 in_reply_to,
214 thread_summary,
215 })
216 }
217
218 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
220 match self {
221 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
222 TimelineItemContent::MembershipChange(_) => "a membership change",
223 TimelineItemContent::ProfileChange(_) => "a profile change",
224 TimelineItemContent::OtherState(_) => "a state event",
225 TimelineItemContent::FailedToParseMessageLike { .. }
226 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
227 TimelineItemContent::CallInvite => "a call invite",
228 TimelineItemContent::RtcNotification => "a call notification",
229 }
230 }
231
232 pub(crate) fn room_member(
233 user_id: OwnedUserId,
234 full_content: FullStateEventContent<RoomMemberEventContent>,
235 sender: OwnedUserId,
236 ) -> Self {
237 use ruma::events::room::member::MembershipChange as MChange;
238 match &full_content {
239 FullStateEventContent::Original { content, prev_content } => {
240 let membership_change = content.membership_change(
241 prev_content.as_ref().map(|c| c.details()),
242 &sender,
243 &user_id,
244 );
245
246 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
247 membership_change
248 {
249 Self::ProfileChange(MemberProfileChange {
250 user_id,
251 displayname_change: displayname_change.map(|c| Change {
252 new: c.new.map(ToOwned::to_owned),
253 old: c.old.map(ToOwned::to_owned),
254 }),
255 avatar_url_change: avatar_url_change.map(|c| Change {
256 new: c.new.map(ToOwned::to_owned),
257 old: c.old.map(ToOwned::to_owned),
258 }),
259 })
260 } else {
261 let change = match membership_change {
262 MChange::None => MembershipChange::None,
263 MChange::Error => MembershipChange::Error,
264 MChange::Joined => MembershipChange::Joined,
265 MChange::Left => MembershipChange::Left,
266 MChange::Banned => MembershipChange::Banned,
267 MChange::Unbanned => MembershipChange::Unbanned,
268 MChange::Kicked => MembershipChange::Kicked,
269 MChange::Invited => MembershipChange::Invited,
270 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
271 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
272 MChange::InvitationRejected => MembershipChange::InvitationRejected,
273 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
274 MChange::Knocked => MembershipChange::Knocked,
275 MChange::KnockAccepted => MembershipChange::KnockAccepted,
276 MChange::KnockRetracted => MembershipChange::KnockRetracted,
277 MChange::KnockDenied => MembershipChange::KnockDenied,
278 MChange::ProfileChanged { .. } => unreachable!(),
279 _ => MembershipChange::NotImplemented,
280 };
281
282 Self::MembershipChange(RoomMembershipChange {
283 user_id,
284 content: full_content,
285 change: Some(change),
286 })
287 }
288 }
289 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
290 user_id,
291 content: full_content,
292 change: None,
293 }),
294 }
295 }
296
297 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
298 match self {
299 Self::MsgLike(_) | Self::CallInvite | Self::RtcNotification => {
300 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
301 }
302 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
303 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
304 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
305 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
306 }
307 }
308
309 pub fn thread_root(&self) -> Option<OwnedEventId> {
311 as_variant!(self, Self::MsgLike)?.thread_root.clone()
312 }
313
314 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
316 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
317 }
318
319 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
322 match self {
323 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
324
325 TimelineItemContent::MembershipChange(..)
326 | TimelineItemContent::ProfileChange(..)
327 | TimelineItemContent::OtherState(..)
328 | TimelineItemContent::FailedToParseMessageLike { .. }
329 | TimelineItemContent::FailedToParseState { .. }
330 | TimelineItemContent::CallInvite
331 | TimelineItemContent::RtcNotification => {
332 None
334 }
335 }
336 }
337
338 pub fn thread_summary(&self) -> Option<ThreadSummary> {
340 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
341 }
342
343 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
347 match self {
348 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
349
350 TimelineItemContent::MembershipChange(..)
351 | TimelineItemContent::ProfileChange(..)
352 | TimelineItemContent::OtherState(..)
353 | TimelineItemContent::FailedToParseMessageLike { .. }
354 | TimelineItemContent::FailedToParseState { .. }
355 | TimelineItemContent::CallInvite
356 | TimelineItemContent::RtcNotification => {
357 None
359 }
360 }
361 }
362
363 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
364 let mut cloned = self.clone();
365 if let Some(r) = cloned.reactions_mut() {
366 *r = reactions;
367 }
368 cloned
369 }
370}
371
372#[derive(Clone, Debug)]
374pub enum EncryptedMessage {
375 OlmV1Curve25519AesSha2 {
378 sender_key: String,
380 },
381 MegolmV1AesSha2 {
383 #[deprecated = "this field should still be sent but should not be used when received"]
385 #[doc(hidden)] sender_key: Option<String>,
387
388 #[deprecated = "this field should still be sent but should not be used when received"]
390 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
392
393 session_id: String,
395
396 cause: UtdCause,
399 },
400 Unknown,
402}
403
404impl EncryptedMessage {
405 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
406 match content.scheme {
407 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
408 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
409 }
410 #[allow(deprecated)]
411 EncryptedEventScheme::MegolmV1AesSha2(s) => {
412 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
413
414 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
415 }
416 _ => Self::Unknown,
417 }
418 }
419
420 pub(crate) fn session_id(&self) -> Option<&str> {
423 match self {
424 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
425 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
426 EncryptedMessage::Unknown => None,
427 }
428 }
429}
430
431#[derive(Clone, Debug)]
433pub struct Sticker {
434 pub(in crate::timeline) content: StickerEventContent,
435}
436
437impl Sticker {
438 pub fn content(&self) -> &StickerEventContent {
440 &self.content
441 }
442}
443
444#[derive(Clone, Debug)]
446pub struct RoomMembershipChange {
447 pub(in crate::timeline) user_id: OwnedUserId,
448 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
449 pub(in crate::timeline) change: Option<MembershipChange>,
450}
451
452impl RoomMembershipChange {
453 pub fn user_id(&self) -> &UserId {
455 &self.user_id
456 }
457
458 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
460 &self.content
461 }
462
463 pub fn display_name(&self) -> Option<String> {
466 if let FullStateEventContent::Original { content, prev_content } = &self.content {
467 content
468 .displayname
469 .as_ref()
470 .or_else(|| {
471 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
472 })
473 .cloned()
474 } else {
475 None
476 }
477 }
478
479 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
482 if let FullStateEventContent::Original { content, prev_content } = &self.content {
483 content
484 .avatar_url
485 .as_ref()
486 .or_else(|| {
487 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
488 })
489 .cloned()
490 } else {
491 None
492 }
493 }
494
495 pub fn change(&self) -> Option<MembershipChange> {
503 self.change
504 }
505
506 fn redact(&self, rules: &RedactionRules) -> Self {
507 Self {
508 user_id: self.user_id.clone(),
509 content: FullStateEventContent::Redacted(self.content.clone().redact(rules)),
510 change: self.change,
511 }
512 }
513}
514
515#[derive(Clone, Copy, Debug, PartialEq, Eq)]
517pub enum MembershipChange {
518 None,
520
521 Error,
523
524 Joined,
526
527 Left,
529
530 Banned,
532
533 Unbanned,
535
536 Kicked,
538
539 Invited,
541
542 KickedAndBanned,
544
545 InvitationAccepted,
547
548 InvitationRejected,
550
551 InvitationRevoked,
553
554 Knocked,
556
557 KnockAccepted,
559
560 KnockRetracted,
562
563 KnockDenied,
565
566 NotImplemented,
568}
569
570#[derive(Clone, Debug)]
575pub struct MemberProfileChange {
576 pub(in crate::timeline) user_id: OwnedUserId,
577 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
578 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
579}
580
581impl MemberProfileChange {
582 pub fn user_id(&self) -> &UserId {
584 &self.user_id
585 }
586
587 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
589 self.displayname_change.as_ref()
590 }
591
592 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
594 self.avatar_url_change.as_ref()
595 }
596
597 fn redact(&self) -> Self {
598 Self {
599 user_id: self.user_id.clone(),
600 displayname_change: None,
605 avatar_url_change: None,
606 }
607 }
608}
609
610#[derive(Clone, Debug)]
613pub enum AnyOtherFullStateEventContent {
614 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
616
617 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
619
620 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
622
623 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
625
626 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
628
629 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
631
632 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
634
635 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
637
638 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
640
641 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
643
644 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
646
647 RoomName(FullStateEventContent<RoomNameEventContent>),
649
650 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
652
653 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
655
656 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
658
659 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
661
662 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
664
665 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
667
668 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
670
671 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
673
674 #[doc(hidden)]
675 _Custom { event_type: String },
676}
677
678impl AnyOtherFullStateEventContent {
679 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
685 let event_type = content.event_type();
686
687 match content {
688 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
689 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
690 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
691 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
692 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
693 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
694 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
695 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
696 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
697 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
698 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
699 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
700 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
701 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
702 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
703 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
704 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
705 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
706 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
707 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
708 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
709 _ => Self::_Custom { event_type: event_type.to_string() },
710 }
711 }
712
713 pub fn event_type(&self) -> StateEventType {
715 match self {
716 Self::PolicyRuleRoom(c) => c.event_type(),
717 Self::PolicyRuleServer(c) => c.event_type(),
718 Self::PolicyRuleUser(c) => c.event_type(),
719 Self::RoomAliases(c) => c.event_type(),
720 Self::RoomAvatar(c) => c.event_type(),
721 Self::RoomCanonicalAlias(c) => c.event_type(),
722 Self::RoomCreate(c) => c.event_type(),
723 Self::RoomEncryption(c) => c.event_type(),
724 Self::RoomGuestAccess(c) => c.event_type(),
725 Self::RoomHistoryVisibility(c) => c.event_type(),
726 Self::RoomJoinRules(c) => c.event_type(),
727 Self::RoomName(c) => c.event_type(),
728 Self::RoomPinnedEvents(c) => c.event_type(),
729 Self::RoomPowerLevels(c) => c.event_type(),
730 Self::RoomServerAcl(c) => c.event_type(),
731 Self::RoomThirdPartyInvite(c) => c.event_type(),
732 Self::RoomTombstone(c) => c.event_type(),
733 Self::RoomTopic(c) => c.event_type(),
734 Self::SpaceChild(c) => c.event_type(),
735 Self::SpaceParent(c) => c.event_type(),
736 Self::_Custom { event_type } => event_type.as_str().into(),
737 }
738 }
739
740 fn redact(&self, rules: &RedactionRules) -> Self {
741 match self {
742 Self::PolicyRuleRoom(c) => {
743 Self::PolicyRuleRoom(FullStateEventContent::Redacted(c.clone().redact(rules)))
744 }
745 Self::PolicyRuleServer(c) => {
746 Self::PolicyRuleServer(FullStateEventContent::Redacted(c.clone().redact(rules)))
747 }
748 Self::PolicyRuleUser(c) => {
749 Self::PolicyRuleUser(FullStateEventContent::Redacted(c.clone().redact(rules)))
750 }
751 Self::RoomAliases(c) => {
752 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(rules)))
753 }
754 Self::RoomAvatar(c) => {
755 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(rules)))
756 }
757 Self::RoomCanonicalAlias(c) => {
758 Self::RoomCanonicalAlias(FullStateEventContent::Redacted(c.clone().redact(rules)))
759 }
760 Self::RoomCreate(c) => {
761 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(rules)))
762 }
763 Self::RoomEncryption(c) => {
764 Self::RoomEncryption(FullStateEventContent::Redacted(c.clone().redact(rules)))
765 }
766 Self::RoomGuestAccess(c) => {
767 Self::RoomGuestAccess(FullStateEventContent::Redacted(c.clone().redact(rules)))
768 }
769 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
770 FullStateEventContent::Redacted(c.clone().redact(rules)),
771 ),
772 Self::RoomJoinRules(c) => {
773 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(rules)))
774 }
775 Self::RoomName(c) => {
776 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(rules)))
777 }
778 Self::RoomPinnedEvents(c) => {
779 Self::RoomPinnedEvents(FullStateEventContent::Redacted(c.clone().redact(rules)))
780 }
781 Self::RoomPowerLevels(c) => {
782 Self::RoomPowerLevels(FullStateEventContent::Redacted(c.clone().redact(rules)))
783 }
784 Self::RoomServerAcl(c) => {
785 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(rules)))
786 }
787 Self::RoomThirdPartyInvite(c) => {
788 Self::RoomThirdPartyInvite(FullStateEventContent::Redacted(c.clone().redact(rules)))
789 }
790 Self::RoomTombstone(c) => {
791 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(rules)))
792 }
793 Self::RoomTopic(c) => {
794 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(rules)))
795 }
796 Self::SpaceChild(c) => {
797 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(rules)))
798 }
799 Self::SpaceParent(c) => {
800 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(rules)))
801 }
802 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
803 }
804 }
805}
806
807#[derive(Clone, Debug)]
809pub struct OtherState {
810 pub(in crate::timeline) state_key: String,
811 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
812}
813
814impl OtherState {
815 pub fn state_key(&self) -> &str {
817 &self.state_key
818 }
819
820 pub fn content(&self) -> &AnyOtherFullStateEventContent {
822 &self.content
823 }
824
825 fn redact(&self, rules: &RedactionRules) -> Self {
826 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use assert_matches2::assert_let;
833 use matrix_sdk_test::ALICE;
834 use ruma::{
835 assign,
836 events::{
837 FullStateEventContent,
838 room::member::{MembershipState, RoomMemberEventContent},
839 },
840 room_version_rules::RedactionRules,
841 };
842
843 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
844
845 #[test]
846 fn redact_membership_change() {
847 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
848 user_id: ALICE.to_owned(),
849 content: FullStateEventContent::Original {
850 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
851 reason: Some("🤬".to_owned()),
852 }),
853 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
854 },
855 change: Some(MembershipChange::Banned),
856 });
857
858 let redacted = content.redact(&RedactionRules::V11);
859 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
860 assert_eq!(inner.change, Some(MembershipChange::Banned));
861 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
862 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
863 }
864}