1use std::sync::Arc;
16
17use as_variant::as_variant;
18use matrix_sdk::crypto::types::events::UtdCause;
19use matrix_sdk_base::latest_event::{PossibleLatestEvent, is_suitable_for_latest_event};
20use ruma::{
21 OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, UserId,
22 events::{
23 AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent, Mentions,
24 MessageLikeEventType, StateEventType,
25 call::invite::SyncCallInviteEvent,
26 policy::rule::{
27 room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
28 user::PolicyRuleUserEventContent,
29 },
30 poll::unstable_start::{SyncUnstablePollStartEvent, UnstablePollStartEventContent},
31 room::{
32 aliases::RoomAliasesEventContent,
33 avatar::RoomAvatarEventContent,
34 canonical_alias::RoomCanonicalAliasEventContent,
35 create::RoomCreateEventContent,
36 encrypted::{EncryptedEventScheme, MegolmV1AesSha2Content, RoomEncryptedEventContent},
37 encryption::RoomEncryptionEventContent,
38 guest_access::RoomGuestAccessEventContent,
39 history_visibility::RoomHistoryVisibilityEventContent,
40 join_rules::RoomJoinRulesEventContent,
41 member::{Change, RoomMemberEventContent, SyncRoomMemberEvent},
42 message::{MessageType, Relation, SyncRoomMessageEvent},
43 name::RoomNameEventContent,
44 pinned_events::RoomPinnedEventsEventContent,
45 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
46 server_acl::RoomServerAclEventContent,
47 third_party_invite::RoomThirdPartyInviteEventContent,
48 tombstone::RoomTombstoneEventContent,
49 topic::RoomTopicEventContent,
50 },
51 rtc::notification::SyncRtcNotificationEvent,
52 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
53 sticker::{StickerEventContent, SyncStickerEvent},
54 },
55 html::RemoveReplyFallback,
56 room_version_rules::RedactionRules,
57};
58use tracing::warn;
59
60mod message;
61mod msg_like;
62pub(super) mod other;
63pub(crate) mod pinned_events;
64mod polls;
65mod reply;
66
67pub use pinned_events::RoomPinnedEventsChange;
68
69pub(in crate::timeline) use self::message::{
70 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
71};
72pub use self::{
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#[derive(Clone, Debug)]
83pub enum TimelineItemContent {
84 MsgLike(MsgLikeContent),
85
86 MembershipChange(RoomMembershipChange),
88
89 ProfileChange(MemberProfileChange),
91
92 OtherState(OtherState),
94
95 FailedToParseMessageLike {
97 event_type: MessageLikeEventType,
99
100 error: Arc<serde_json::Error>,
102 },
103
104 FailedToParseState {
106 event_type: StateEventType,
108
109 state_key: String,
111
112 error: Arc<serde_json::Error>,
114 },
115
116 CallInvite,
118
119 RtcNotification,
121}
122
123impl TimelineItemContent {
124 pub(crate) fn from_latest_event_content(
128 event: AnySyncTimelineEvent,
129 power_levels_info: Option<(&UserId, &RoomPowerLevels)>,
130 ) -> Option<TimelineItemContent> {
131 match is_suitable_for_latest_event(&event, power_levels_info) {
132 PossibleLatestEvent::YesRoomMessage(m) => {
133 Some(Self::from_suitable_latest_event_content(m))
134 }
135 PossibleLatestEvent::YesSticker(s) => {
136 Some(Self::from_suitable_latest_sticker_content(s))
137 }
138 PossibleLatestEvent::YesPoll(poll) => {
139 Some(Self::from_suitable_latest_poll_event_content(poll))
140 }
141 PossibleLatestEvent::YesCallInvite(call_invite) => {
142 Some(Self::from_suitable_latest_call_invite_content(call_invite))
143 }
144 PossibleLatestEvent::YesRtcNotification(rtc_notification) => {
145 Some(Self::from_suitable_latest_rtc_notification_content(rtc_notification))
146 }
147 PossibleLatestEvent::NoUnsupportedEventType => {
148 warn!("Found a state event cached as latest_event! ID={}", event.event_id());
150 None
151 }
152 PossibleLatestEvent::NoUnsupportedMessageLikeType => {
153 warn!(
155 "Found an event cached as latest_event, but I don't know how \
156 to wrap it in a TimelineItemContent. type={}, ID={}",
157 event.event_type().to_string(),
158 event.event_id()
159 );
160 None
161 }
162 PossibleLatestEvent::YesKnockedStateEvent(member) => {
163 Some(Self::from_suitable_latest_knock_state_event_content(member))
164 }
165 PossibleLatestEvent::NoEncrypted => {
166 warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
167 None
168 }
169 }
170 }
171
172 fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent {
176 match event {
177 SyncRoomMessageEvent::Original(event) => {
178 let event_content = event.content.clone();
180
181 let edit = event
183 .unsigned
184 .relations
185 .replace
186 .as_ref()
187 .and_then(|boxed| match &boxed.content.relates_to {
188 Some(Relation::Replacement(re)) => Some(re.new_content.clone()),
189 _ => {
190 warn!("got m.room.message event with an edit without a valid m.replace relation");
191 None
192 }
193 });
194
195 let reactions = Default::default();
197 let thread_root = None;
198 let in_reply_to = None;
199 let thread_summary = None;
200
201 let msglike = MsgLikeContent {
202 kind: MsgLikeKind::Message(Message::from_event(
203 event_content.msgtype,
204 event_content.mentions,
205 edit,
206 RemoveReplyFallback::Yes,
207 )),
208 reactions,
209 thread_root,
210 in_reply_to,
211 thread_summary,
212 };
213
214 TimelineItemContent::MsgLike(msglike)
215 }
216
217 SyncRoomMessageEvent::Redacted(_) => {
218 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
219 }
220 }
221 }
222
223 fn from_suitable_latest_knock_state_event_content(
224 event: &SyncRoomMemberEvent,
225 ) -> TimelineItemContent {
226 match event {
227 SyncRoomMemberEvent::Original(event) => {
228 let content = event.content.clone();
229 let prev_content = event.prev_content().cloned();
230 TimelineItemContent::room_member(
231 event.state_key.to_owned(),
232 FullStateEventContent::Original { content, prev_content },
233 event.sender.to_owned(),
234 )
235 }
236 SyncRoomMemberEvent::Redacted(_) => {
237 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
238 }
239 }
240 }
241
242 fn from_suitable_latest_sticker_content(event: &SyncStickerEvent) -> TimelineItemContent {
246 match event {
247 SyncStickerEvent::Original(event) => {
248 let event_content = event.content.clone();
250
251 let reactions = Default::default();
253 let thread_root = None;
254 let in_reply_to = None;
255 let thread_summary = None;
256
257 let msglike = MsgLikeContent {
258 kind: MsgLikeKind::Sticker(Sticker { content: event_content }),
259 reactions,
260 thread_root,
261 in_reply_to,
262 thread_summary,
263 };
264
265 TimelineItemContent::MsgLike(msglike)
266 }
267 SyncStickerEvent::Redacted(_) => {
268 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
269 }
270 }
271 }
272
273 fn from_suitable_latest_poll_event_content(
276 event: &SyncUnstablePollStartEvent,
277 ) -> TimelineItemContent {
278 let SyncUnstablePollStartEvent::Original(event) = event else {
279 return TimelineItemContent::MsgLike(MsgLikeContent::redacted());
280 };
281
282 let edit =
284 event.unsigned.relations.replace.as_ref().and_then(|boxed| match &boxed.content {
285 UnstablePollStartEventContent::Replacement(re) => {
286 Some(re.relates_to.new_content.clone())
287 }
288 _ => {
289 warn!("got poll event with an edit without a valid m.replace relation");
290 None
291 }
292 });
293
294 let mut poll = PollState::new(event.content.poll_start().clone(), None);
295 if let Some(edit) = edit {
296 poll = poll.edit(edit).expect("the poll can't be ended yet!"); }
298
299 let reactions = Default::default();
301 let thread_root = None;
302 let in_reply_to = None;
303 let thread_summary = None;
304
305 let msglike = MsgLikeContent {
306 kind: MsgLikeKind::Poll(poll),
307 reactions,
308 thread_root,
309 in_reply_to,
310 thread_summary,
311 };
312
313 TimelineItemContent::MsgLike(msglike)
314 }
315
316 fn from_suitable_latest_call_invite_content(
317 event: &SyncCallInviteEvent,
318 ) -> TimelineItemContent {
319 match event {
320 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
321 SyncCallInviteEvent::Redacted(_) => {
322 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
323 }
324 }
325 }
326
327 fn from_suitable_latest_rtc_notification_content(
328 event: &SyncRtcNotificationEvent,
329 ) -> TimelineItemContent {
330 match event {
331 SyncRtcNotificationEvent::Original(_) => TimelineItemContent::RtcNotification,
332 SyncRtcNotificationEvent::Redacted(_) => {
333 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
334 }
335 }
336 }
337
338 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
339 as_variant!(self, TimelineItemContent::MsgLike)
340 }
341
342 pub fn as_message(&self) -> Option<&Message> {
345 as_variant!(self, Self::MsgLike(MsgLikeContent {
346 kind: MsgLikeKind::Message(message),
347 ..
348 }) => message)
349 }
350
351 pub fn is_message(&self) -> bool {
354 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
355 }
356
357 pub fn as_poll(&self) -> Option<&PollState> {
360 as_variant!(self, Self::MsgLike(MsgLikeContent {
361 kind: MsgLikeKind::Poll(poll_state),
362 ..
363 }) => poll_state)
364 }
365
366 pub fn is_poll(&self) -> bool {
369 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
370 }
371
372 pub fn as_sticker(&self) -> Option<&Sticker> {
373 as_variant!(
374 self,
375 Self::MsgLike(MsgLikeContent {
376 kind: MsgLikeKind::Sticker(sticker),
377 ..
378 }) => sticker
379 )
380 }
381
382 pub fn is_sticker(&self) -> bool {
385 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
386 }
387
388 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
391 as_variant!(
392 self,
393 Self::MsgLike(MsgLikeContent {
394 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
395 ..
396 }) => encrypted_message
397 )
398 }
399
400 pub fn is_unable_to_decrypt(&self) -> bool {
403 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
404 }
405
406 pub fn is_redacted(&self) -> bool {
407 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
408 }
409
410 pub(crate) fn message(
413 msgtype: MessageType,
414 mentions: Option<Mentions>,
415 reactions: ReactionsByKeyBySender,
416 thread_root: Option<OwnedEventId>,
417 in_reply_to: Option<InReplyToDetails>,
418 thread_summary: Option<ThreadSummary>,
419 ) -> Self {
420 let remove_reply_fallback =
421 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
422
423 Self::MsgLike(MsgLikeContent {
424 kind: MsgLikeKind::Message(Message::from_event(
425 msgtype,
426 mentions,
427 None,
428 remove_reply_fallback,
429 )),
430 reactions,
431 thread_root,
432 in_reply_to,
433 thread_summary,
434 })
435 }
436
437 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
439 match self {
440 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
441 TimelineItemContent::MembershipChange(_) => "a membership change",
442 TimelineItemContent::ProfileChange(_) => "a profile change",
443 TimelineItemContent::OtherState(_) => "a state event",
444 TimelineItemContent::FailedToParseMessageLike { .. }
445 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
446 TimelineItemContent::CallInvite => "a call invite",
447 TimelineItemContent::RtcNotification => "a call notification",
448 }
449 }
450
451 pub(crate) fn room_member(
452 user_id: OwnedUserId,
453 full_content: FullStateEventContent<RoomMemberEventContent>,
454 sender: OwnedUserId,
455 ) -> Self {
456 use ruma::events::room::member::MembershipChange as MChange;
457 match &full_content {
458 FullStateEventContent::Original { content, prev_content } => {
459 let membership_change = content.membership_change(
460 prev_content.as_ref().map(|c| c.details()),
461 &sender,
462 &user_id,
463 );
464
465 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
466 membership_change
467 {
468 Self::ProfileChange(MemberProfileChange {
469 user_id,
470 displayname_change: displayname_change.map(|c| Change {
471 new: c.new.map(ToOwned::to_owned),
472 old: c.old.map(ToOwned::to_owned),
473 }),
474 avatar_url_change: avatar_url_change.map(|c| Change {
475 new: c.new.map(ToOwned::to_owned),
476 old: c.old.map(ToOwned::to_owned),
477 }),
478 })
479 } else {
480 let change = match membership_change {
481 MChange::None => MembershipChange::None,
482 MChange::Error => MembershipChange::Error,
483 MChange::Joined => MembershipChange::Joined,
484 MChange::Left => MembershipChange::Left,
485 MChange::Banned => MembershipChange::Banned,
486 MChange::Unbanned => MembershipChange::Unbanned,
487 MChange::Kicked => MembershipChange::Kicked,
488 MChange::Invited => MembershipChange::Invited,
489 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
490 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
491 MChange::InvitationRejected => MembershipChange::InvitationRejected,
492 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
493 MChange::Knocked => MembershipChange::Knocked,
494 MChange::KnockAccepted => MembershipChange::KnockAccepted,
495 MChange::KnockRetracted => MembershipChange::KnockRetracted,
496 MChange::KnockDenied => MembershipChange::KnockDenied,
497 MChange::ProfileChanged { .. } => unreachable!(),
498 _ => MembershipChange::NotImplemented,
499 };
500
501 Self::MembershipChange(RoomMembershipChange {
502 user_id,
503 content: full_content,
504 change: Some(change),
505 })
506 }
507 }
508 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
509 user_id,
510 content: full_content,
511 change: None,
512 }),
513 }
514 }
515
516 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
517 match self {
518 Self::MsgLike(_) | Self::CallInvite | Self::RtcNotification => {
519 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
520 }
521 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
522 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
523 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
524 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
525 }
526 }
527
528 pub fn thread_root(&self) -> Option<OwnedEventId> {
530 as_variant!(self, Self::MsgLike)?.thread_root.clone()
531 }
532
533 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
535 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
536 }
537
538 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
541 match self {
542 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
543
544 TimelineItemContent::MembershipChange(..)
545 | TimelineItemContent::ProfileChange(..)
546 | TimelineItemContent::OtherState(..)
547 | TimelineItemContent::FailedToParseMessageLike { .. }
548 | TimelineItemContent::FailedToParseState { .. }
549 | TimelineItemContent::CallInvite
550 | TimelineItemContent::RtcNotification => {
551 None
553 }
554 }
555 }
556
557 pub fn thread_summary(&self) -> Option<ThreadSummary> {
559 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
560 }
561
562 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
566 match self {
567 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
568
569 TimelineItemContent::MembershipChange(..)
570 | TimelineItemContent::ProfileChange(..)
571 | TimelineItemContent::OtherState(..)
572 | TimelineItemContent::FailedToParseMessageLike { .. }
573 | TimelineItemContent::FailedToParseState { .. }
574 | TimelineItemContent::CallInvite
575 | TimelineItemContent::RtcNotification => {
576 None
578 }
579 }
580 }
581
582 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
583 let mut cloned = self.clone();
584 if let Some(r) = cloned.reactions_mut() {
585 *r = reactions;
586 }
587 cloned
588 }
589}
590
591#[derive(Clone, Debug)]
593pub enum EncryptedMessage {
594 OlmV1Curve25519AesSha2 {
597 sender_key: String,
599 },
600 MegolmV1AesSha2 {
602 #[deprecated = "this field should still be sent but should not be used when received"]
604 #[doc(hidden)] sender_key: Option<String>,
606
607 #[deprecated = "this field should still be sent but should not be used when received"]
609 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
611
612 session_id: String,
614
615 cause: UtdCause,
618 },
619 Unknown,
621}
622
623impl EncryptedMessage {
624 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
625 match content.scheme {
626 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
627 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
628 }
629 #[allow(deprecated)]
630 EncryptedEventScheme::MegolmV1AesSha2(s) => {
631 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
632
633 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
634 }
635 _ => Self::Unknown,
636 }
637 }
638
639 pub(crate) fn session_id(&self) -> Option<&str> {
642 match self {
643 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
644 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
645 EncryptedMessage::Unknown => None,
646 }
647 }
648}
649
650#[derive(Clone, Debug)]
652pub struct Sticker {
653 pub(in crate::timeline) content: StickerEventContent,
654}
655
656impl Sticker {
657 pub fn content(&self) -> &StickerEventContent {
659 &self.content
660 }
661}
662
663#[derive(Clone, Debug)]
665pub struct RoomMembershipChange {
666 pub(in crate::timeline) user_id: OwnedUserId,
667 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
668 pub(in crate::timeline) change: Option<MembershipChange>,
669}
670
671impl RoomMembershipChange {
672 pub fn user_id(&self) -> &UserId {
674 &self.user_id
675 }
676
677 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
679 &self.content
680 }
681
682 pub fn display_name(&self) -> Option<String> {
685 if let FullStateEventContent::Original { content, prev_content } = &self.content {
686 content
687 .displayname
688 .as_ref()
689 .or_else(|| {
690 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
691 })
692 .cloned()
693 } else {
694 None
695 }
696 }
697
698 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
701 if let FullStateEventContent::Original { content, prev_content } = &self.content {
702 content
703 .avatar_url
704 .as_ref()
705 .or_else(|| {
706 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
707 })
708 .cloned()
709 } else {
710 None
711 }
712 }
713
714 pub fn change(&self) -> Option<MembershipChange> {
722 self.change
723 }
724
725 fn redact(&self, rules: &RedactionRules) -> Self {
726 Self {
727 user_id: self.user_id.clone(),
728 content: FullStateEventContent::Redacted(self.content.clone().redact(rules)),
729 change: self.change,
730 }
731 }
732}
733
734#[derive(Clone, Copy, Debug, PartialEq, Eq)]
736pub enum MembershipChange {
737 None,
739
740 Error,
742
743 Joined,
745
746 Left,
748
749 Banned,
751
752 Unbanned,
754
755 Kicked,
757
758 Invited,
760
761 KickedAndBanned,
763
764 InvitationAccepted,
766
767 InvitationRejected,
769
770 InvitationRevoked,
772
773 Knocked,
775
776 KnockAccepted,
778
779 KnockRetracted,
781
782 KnockDenied,
784
785 NotImplemented,
787}
788
789#[derive(Clone, Debug)]
794pub struct MemberProfileChange {
795 pub(in crate::timeline) user_id: OwnedUserId,
796 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
797 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
798}
799
800impl MemberProfileChange {
801 pub fn user_id(&self) -> &UserId {
803 &self.user_id
804 }
805
806 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
808 self.displayname_change.as_ref()
809 }
810
811 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
813 self.avatar_url_change.as_ref()
814 }
815
816 fn redact(&self) -> Self {
817 Self {
818 user_id: self.user_id.clone(),
819 displayname_change: None,
824 avatar_url_change: None,
825 }
826 }
827}
828
829#[derive(Clone, Debug)]
832pub enum AnyOtherFullStateEventContent {
833 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
835
836 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
838
839 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
841
842 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
844
845 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
847
848 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
850
851 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
853
854 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
856
857 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
859
860 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
862
863 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
865
866 RoomName(FullStateEventContent<RoomNameEventContent>),
868
869 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
871
872 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
874
875 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
877
878 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
880
881 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
883
884 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
886
887 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
889
890 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
892
893 #[doc(hidden)]
894 _Custom { event_type: String },
895}
896
897impl AnyOtherFullStateEventContent {
898 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
904 let event_type = content.event_type();
905
906 match content {
907 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
908 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
909 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
910 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
911 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
912 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
913 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
914 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
915 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
916 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
917 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
918 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
919 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
920 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
921 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
922 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
923 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
924 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
925 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
926 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
927 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
928 _ => Self::_Custom { event_type: event_type.to_string() },
929 }
930 }
931
932 pub fn event_type(&self) -> StateEventType {
934 match self {
935 Self::PolicyRuleRoom(c) => c.event_type(),
936 Self::PolicyRuleServer(c) => c.event_type(),
937 Self::PolicyRuleUser(c) => c.event_type(),
938 Self::RoomAliases(c) => c.event_type(),
939 Self::RoomAvatar(c) => c.event_type(),
940 Self::RoomCanonicalAlias(c) => c.event_type(),
941 Self::RoomCreate(c) => c.event_type(),
942 Self::RoomEncryption(c) => c.event_type(),
943 Self::RoomGuestAccess(c) => c.event_type(),
944 Self::RoomHistoryVisibility(c) => c.event_type(),
945 Self::RoomJoinRules(c) => c.event_type(),
946 Self::RoomName(c) => c.event_type(),
947 Self::RoomPinnedEvents(c) => c.event_type(),
948 Self::RoomPowerLevels(c) => c.event_type(),
949 Self::RoomServerAcl(c) => c.event_type(),
950 Self::RoomThirdPartyInvite(c) => c.event_type(),
951 Self::RoomTombstone(c) => c.event_type(),
952 Self::RoomTopic(c) => c.event_type(),
953 Self::SpaceChild(c) => c.event_type(),
954 Self::SpaceParent(c) => c.event_type(),
955 Self::_Custom { event_type } => event_type.as_str().into(),
956 }
957 }
958
959 fn redact(&self, rules: &RedactionRules) -> Self {
960 match self {
961 Self::PolicyRuleRoom(c) => {
962 Self::PolicyRuleRoom(FullStateEventContent::Redacted(c.clone().redact(rules)))
963 }
964 Self::PolicyRuleServer(c) => {
965 Self::PolicyRuleServer(FullStateEventContent::Redacted(c.clone().redact(rules)))
966 }
967 Self::PolicyRuleUser(c) => {
968 Self::PolicyRuleUser(FullStateEventContent::Redacted(c.clone().redact(rules)))
969 }
970 Self::RoomAliases(c) => {
971 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(rules)))
972 }
973 Self::RoomAvatar(c) => {
974 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(rules)))
975 }
976 Self::RoomCanonicalAlias(c) => {
977 Self::RoomCanonicalAlias(FullStateEventContent::Redacted(c.clone().redact(rules)))
978 }
979 Self::RoomCreate(c) => {
980 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(rules)))
981 }
982 Self::RoomEncryption(c) => {
983 Self::RoomEncryption(FullStateEventContent::Redacted(c.clone().redact(rules)))
984 }
985 Self::RoomGuestAccess(c) => {
986 Self::RoomGuestAccess(FullStateEventContent::Redacted(c.clone().redact(rules)))
987 }
988 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
989 FullStateEventContent::Redacted(c.clone().redact(rules)),
990 ),
991 Self::RoomJoinRules(c) => {
992 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(rules)))
993 }
994 Self::RoomName(c) => {
995 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(rules)))
996 }
997 Self::RoomPinnedEvents(c) => {
998 Self::RoomPinnedEvents(FullStateEventContent::Redacted(c.clone().redact(rules)))
999 }
1000 Self::RoomPowerLevels(c) => {
1001 Self::RoomPowerLevels(FullStateEventContent::Redacted(c.clone().redact(rules)))
1002 }
1003 Self::RoomServerAcl(c) => {
1004 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(rules)))
1005 }
1006 Self::RoomThirdPartyInvite(c) => {
1007 Self::RoomThirdPartyInvite(FullStateEventContent::Redacted(c.clone().redact(rules)))
1008 }
1009 Self::RoomTombstone(c) => {
1010 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(rules)))
1011 }
1012 Self::RoomTopic(c) => {
1013 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(rules)))
1014 }
1015 Self::SpaceChild(c) => {
1016 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(rules)))
1017 }
1018 Self::SpaceParent(c) => {
1019 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(rules)))
1020 }
1021 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
1022 }
1023 }
1024}
1025
1026#[derive(Clone, Debug)]
1028pub struct OtherState {
1029 pub(in crate::timeline) state_key: String,
1030 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
1031}
1032
1033impl OtherState {
1034 pub fn state_key(&self) -> &str {
1036 &self.state_key
1037 }
1038
1039 pub fn content(&self) -> &AnyOtherFullStateEventContent {
1041 &self.content
1042 }
1043
1044 fn redact(&self, rules: &RedactionRules) -> Self {
1045 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
1046 }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051 use assert_matches2::assert_let;
1052 use matrix_sdk_test::ALICE;
1053 use ruma::{
1054 assign,
1055 events::{
1056 FullStateEventContent,
1057 room::member::{MembershipState, RoomMemberEventContent},
1058 },
1059 room_version_rules::RedactionRules,
1060 };
1061
1062 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1063
1064 #[test]
1065 fn redact_membership_change() {
1066 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1067 user_id: ALICE.to_owned(),
1068 content: FullStateEventContent::Original {
1069 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1070 reason: Some("🤬".to_owned()),
1071 }),
1072 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
1073 },
1074 change: Some(MembershipChange::Banned),
1075 });
1076
1077 let redacted = content.redact(&RedactionRules::V11);
1078 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1079 assert_eq!(inner.change, Some(MembershipChange::Banned));
1080 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
1081 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1082 }
1083}