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, notify::SyncCallNotifyEvent},
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 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
55 sticker::{StickerEventContent, SyncStickerEvent},
56 },
57 html::RemoveReplyFallback,
58 room_version_rules::RedactionRules,
59};
60use tracing::warn;
61
62mod message;
63mod msg_like;
64pub(crate) mod pinned_events;
65mod polls;
66mod reply;
67
68pub use pinned_events::RoomPinnedEventsChange;
69
70pub(in crate::timeline) use self::message::{
71 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
72};
73pub use self::{
74 message::Message,
75 msg_like::{MsgLikeContent, MsgLikeKind, ThreadSummary},
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 CallNotify,
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::YesCallNotify(call_notify) => {
145 Some(Self::from_suitable_latest_call_notify_content(call_notify))
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(NewUnstablePollStartEventContent::new(
295 event.content.poll_start().clone(),
296 ));
297 if let Some(edit) = edit {
298 poll = poll.edit(edit).expect("the poll can't be ended yet!"); }
300
301 let reactions = Default::default();
303 let thread_root = None;
304 let in_reply_to = None;
305 let thread_summary = None;
306
307 let msglike = MsgLikeContent {
308 kind: MsgLikeKind::Poll(poll),
309 reactions,
310 thread_root,
311 in_reply_to,
312 thread_summary,
313 };
314
315 TimelineItemContent::MsgLike(msglike)
316 }
317
318 fn from_suitable_latest_call_invite_content(
319 event: &SyncCallInviteEvent,
320 ) -> TimelineItemContent {
321 match event {
322 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
323 SyncCallInviteEvent::Redacted(_) => {
324 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
325 }
326 }
327 }
328
329 fn from_suitable_latest_call_notify_content(
330 event: &SyncCallNotifyEvent,
331 ) -> TimelineItemContent {
332 match event {
333 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
334 SyncCallNotifyEvent::Redacted(_) => {
335 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
336 }
337 }
338 }
339
340 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
341 as_variant!(self, TimelineItemContent::MsgLike)
342 }
343
344 pub fn as_message(&self) -> Option<&Message> {
347 as_variant!(self, Self::MsgLike(MsgLikeContent {
348 kind: MsgLikeKind::Message(message),
349 ..
350 }) => message)
351 }
352
353 pub fn is_message(&self) -> bool {
356 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
357 }
358
359 pub fn as_poll(&self) -> Option<&PollState> {
362 as_variant!(self, Self::MsgLike(MsgLikeContent {
363 kind: MsgLikeKind::Poll(poll_state),
364 ..
365 }) => poll_state)
366 }
367
368 pub fn is_poll(&self) -> bool {
371 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
372 }
373
374 pub fn as_sticker(&self) -> Option<&Sticker> {
375 as_variant!(
376 self,
377 Self::MsgLike(MsgLikeContent {
378 kind: MsgLikeKind::Sticker(sticker),
379 ..
380 }) => sticker
381 )
382 }
383
384 pub fn is_sticker(&self) -> bool {
387 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
388 }
389
390 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
393 as_variant!(
394 self,
395 Self::MsgLike(MsgLikeContent {
396 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
397 ..
398 }) => encrypted_message
399 )
400 }
401
402 pub fn is_unable_to_decrypt(&self) -> bool {
405 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
406 }
407
408 pub fn is_redacted(&self) -> bool {
409 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
410 }
411
412 pub(crate) fn message(
415 msgtype: MessageType,
416 mentions: Option<Mentions>,
417 reactions: ReactionsByKeyBySender,
418 thread_root: Option<OwnedEventId>,
419 in_reply_to: Option<InReplyToDetails>,
420 thread_summary: Option<ThreadSummary>,
421 ) -> Self {
422 let remove_reply_fallback =
423 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
424
425 Self::MsgLike(MsgLikeContent {
426 kind: MsgLikeKind::Message(Message::from_event(
427 msgtype,
428 mentions,
429 None,
430 remove_reply_fallback,
431 )),
432 reactions,
433 thread_root,
434 in_reply_to,
435 thread_summary,
436 })
437 }
438
439 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
441 match self {
442 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
443 TimelineItemContent::MembershipChange(_) => "a membership change",
444 TimelineItemContent::ProfileChange(_) => "a profile change",
445 TimelineItemContent::OtherState(_) => "a state event",
446 TimelineItemContent::FailedToParseMessageLike { .. }
447 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
448 TimelineItemContent::CallInvite => "a call invite",
449 TimelineItemContent::CallNotify => "a call notification",
450 }
451 }
452
453 pub(crate) fn room_member(
454 user_id: OwnedUserId,
455 full_content: FullStateEventContent<RoomMemberEventContent>,
456 sender: OwnedUserId,
457 ) -> Self {
458 use ruma::events::room::member::MembershipChange as MChange;
459 match &full_content {
460 FullStateEventContent::Original { content, prev_content } => {
461 let membership_change = content.membership_change(
462 prev_content.as_ref().map(|c| c.details()),
463 &sender,
464 &user_id,
465 );
466
467 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
468 membership_change
469 {
470 Self::ProfileChange(MemberProfileChange {
471 user_id,
472 displayname_change: displayname_change.map(|c| Change {
473 new: c.new.map(ToOwned::to_owned),
474 old: c.old.map(ToOwned::to_owned),
475 }),
476 avatar_url_change: avatar_url_change.map(|c| Change {
477 new: c.new.map(ToOwned::to_owned),
478 old: c.old.map(ToOwned::to_owned),
479 }),
480 })
481 } else {
482 let change = match membership_change {
483 MChange::None => MembershipChange::None,
484 MChange::Error => MembershipChange::Error,
485 MChange::Joined => MembershipChange::Joined,
486 MChange::Left => MembershipChange::Left,
487 MChange::Banned => MembershipChange::Banned,
488 MChange::Unbanned => MembershipChange::Unbanned,
489 MChange::Kicked => MembershipChange::Kicked,
490 MChange::Invited => MembershipChange::Invited,
491 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
492 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
493 MChange::InvitationRejected => MembershipChange::InvitationRejected,
494 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
495 MChange::Knocked => MembershipChange::Knocked,
496 MChange::KnockAccepted => MembershipChange::KnockAccepted,
497 MChange::KnockRetracted => MembershipChange::KnockRetracted,
498 MChange::KnockDenied => MembershipChange::KnockDenied,
499 MChange::ProfileChanged { .. } => unreachable!(),
500 _ => MembershipChange::NotImplemented,
501 };
502
503 Self::MembershipChange(RoomMembershipChange {
504 user_id,
505 content: full_content,
506 change: Some(change),
507 })
508 }
509 }
510 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
511 user_id,
512 content: full_content,
513 change: None,
514 }),
515 }
516 }
517
518 pub(in crate::timeline) fn redact(&self, rules: &RedactionRules) -> Self {
519 match self {
520 Self::MsgLike(_) | Self::CallInvite | Self::CallNotify => {
521 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
522 }
523 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(rules)),
524 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
525 Self::OtherState(ev) => Self::OtherState(ev.redact(rules)),
526 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
527 }
528 }
529
530 pub fn thread_root(&self) -> Option<OwnedEventId> {
532 as_variant!(self, Self::MsgLike)?.thread_root.clone()
533 }
534
535 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
537 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
538 }
539
540 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
543 match self {
544 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
545
546 TimelineItemContent::MembershipChange(..)
547 | TimelineItemContent::ProfileChange(..)
548 | TimelineItemContent::OtherState(..)
549 | TimelineItemContent::FailedToParseMessageLike { .. }
550 | TimelineItemContent::FailedToParseState { .. }
551 | TimelineItemContent::CallInvite
552 | TimelineItemContent::CallNotify => {
553 None
555 }
556 }
557 }
558
559 pub fn thread_summary(&self) -> Option<ThreadSummary> {
561 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
562 }
563
564 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
568 match self {
569 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
570
571 TimelineItemContent::MembershipChange(..)
572 | TimelineItemContent::ProfileChange(..)
573 | TimelineItemContent::OtherState(..)
574 | TimelineItemContent::FailedToParseMessageLike { .. }
575 | TimelineItemContent::FailedToParseState { .. }
576 | TimelineItemContent::CallInvite
577 | TimelineItemContent::CallNotify => {
578 None
580 }
581 }
582 }
583
584 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
585 let mut cloned = self.clone();
586 if let Some(r) = cloned.reactions_mut() {
587 *r = reactions;
588 }
589 cloned
590 }
591}
592
593#[derive(Clone, Debug)]
595pub enum EncryptedMessage {
596 OlmV1Curve25519AesSha2 {
599 sender_key: String,
601 },
602 MegolmV1AesSha2 {
604 #[deprecated = "this field should still be sent but should not be used when received"]
606 #[doc(hidden)] sender_key: Option<String>,
608
609 #[deprecated = "this field should still be sent but should not be used when received"]
611 #[doc(hidden)] device_id: Option<OwnedDeviceId>,
613
614 session_id: String,
616
617 cause: UtdCause,
620 },
621 Unknown,
623}
624
625impl EncryptedMessage {
626 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
627 match content.scheme {
628 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
629 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
630 }
631 #[allow(deprecated)]
632 EncryptedEventScheme::MegolmV1AesSha2(s) => {
633 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
634
635 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
636 }
637 _ => Self::Unknown,
638 }
639 }
640
641 pub(crate) fn session_id(&self) -> Option<&str> {
644 match self {
645 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
646 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
647 EncryptedMessage::Unknown => None,
648 }
649 }
650}
651
652#[derive(Clone, Debug)]
654pub struct Sticker {
655 pub(in crate::timeline) content: StickerEventContent,
656}
657
658impl Sticker {
659 pub fn content(&self) -> &StickerEventContent {
661 &self.content
662 }
663}
664
665#[derive(Clone, Debug)]
667pub struct RoomMembershipChange {
668 pub(in crate::timeline) user_id: OwnedUserId,
669 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
670 pub(in crate::timeline) change: Option<MembershipChange>,
671}
672
673impl RoomMembershipChange {
674 pub fn user_id(&self) -> &UserId {
676 &self.user_id
677 }
678
679 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
681 &self.content
682 }
683
684 pub fn display_name(&self) -> Option<String> {
687 if let FullStateEventContent::Original { content, prev_content } = &self.content {
688 content
689 .displayname
690 .as_ref()
691 .or_else(|| {
692 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
693 })
694 .cloned()
695 } else {
696 None
697 }
698 }
699
700 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
703 if let FullStateEventContent::Original { content, prev_content } = &self.content {
704 content
705 .avatar_url
706 .as_ref()
707 .or_else(|| {
708 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
709 })
710 .cloned()
711 } else {
712 None
713 }
714 }
715
716 pub fn change(&self) -> Option<MembershipChange> {
724 self.change
725 }
726
727 fn redact(&self, rules: &RedactionRules) -> Self {
728 Self {
729 user_id: self.user_id.clone(),
730 content: FullStateEventContent::Redacted(self.content.clone().redact(rules)),
731 change: self.change,
732 }
733 }
734}
735
736#[derive(Clone, Copy, Debug, PartialEq, Eq)]
738pub enum MembershipChange {
739 None,
741
742 Error,
744
745 Joined,
747
748 Left,
750
751 Banned,
753
754 Unbanned,
756
757 Kicked,
759
760 Invited,
762
763 KickedAndBanned,
765
766 InvitationAccepted,
768
769 InvitationRejected,
771
772 InvitationRevoked,
774
775 Knocked,
777
778 KnockAccepted,
780
781 KnockRetracted,
783
784 KnockDenied,
786
787 NotImplemented,
789}
790
791#[derive(Clone, Debug)]
796pub struct MemberProfileChange {
797 pub(in crate::timeline) user_id: OwnedUserId,
798 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
799 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
800}
801
802impl MemberProfileChange {
803 pub fn user_id(&self) -> &UserId {
805 &self.user_id
806 }
807
808 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
810 self.displayname_change.as_ref()
811 }
812
813 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
815 self.avatar_url_change.as_ref()
816 }
817
818 fn redact(&self) -> Self {
819 Self {
820 user_id: self.user_id.clone(),
821 displayname_change: None,
826 avatar_url_change: None,
827 }
828 }
829}
830
831#[derive(Clone, Debug)]
834pub enum AnyOtherFullStateEventContent {
835 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
837
838 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
840
841 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
843
844 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
846
847 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
849
850 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
852
853 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
855
856 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
858
859 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
861
862 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
864
865 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
867
868 RoomName(FullStateEventContent<RoomNameEventContent>),
870
871 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
873
874 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
876
877 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
879
880 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
882
883 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
885
886 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
888
889 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
891
892 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
894
895 #[doc(hidden)]
896 _Custom { event_type: String },
897}
898
899impl AnyOtherFullStateEventContent {
900 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
906 let event_type = content.event_type();
907
908 match content {
909 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
910 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
911 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
912 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
913 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
914 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
915 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
916 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
917 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
918 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
919 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
920 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
921 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
922 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
923 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
924 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
925 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
926 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
927 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
928 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
929 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
930 _ => Self::_Custom { event_type: event_type.to_string() },
931 }
932 }
933
934 pub fn event_type(&self) -> StateEventType {
936 match self {
937 Self::PolicyRuleRoom(c) => c.event_type(),
938 Self::PolicyRuleServer(c) => c.event_type(),
939 Self::PolicyRuleUser(c) => c.event_type(),
940 Self::RoomAliases(c) => c.event_type(),
941 Self::RoomAvatar(c) => c.event_type(),
942 Self::RoomCanonicalAlias(c) => c.event_type(),
943 Self::RoomCreate(c) => c.event_type(),
944 Self::RoomEncryption(c) => c.event_type(),
945 Self::RoomGuestAccess(c) => c.event_type(),
946 Self::RoomHistoryVisibility(c) => c.event_type(),
947 Self::RoomJoinRules(c) => c.event_type(),
948 Self::RoomName(c) => c.event_type(),
949 Self::RoomPinnedEvents(c) => c.event_type(),
950 Self::RoomPowerLevels(c) => c.event_type(),
951 Self::RoomServerAcl(c) => c.event_type(),
952 Self::RoomThirdPartyInvite(c) => c.event_type(),
953 Self::RoomTombstone(c) => c.event_type(),
954 Self::RoomTopic(c) => c.event_type(),
955 Self::SpaceChild(c) => c.event_type(),
956 Self::SpaceParent(c) => c.event_type(),
957 Self::_Custom { event_type } => event_type.as_str().into(),
958 }
959 }
960
961 fn redact(&self, rules: &RedactionRules) -> Self {
962 match self {
963 Self::PolicyRuleRoom(c) => {
964 Self::PolicyRuleRoom(FullStateEventContent::Redacted(c.clone().redact(rules)))
965 }
966 Self::PolicyRuleServer(c) => {
967 Self::PolicyRuleServer(FullStateEventContent::Redacted(c.clone().redact(rules)))
968 }
969 Self::PolicyRuleUser(c) => {
970 Self::PolicyRuleUser(FullStateEventContent::Redacted(c.clone().redact(rules)))
971 }
972 Self::RoomAliases(c) => {
973 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(rules)))
974 }
975 Self::RoomAvatar(c) => {
976 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(rules)))
977 }
978 Self::RoomCanonicalAlias(c) => {
979 Self::RoomCanonicalAlias(FullStateEventContent::Redacted(c.clone().redact(rules)))
980 }
981 Self::RoomCreate(c) => {
982 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(rules)))
983 }
984 Self::RoomEncryption(c) => {
985 Self::RoomEncryption(FullStateEventContent::Redacted(c.clone().redact(rules)))
986 }
987 Self::RoomGuestAccess(c) => {
988 Self::RoomGuestAccess(FullStateEventContent::Redacted(c.clone().redact(rules)))
989 }
990 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
991 FullStateEventContent::Redacted(c.clone().redact(rules)),
992 ),
993 Self::RoomJoinRules(c) => {
994 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(rules)))
995 }
996 Self::RoomName(c) => {
997 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(rules)))
998 }
999 Self::RoomPinnedEvents(c) => {
1000 Self::RoomPinnedEvents(FullStateEventContent::Redacted(c.clone().redact(rules)))
1001 }
1002 Self::RoomPowerLevels(c) => {
1003 Self::RoomPowerLevels(FullStateEventContent::Redacted(c.clone().redact(rules)))
1004 }
1005 Self::RoomServerAcl(c) => {
1006 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(rules)))
1007 }
1008 Self::RoomThirdPartyInvite(c) => {
1009 Self::RoomThirdPartyInvite(FullStateEventContent::Redacted(c.clone().redact(rules)))
1010 }
1011 Self::RoomTombstone(c) => {
1012 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(rules)))
1013 }
1014 Self::RoomTopic(c) => {
1015 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(rules)))
1016 }
1017 Self::SpaceChild(c) => {
1018 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(rules)))
1019 }
1020 Self::SpaceParent(c) => {
1021 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(rules)))
1022 }
1023 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
1024 }
1025 }
1026}
1027
1028#[derive(Clone, Debug)]
1030pub struct OtherState {
1031 pub(in crate::timeline) state_key: String,
1032 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
1033}
1034
1035impl OtherState {
1036 pub fn state_key(&self) -> &str {
1038 &self.state_key
1039 }
1040
1041 pub fn content(&self) -> &AnyOtherFullStateEventContent {
1043 &self.content
1044 }
1045
1046 fn redact(&self, rules: &RedactionRules) -> Self {
1047 Self { state_key: self.state_key.clone(), content: self.content.redact(rules) }
1048 }
1049}
1050
1051#[cfg(test)]
1052mod tests {
1053 use assert_matches2::assert_let;
1054 use matrix_sdk_test::ALICE;
1055 use ruma::{
1056 assign,
1057 events::{
1058 FullStateEventContent,
1059 room::member::{MembershipState, RoomMemberEventContent},
1060 },
1061 room_version_rules::RedactionRules,
1062 };
1063
1064 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1065
1066 #[test]
1067 fn redact_membership_change() {
1068 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1069 user_id: ALICE.to_owned(),
1070 content: FullStateEventContent::Original {
1071 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1072 reason: Some("🤬".to_owned()),
1073 }),
1074 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
1075 },
1076 change: Some(MembershipChange::Banned),
1077 });
1078
1079 let redacted = content.redact(&RedactionRules::V11);
1080 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1081 assert_eq!(inner.change, Some(MembershipChange::Banned));
1082 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
1083 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1084 }
1085}