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