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#[allow(clippy::large_enum_variant)]
78#[derive(Clone, Debug)]
79pub enum TimelineItemContent {
80 MsgLike(MsgLikeContent),
81
82 MembershipChange(RoomMembershipChange),
84
85 ProfileChange(MemberProfileChange),
87
88 OtherState(OtherState),
90
91 FailedToParseMessageLike {
93 event_type: MessageLikeEventType,
95
96 error: Arc<serde_json::Error>,
98 },
99
100 FailedToParseState {
102 event_type: StateEventType,
104
105 state_key: String,
107
108 error: Arc<serde_json::Error>,
110 },
111
112 CallInvite,
114
115 RtcNotification,
117}
118
119impl TimelineItemContent {
120 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
121 as_variant!(self, TimelineItemContent::MsgLike)
122 }
123
124 pub fn as_message(&self) -> Option<&Message> {
127 as_variant!(self, Self::MsgLike(MsgLikeContent {
128 kind: MsgLikeKind::Message(message),
129 ..
130 }) => message)
131 }
132
133 pub fn is_message(&self) -> bool {
136 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
137 }
138
139 pub fn as_poll(&self) -> Option<&PollState> {
142 as_variant!(self, Self::MsgLike(MsgLikeContent {
143 kind: MsgLikeKind::Poll(poll_state),
144 ..
145 }) => poll_state)
146 }
147
148 pub fn is_poll(&self) -> bool {
151 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
152 }
153
154 pub fn as_sticker(&self) -> Option<&Sticker> {
155 as_variant!(
156 self,
157 Self::MsgLike(MsgLikeContent {
158 kind: MsgLikeKind::Sticker(sticker),
159 ..
160 }) => sticker
161 )
162 }
163
164 pub fn is_sticker(&self) -> bool {
167 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
168 }
169
170 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
173 as_variant!(
174 self,
175 Self::MsgLike(MsgLikeContent {
176 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
177 ..
178 }) => encrypted_message
179 )
180 }
181
182 pub fn is_unable_to_decrypt(&self) -> bool {
185 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
186 }
187
188 pub fn is_redacted(&self) -> bool {
189 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
190 }
191
192 pub(crate) fn message(
195 msgtype: MessageType,
196 mentions: Option<Mentions>,
197 reactions: ReactionsByKeyBySender,
198 thread_root: Option<OwnedEventId>,
199 in_reply_to: Option<InReplyToDetails>,
200 thread_summary: Option<ThreadSummary>,
201 ) -> Self {
202 let remove_reply_fallback =
203 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
204
205 Self::MsgLike(MsgLikeContent {
206 kind: MsgLikeKind::Message(Message::from_event(
207 msgtype,
208 mentions,
209 None,
210 remove_reply_fallback,
211 )),
212 reactions,
213 thread_root,
214 in_reply_to,
215 thread_summary,
216 })
217 }
218
219 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
221 match self {
222 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
223 TimelineItemContent::MembershipChange(_) => "a membership change",
224 TimelineItemContent::ProfileChange(_) => "a profile change",
225 TimelineItemContent::OtherState(_) => "a state event",
226 TimelineItemContent::FailedToParseMessageLike { .. }
227 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
228 TimelineItemContent::CallInvite => "a call invite",
229 TimelineItemContent::RtcNotification => "a call notification",
230 }
231 }
232
233 pub(crate) fn room_member(
234 user_id: OwnedUserId,
235 full_content: FullStateEventContent<RoomMemberEventContent>,
236 sender: OwnedUserId,
237 ) -> Self {
238 use ruma::events::room::member::MembershipChange as MChange;
239 match &full_content {
240 FullStateEventContent::Original { content, prev_content } => {
241 let membership_change = content.membership_change(
242 prev_content.as_ref().map(|c| c.details()),
243 &sender,
244 &user_id,
245 );
246
247 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
248 membership_change
249 {
250 Self::ProfileChange(MemberProfileChange {
251 user_id,
252 displayname_change: displayname_change.map(|c| Change {
253 new: c.new.map(ToOwned::to_owned),
254 old: c.old.map(ToOwned::to_owned),
255 }),
256 avatar_url_change: avatar_url_change.map(|c| Change {
257 new: c.new.map(ToOwned::to_owned),
258 old: c.old.map(ToOwned::to_owned),
259 }),
260 })
261 } else {
262 let change = match membership_change {
263 MChange::None => MembershipChange::None,
264 MChange::Error => MembershipChange::Error,
265 MChange::Joined => MembershipChange::Joined,
266 MChange::Left => MembershipChange::Left,
267 MChange::Banned => MembershipChange::Banned,
268 MChange::Unbanned => MembershipChange::Unbanned,
269 MChange::Kicked => MembershipChange::Kicked,
270 MChange::Invited => MembershipChange::Invited,
271 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
272 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
273 MChange::InvitationRejected => MembershipChange::InvitationRejected,
274 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
275 MChange::Knocked => MembershipChange::Knocked,
276 MChange::KnockAccepted => MembershipChange::KnockAccepted,
277 MChange::KnockRetracted => MembershipChange::KnockRetracted,
278 MChange::KnockDenied => MembershipChange::KnockDenied,
279 MChange::ProfileChanged { .. } => unreachable!(),
280 _ => MembershipChange::NotImplemented,
281 };
282
283 Self::MembershipChange(RoomMembershipChange {
284 user_id,
285 content: full_content,
286 change: Some(change),
287 })
288 }
289 }
290 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
291 user_id,
292 content: full_content,
293 change: None,
294 }),
295 }
296 }
297
298 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
299 match self {
300 Self::MsgLike(_) | Self::CallInvite | Self::RtcNotification => {
301 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
302 }
303 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
304 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
305 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
306 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
307 }
308 }
309
310 pub fn thread_root(&self) -> Option<OwnedEventId> {
312 as_variant!(self, Self::MsgLike)?.thread_root.clone()
313 }
314
315 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
317 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
318 }
319
320 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
323 match self {
324 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
325
326 TimelineItemContent::MembershipChange(..)
327 | TimelineItemContent::ProfileChange(..)
328 | TimelineItemContent::OtherState(..)
329 | TimelineItemContent::FailedToParseMessageLike { .. }
330 | TimelineItemContent::FailedToParseState { .. }
331 | TimelineItemContent::CallInvite
332 | TimelineItemContent::RtcNotification => {
333 None
335 }
336 }
337 }
338
339 pub fn thread_summary(&self) -> Option<ThreadSummary> {
341 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
342 }
343
344 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
348 match self {
349 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
350
351 TimelineItemContent::MembershipChange(..)
352 | TimelineItemContent::ProfileChange(..)
353 | TimelineItemContent::OtherState(..)
354 | TimelineItemContent::FailedToParseMessageLike { .. }
355 | TimelineItemContent::FailedToParseState { .. }
356 | TimelineItemContent::CallInvite
357 | TimelineItemContent::RtcNotification => {
358 None
360 }
361 }
362 }
363
364 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
365 let mut cloned = self.clone();
366 if let Some(r) = cloned.reactions_mut() {
367 *r = reactions;
368 }
369 cloned
370 }
371}
372
373#[derive(Clone, Debug)]
375pub enum EncryptedMessage {
376 OlmV1Curve25519AesSha2 {
379 sender_key: String,
381 },
382 MegolmV1AesSha2 {
384 #[deprecated = "this field should still be sent but should not be used when received"]
386 #[doc(hidden)] sender_key: Option<String>,
388
389 #[deprecated = "this field should still be sent but should not be used when received"]
391 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
393
394 session_id: String,
396
397 cause: UtdCause,
400 },
401 Unknown,
403}
404
405impl EncryptedMessage {
406 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
407 match content.scheme {
408 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
409 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
410 }
411 #[allow(deprecated)]
412 EncryptedEventScheme::MegolmV1AesSha2(s) => {
413 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
414
415 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
416 }
417 _ => Self::Unknown,
418 }
419 }
420
421 pub(crate) fn session_id(&self) -> Option<&str> {
424 match self {
425 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
426 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
427 EncryptedMessage::Unknown => None,
428 }
429 }
430}
431
432#[derive(Clone, Debug)]
434pub struct Sticker {
435 pub(in crate::timeline) content: StickerEventContent,
436}
437
438impl Sticker {
439 pub fn content(&self) -> &StickerEventContent {
441 &self.content
442 }
443}
444
445#[derive(Clone, Debug)]
447pub struct RoomMembershipChange {
448 pub(in crate::timeline) user_id: OwnedUserId,
449 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
450 pub(in crate::timeline) change: Option<MembershipChange>,
451}
452
453impl RoomMembershipChange {
454 pub fn user_id(&self) -> &UserId {
456 &self.user_id
457 }
458
459 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
461 &self.content
462 }
463
464 pub fn display_name(&self) -> Option<String> {
467 if let FullStateEventContent::Original { content, prev_content } = &self.content {
468 content
469 .displayname
470 .as_ref()
471 .or_else(|| {
472 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
473 })
474 .cloned()
475 } else {
476 None
477 }
478 }
479
480 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
483 if let FullStateEventContent::Original { content, prev_content } = &self.content {
484 content
485 .avatar_url
486 .as_ref()
487 .or_else(|| {
488 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
489 })
490 .cloned()
491 } else {
492 None
493 }
494 }
495
496 pub fn change(&self) -> Option<MembershipChange> {
504 self.change
505 }
506
507 fn redact(&self, rules: &RedactionRules) -> Self {
508 Self {
509 user_id: self.user_id.clone(),
510 content: FullStateEventContent::Redacted(self.content.clone().redact(rules)),
511 change: self.change,
512 }
513 }
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
518pub enum MembershipChange {
519 None,
521
522 Error,
524
525 Joined,
527
528 Left,
530
531 Banned,
533
534 Unbanned,
536
537 Kicked,
539
540 Invited,
542
543 KickedAndBanned,
545
546 InvitationAccepted,
548
549 InvitationRejected,
551
552 InvitationRevoked,
554
555 Knocked,
557
558 KnockAccepted,
560
561 KnockRetracted,
563
564 KnockDenied,
566
567 NotImplemented,
569}
570
571#[derive(Clone, Debug)]
576pub struct MemberProfileChange {
577 pub(in crate::timeline) user_id: OwnedUserId,
578 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
579 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
580}
581
582impl MemberProfileChange {
583 pub fn user_id(&self) -> &UserId {
585 &self.user_id
586 }
587
588 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
590 self.displayname_change.as_ref()
591 }
592
593 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
595 self.avatar_url_change.as_ref()
596 }
597
598 fn redact(&self) -> Self {
599 Self {
600 user_id: self.user_id.clone(),
601 displayname_change: None,
606 avatar_url_change: None,
607 }
608 }
609}
610
611#[derive(Clone, Debug)]
614pub enum AnyOtherFullStateEventContent {
615 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
617
618 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
620
621 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
623
624 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
626
627 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
629
630 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
632
633 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
635
636 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
638
639 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
641
642 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
644
645 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
647
648 RoomName(FullStateEventContent<RoomNameEventContent>),
650
651 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
653
654 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
656
657 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
659
660 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
662
663 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
665
666 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
668
669 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
671
672 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
674
675 #[doc(hidden)]
676 _Custom { event_type: String },
677}
678
679impl AnyOtherFullStateEventContent {
680 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
686 let event_type = content.event_type();
687
688 match content {
689 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
690 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
691 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
692 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
693 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
694 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
695 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
696 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
697 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
698 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
699 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
700 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
701 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
702 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
703 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
704 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
705 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
706 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
707 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
708 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
709 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
710 _ => Self::_Custom { event_type: event_type.to_string() },
711 }
712 }
713
714 pub fn event_type(&self) -> StateEventType {
716 match self {
717 Self::PolicyRuleRoom(c) => c.event_type(),
718 Self::PolicyRuleServer(c) => c.event_type(),
719 Self::PolicyRuleUser(c) => c.event_type(),
720 Self::RoomAliases(c) => c.event_type(),
721 Self::RoomAvatar(c) => c.event_type(),
722 Self::RoomCanonicalAlias(c) => c.event_type(),
723 Self::RoomCreate(c) => c.event_type(),
724 Self::RoomEncryption(c) => c.event_type(),
725 Self::RoomGuestAccess(c) => c.event_type(),
726 Self::RoomHistoryVisibility(c) => c.event_type(),
727 Self::RoomJoinRules(c) => c.event_type(),
728 Self::RoomName(c) => c.event_type(),
729 Self::RoomPinnedEvents(c) => c.event_type(),
730 Self::RoomPowerLevels(c) => c.event_type(),
731 Self::RoomServerAcl(c) => c.event_type(),
732 Self::RoomThirdPartyInvite(c) => c.event_type(),
733 Self::RoomTombstone(c) => c.event_type(),
734 Self::RoomTopic(c) => c.event_type(),
735 Self::SpaceChild(c) => c.event_type(),
736 Self::SpaceParent(c) => c.event_type(),
737 Self::_Custom { event_type } => event_type.as_str().into(),
738 }
739 }
740
741 fn redact(&self, rules: &RedactionRules) -> Self {
742 match self {
743 Self::PolicyRuleRoom(c) => {
744 Self::PolicyRuleRoom(FullStateEventContent::Redacted(c.clone().redact(rules)))
745 }
746 Self::PolicyRuleServer(c) => {
747 Self::PolicyRuleServer(FullStateEventContent::Redacted(c.clone().redact(rules)))
748 }
749 Self::PolicyRuleUser(c) => {
750 Self::PolicyRuleUser(FullStateEventContent::Redacted(c.clone().redact(rules)))
751 }
752 Self::RoomAliases(c) => {
753 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(rules)))
754 }
755 Self::RoomAvatar(c) => {
756 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(rules)))
757 }
758 Self::RoomCanonicalAlias(c) => {
759 Self::RoomCanonicalAlias(FullStateEventContent::Redacted(c.clone().redact(rules)))
760 }
761 Self::RoomCreate(c) => {
762 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(rules)))
763 }
764 Self::RoomEncryption(c) => {
765 Self::RoomEncryption(FullStateEventContent::Redacted(c.clone().redact(rules)))
766 }
767 Self::RoomGuestAccess(c) => {
768 Self::RoomGuestAccess(FullStateEventContent::Redacted(c.clone().redact(rules)))
769 }
770 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
771 FullStateEventContent::Redacted(c.clone().redact(rules)),
772 ),
773 Self::RoomJoinRules(c) => {
774 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(rules)))
775 }
776 Self::RoomName(c) => {
777 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(rules)))
778 }
779 Self::RoomPinnedEvents(c) => {
780 Self::RoomPinnedEvents(FullStateEventContent::Redacted(c.clone().redact(rules)))
781 }
782 Self::RoomPowerLevels(c) => {
783 Self::RoomPowerLevels(FullStateEventContent::Redacted(c.clone().redact(rules)))
784 }
785 Self::RoomServerAcl(c) => {
786 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(rules)))
787 }
788 Self::RoomThirdPartyInvite(c) => {
789 Self::RoomThirdPartyInvite(FullStateEventContent::Redacted(c.clone().redact(rules)))
790 }
791 Self::RoomTombstone(c) => {
792 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(rules)))
793 }
794 Self::RoomTopic(c) => {
795 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(rules)))
796 }
797 Self::SpaceChild(c) => {
798 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(rules)))
799 }
800 Self::SpaceParent(c) => {
801 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(rules)))
802 }
803 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
804 }
805 }
806}
807
808#[derive(Clone, Debug)]
810pub struct OtherState {
811 pub(in crate::timeline) state_key: String,
812 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
813}
814
815impl OtherState {
816 pub fn state_key(&self) -> &str {
818 &self.state_key
819 }
820
821 pub fn content(&self) -> &AnyOtherFullStateEventContent {
823 &self.content
824 }
825
826 fn redact(&self, rules: &RedactionRules) -> Self {
827 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use assert_matches2::assert_let;
834 use matrix_sdk_test::ALICE;
835 use ruma::{
836 assign,
837 events::{
838 FullStateEventContent,
839 room::member::{
840 MembershipState, PossiblyRedactedRoomMemberEventContent, RoomMemberEventContent,
841 },
842 },
843 room_version_rules::RedactionRules,
844 };
845
846 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
847
848 #[test]
849 fn redact_membership_change() {
850 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
851 user_id: ALICE.to_owned(),
852 content: FullStateEventContent::Original {
853 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
854 reason: Some("🤬".to_owned()),
855 }),
856 prev_content: Some(PossiblyRedactedRoomMemberEventContent::new(
857 MembershipState::Join,
858 )),
859 },
860 change: Some(MembershipChange::Banned),
861 });
862
863 let redacted = content.redact(&RedactionRules::V11);
864 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
865 assert_eq!(inner.change, Some(MembershipChange::Banned));
866 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
867 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
868 }
869}