1use std::sync::Arc;
16
17use as_variant::as_variant;
18use matrix_sdk::crypto::types::events::UtdCause;
19use matrix_sdk_base::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent};
20use ruma::{
21 events::{
22 call::{invite::SyncCallInviteEvent, notify::SyncCallNotifyEvent},
23 policy::rule::{
24 room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
25 user::PolicyRuleUserEventContent,
26 },
27 poll::unstable_start::{
28 NewUnstablePollStartEventContent, SyncUnstablePollStartEvent,
29 UnstablePollStartEventContent,
30 },
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 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
52 sticker::{StickerEventContent, SyncStickerEvent},
53 AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent, Mentions,
54 MessageLikeEventType, StateEventType,
55 },
56 html::RemoveReplyFallback,
57 OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, RoomVersionId, UserId,
58};
59use tracing::warn;
60
61mod message;
62mod msg_like;
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 polls::{PollResult, PollState},
76 reply::{EmbeddedEvent, InReplyToDetails},
77};
78use super::ReactionsByKeyBySender;
79
80#[derive(Clone, Debug)]
82pub enum TimelineItemContent {
83 MsgLike(MsgLikeContent),
84
85 MembershipChange(RoomMembershipChange),
87
88 ProfileChange(MemberProfileChange),
90
91 OtherState(OtherState),
93
94 FailedToParseMessageLike {
96 event_type: MessageLikeEventType,
98
99 error: Arc<serde_json::Error>,
101 },
102
103 FailedToParseState {
105 event_type: StateEventType,
107
108 state_key: String,
110
111 error: Arc<serde_json::Error>,
113 },
114
115 CallInvite,
117
118 CallNotify,
120}
121
122impl TimelineItemContent {
123 pub(crate) fn from_latest_event_content(
127 event: AnySyncTimelineEvent,
128 power_levels_info: Option<(&UserId, &RoomPowerLevels)>,
129 ) -> Option<TimelineItemContent> {
130 match is_suitable_for_latest_event(&event, power_levels_info) {
131 PossibleLatestEvent::YesRoomMessage(m) => {
132 Some(Self::from_suitable_latest_event_content(m))
133 }
134 PossibleLatestEvent::YesSticker(s) => {
135 Some(Self::from_suitable_latest_sticker_content(s))
136 }
137 PossibleLatestEvent::YesPoll(poll) => {
138 Some(Self::from_suitable_latest_poll_event_content(poll))
139 }
140 PossibleLatestEvent::YesCallInvite(call_invite) => {
141 Some(Self::from_suitable_latest_call_invite_content(call_invite))
142 }
143 PossibleLatestEvent::YesCallNotify(call_notify) => {
144 Some(Self::from_suitable_latest_call_notify_content(call_notify))
145 }
146 PossibleLatestEvent::NoUnsupportedEventType => {
147 warn!("Found a state event cached as latest_event! ID={}", event.event_id());
149 None
150 }
151 PossibleLatestEvent::NoUnsupportedMessageLikeType => {
152 warn!(
154 "Found an event cached as latest_event, but I don't know how \
155 to wrap it in a TimelineItemContent. type={}, ID={}",
156 event.event_type().to_string(),
157 event.event_id()
158 );
159 None
160 }
161 PossibleLatestEvent::YesKnockedStateEvent(member) => {
162 Some(Self::from_suitable_latest_knock_state_event_content(member))
163 }
164 PossibleLatestEvent::NoEncrypted => {
165 warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
166 None
167 }
168 }
169 }
170
171 fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent {
175 match event {
176 SyncRoomMessageEvent::Original(event) => {
177 let event_content = event.content.clone();
179
180 let edit = event
182 .unsigned
183 .relations
184 .replace
185 .as_ref()
186 .and_then(|boxed| match &boxed.content.relates_to {
187 Some(Relation::Replacement(re)) => Some(re.new_content.clone()),
188 _ => {
189 warn!("got m.room.message event with an edit without a valid m.replace relation");
190 None
191 }
192 });
193
194 let reactions = Default::default();
196 let thread_root = None;
197 let in_reply_to = None;
198 let thread_summary = None;
199
200 let msglike = MsgLikeContent {
201 kind: MsgLikeKind::Message(Message::from_event(
202 event_content.msgtype,
203 event_content.mentions,
204 edit,
205 RemoveReplyFallback::Yes,
206 )),
207 reactions,
208 thread_root,
209 in_reply_to,
210 thread_summary,
211 };
212
213 TimelineItemContent::MsgLike(msglike)
214 }
215
216 SyncRoomMessageEvent::Redacted(_) => {
217 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
218 }
219 }
220 }
221
222 fn from_suitable_latest_knock_state_event_content(
223 event: &SyncRoomMemberEvent,
224 ) -> TimelineItemContent {
225 match event {
226 SyncRoomMemberEvent::Original(event) => {
227 let content = event.content.clone();
228 let prev_content = event.prev_content().cloned();
229 TimelineItemContent::room_member(
230 event.state_key.to_owned(),
231 FullStateEventContent::Original { content, prev_content },
232 event.sender.to_owned(),
233 )
234 }
235 SyncRoomMemberEvent::Redacted(_) => {
236 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
237 }
238 }
239 }
240
241 fn from_suitable_latest_sticker_content(event: &SyncStickerEvent) -> TimelineItemContent {
245 match event {
246 SyncStickerEvent::Original(event) => {
247 let event_content = event.content.clone();
249
250 let reactions = Default::default();
252 let thread_root = None;
253 let in_reply_to = None;
254 let thread_summary = None;
255
256 let msglike = MsgLikeContent {
257 kind: MsgLikeKind::Sticker(Sticker { content: event_content }),
258 reactions,
259 thread_root,
260 in_reply_to,
261 thread_summary,
262 };
263
264 TimelineItemContent::MsgLike(msglike)
265 }
266 SyncStickerEvent::Redacted(_) => {
267 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
268 }
269 }
270 }
271
272 fn from_suitable_latest_poll_event_content(
275 event: &SyncUnstablePollStartEvent,
276 ) -> TimelineItemContent {
277 let SyncUnstablePollStartEvent::Original(event) = event else {
278 return TimelineItemContent::MsgLike(MsgLikeContent::redacted());
279 };
280
281 let edit =
283 event.unsigned.relations.replace.as_ref().and_then(|boxed| match &boxed.content {
284 UnstablePollStartEventContent::Replacement(re) => {
285 Some(re.relates_to.new_content.clone())
286 }
287 _ => {
288 warn!("got poll event with an edit without a valid m.replace relation");
289 None
290 }
291 });
292
293 let mut poll = PollState::new(NewUnstablePollStartEventContent::new(
294 event.content.poll_start().clone(),
295 ));
296 if let Some(edit) = edit {
297 poll = poll.edit(edit).expect("the poll can't be ended yet!"); }
299
300 let reactions = Default::default();
302 let thread_root = None;
303 let in_reply_to = None;
304 let thread_summary = None;
305
306 let msglike = MsgLikeContent {
307 kind: MsgLikeKind::Poll(poll),
308 reactions,
309 thread_root,
310 in_reply_to,
311 thread_summary,
312 };
313
314 TimelineItemContent::MsgLike(msglike)
315 }
316
317 fn from_suitable_latest_call_invite_content(
318 event: &SyncCallInviteEvent,
319 ) -> TimelineItemContent {
320 match event {
321 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
322 SyncCallInviteEvent::Redacted(_) => {
323 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
324 }
325 }
326 }
327
328 fn from_suitable_latest_call_notify_content(
329 event: &SyncCallNotifyEvent,
330 ) -> TimelineItemContent {
331 match event {
332 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
333 SyncCallNotifyEvent::Redacted(_) => {
334 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
335 }
336 }
337 }
338
339 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
340 as_variant!(self, TimelineItemContent::MsgLike)
341 }
342
343 pub fn as_message(&self) -> Option<&Message> {
346 as_variant!(self, Self::MsgLike(MsgLikeContent {
347 kind: MsgLikeKind::Message(message),
348 ..
349 }) => message)
350 }
351
352 pub fn is_message(&self) -> bool {
355 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
356 }
357
358 pub fn as_poll(&self) -> Option<&PollState> {
361 as_variant!(self, Self::MsgLike(MsgLikeContent {
362 kind: MsgLikeKind::Poll(poll_state),
363 ..
364 }) => poll_state)
365 }
366
367 pub fn is_poll(&self) -> bool {
370 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
371 }
372
373 pub fn as_sticker(&self) -> Option<&Sticker> {
374 as_variant!(
375 self,
376 Self::MsgLike(MsgLikeContent {
377 kind: MsgLikeKind::Sticker(sticker),
378 ..
379 }) => sticker
380 )
381 }
382
383 pub fn is_sticker(&self) -> bool {
386 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
387 }
388
389 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
392 as_variant!(
393 self,
394 Self::MsgLike(MsgLikeContent {
395 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
396 ..
397 }) => encrypted_message
398 )
399 }
400
401 pub fn is_unable_to_decrypt(&self) -> bool {
404 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
405 }
406
407 pub fn is_redacted(&self) -> bool {
408 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
409 }
410
411 pub(crate) fn message(
414 msgtype: MessageType,
415 mentions: Option<Mentions>,
416 reactions: ReactionsByKeyBySender,
417 thread_root: Option<OwnedEventId>,
418 in_reply_to: Option<InReplyToDetails>,
419 thread_summary: Option<ThreadSummary>,
420 ) -> Self {
421 let remove_reply_fallback =
422 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
423
424 Self::MsgLike(MsgLikeContent {
425 kind: MsgLikeKind::Message(Message::from_event(
426 msgtype,
427 mentions,
428 None,
429 remove_reply_fallback,
430 )),
431 reactions,
432 thread_root,
433 in_reply_to,
434 thread_summary,
435 })
436 }
437
438 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
440 match self {
441 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
442 TimelineItemContent::MembershipChange(_) => "a membership change",
443 TimelineItemContent::ProfileChange(_) => "a profile change",
444 TimelineItemContent::OtherState(_) => "a state event",
445 TimelineItemContent::FailedToParseMessageLike { .. }
446 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
447 TimelineItemContent::CallInvite => "a call invite",
448 TimelineItemContent::CallNotify => "a call notification",
449 }
450 }
451
452 pub(crate) fn room_member(
453 user_id: OwnedUserId,
454 full_content: FullStateEventContent<RoomMemberEventContent>,
455 sender: OwnedUserId,
456 ) -> Self {
457 use ruma::events::room::member::MembershipChange as MChange;
458 match &full_content {
459 FullStateEventContent::Original { content, prev_content } => {
460 let membership_change = content.membership_change(
461 prev_content.as_ref().map(|c| c.details()),
462 &sender,
463 &user_id,
464 );
465
466 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
467 membership_change
468 {
469 Self::ProfileChange(MemberProfileChange {
470 user_id,
471 displayname_change: displayname_change.map(|c| Change {
472 new: c.new.map(ToOwned::to_owned),
473 old: c.old.map(ToOwned::to_owned),
474 }),
475 avatar_url_change: avatar_url_change.map(|c| Change {
476 new: c.new.map(ToOwned::to_owned),
477 old: c.old.map(ToOwned::to_owned),
478 }),
479 })
480 } else {
481 let change = match membership_change {
482 MChange::None => MembershipChange::None,
483 MChange::Error => MembershipChange::Error,
484 MChange::Joined => MembershipChange::Joined,
485 MChange::Left => MembershipChange::Left,
486 MChange::Banned => MembershipChange::Banned,
487 MChange::Unbanned => MembershipChange::Unbanned,
488 MChange::Kicked => MembershipChange::Kicked,
489 MChange::Invited => MembershipChange::Invited,
490 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
491 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
492 MChange::InvitationRejected => MembershipChange::InvitationRejected,
493 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
494 MChange::Knocked => MembershipChange::Knocked,
495 MChange::KnockAccepted => MembershipChange::KnockAccepted,
496 MChange::KnockRetracted => MembershipChange::KnockRetracted,
497 MChange::KnockDenied => MembershipChange::KnockDenied,
498 MChange::ProfileChanged { .. } => unreachable!(),
499 _ => MembershipChange::NotImplemented,
500 };
501
502 Self::MembershipChange(RoomMembershipChange {
503 user_id,
504 content: full_content,
505 change: Some(change),
506 })
507 }
508 }
509 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
510 user_id,
511 content: full_content,
512 change: None,
513 }),
514 }
515 }
516
517 pub(in crate::timeline) fn redact(&self, room_version: &RoomVersionId) -> Self {
518 match self {
519 Self::MsgLike(_) | Self::CallInvite | Self::CallNotify => {
520 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
521 }
522 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
523 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
524 Self::OtherState(ev) => Self::OtherState(ev.redact(room_version)),
525 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
526 }
527 }
528
529 pub fn thread_root(&self) -> Option<OwnedEventId> {
531 as_variant!(self, Self::MsgLike)?.thread_root.clone()
532 }
533
534 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
536 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
537 }
538
539 pub fn reactions(&self) -> Option<&ReactionsByKeyBySender> {
542 match self {
543 TimelineItemContent::MsgLike(msglike) => Some(&msglike.reactions),
544
545 TimelineItemContent::MembershipChange(..)
546 | TimelineItemContent::ProfileChange(..)
547 | TimelineItemContent::OtherState(..)
548 | TimelineItemContent::FailedToParseMessageLike { .. }
549 | TimelineItemContent::FailedToParseState { .. }
550 | TimelineItemContent::CallInvite
551 | TimelineItemContent::CallNotify => {
552 None
554 }
555 }
556 }
557
558 pub fn thread_summary(&self) -> Option<ThreadSummary> {
560 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
561 }
562
563 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
567 match self {
568 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
569
570 TimelineItemContent::MembershipChange(..)
571 | TimelineItemContent::ProfileChange(..)
572 | TimelineItemContent::OtherState(..)
573 | TimelineItemContent::FailedToParseMessageLike { .. }
574 | TimelineItemContent::FailedToParseState { .. }
575 | TimelineItemContent::CallInvite
576 | TimelineItemContent::CallNotify => {
577 None
579 }
580 }
581 }
582
583 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
584 let mut cloned = self.clone();
585 if let Some(r) = cloned.reactions_mut() {
586 *r = reactions;
587 }
588 cloned
589 }
590}
591
592#[derive(Clone, Debug)]
594pub enum EncryptedMessage {
595 OlmV1Curve25519AesSha2 {
598 sender_key: String,
600 },
601 MegolmV1AesSha2 {
603 #[deprecated = "this field still needs to be sent but should not be used when received"]
605 #[doc(hidden)] sender_key: String,
607
608 #[deprecated = "this field still needs to be sent but should not be used when received"]
610 #[doc(hidden)] device_id: OwnedDeviceId,
612
613 session_id: String,
615
616 cause: UtdCause,
619 },
620 Unknown,
622}
623
624impl EncryptedMessage {
625 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
626 match content.scheme {
627 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
628 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
629 }
630 #[allow(deprecated)]
631 EncryptedEventScheme::MegolmV1AesSha2(s) => {
632 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
633
634 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
635 }
636 _ => Self::Unknown,
637 }
638 }
639
640 pub(crate) fn session_id(&self) -> Option<&str> {
643 match self {
644 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
645 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
646 EncryptedMessage::Unknown => None,
647 }
648 }
649}
650
651#[derive(Clone, Debug)]
653pub struct Sticker {
654 pub(in crate::timeline) content: StickerEventContent,
655}
656
657impl Sticker {
658 pub fn content(&self) -> &StickerEventContent {
660 &self.content
661 }
662}
663
664#[derive(Clone, Debug)]
666pub struct RoomMembershipChange {
667 pub(in crate::timeline) user_id: OwnedUserId,
668 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
669 pub(in crate::timeline) change: Option<MembershipChange>,
670}
671
672impl RoomMembershipChange {
673 pub fn user_id(&self) -> &UserId {
675 &self.user_id
676 }
677
678 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
680 &self.content
681 }
682
683 pub fn display_name(&self) -> Option<String> {
686 if let FullStateEventContent::Original { content, prev_content } = &self.content {
687 content
688 .displayname
689 .as_ref()
690 .or_else(|| {
691 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
692 })
693 .cloned()
694 } else {
695 None
696 }
697 }
698
699 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
702 if let FullStateEventContent::Original { content, prev_content } = &self.content {
703 content
704 .avatar_url
705 .as_ref()
706 .or_else(|| {
707 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
708 })
709 .cloned()
710 } else {
711 None
712 }
713 }
714
715 pub fn change(&self) -> Option<MembershipChange> {
723 self.change
724 }
725
726 fn redact(&self, room_version: &RoomVersionId) -> Self {
727 Self {
728 user_id: self.user_id.clone(),
729 content: FullStateEventContent::Redacted(self.content.clone().redact(room_version)),
730 change: self.change,
731 }
732 }
733}
734
735#[derive(Clone, Copy, Debug, PartialEq, Eq)]
737pub enum MembershipChange {
738 None,
740
741 Error,
743
744 Joined,
746
747 Left,
749
750 Banned,
752
753 Unbanned,
755
756 Kicked,
758
759 Invited,
761
762 KickedAndBanned,
764
765 InvitationAccepted,
767
768 InvitationRejected,
770
771 InvitationRevoked,
773
774 Knocked,
776
777 KnockAccepted,
779
780 KnockRetracted,
782
783 KnockDenied,
785
786 NotImplemented,
788}
789
790#[derive(Clone, Debug)]
795pub struct MemberProfileChange {
796 pub(in crate::timeline) user_id: OwnedUserId,
797 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
798 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
799}
800
801impl MemberProfileChange {
802 pub fn user_id(&self) -> &UserId {
804 &self.user_id
805 }
806
807 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
809 self.displayname_change.as_ref()
810 }
811
812 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
814 self.avatar_url_change.as_ref()
815 }
816
817 fn redact(&self) -> Self {
818 Self {
819 user_id: self.user_id.clone(),
820 displayname_change: None,
825 avatar_url_change: None,
826 }
827 }
828}
829
830#[derive(Clone, Debug)]
833pub enum AnyOtherFullStateEventContent {
834 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
836
837 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
839
840 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
842
843 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
845
846 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
848
849 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
851
852 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
854
855 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
857
858 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
860
861 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
863
864 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
866
867 RoomName(FullStateEventContent<RoomNameEventContent>),
869
870 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
872
873 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
875
876 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
878
879 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
881
882 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
884
885 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
887
888 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
890
891 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
893
894 #[doc(hidden)]
895 _Custom { event_type: String },
896}
897
898impl AnyOtherFullStateEventContent {
899 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
905 let event_type = content.event_type();
906
907 match content {
908 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
909 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
910 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
911 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
912 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
913 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
914 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
915 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
916 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
917 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
918 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
919 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
920 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
921 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
922 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
923 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
924 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
925 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
926 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
927 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
928 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
929 _ => Self::_Custom { event_type: event_type.to_string() },
930 }
931 }
932
933 pub fn event_type(&self) -> StateEventType {
935 match self {
936 Self::PolicyRuleRoom(c) => c.event_type(),
937 Self::PolicyRuleServer(c) => c.event_type(),
938 Self::PolicyRuleUser(c) => c.event_type(),
939 Self::RoomAliases(c) => c.event_type(),
940 Self::RoomAvatar(c) => c.event_type(),
941 Self::RoomCanonicalAlias(c) => c.event_type(),
942 Self::RoomCreate(c) => c.event_type(),
943 Self::RoomEncryption(c) => c.event_type(),
944 Self::RoomGuestAccess(c) => c.event_type(),
945 Self::RoomHistoryVisibility(c) => c.event_type(),
946 Self::RoomJoinRules(c) => c.event_type(),
947 Self::RoomName(c) => c.event_type(),
948 Self::RoomPinnedEvents(c) => c.event_type(),
949 Self::RoomPowerLevels(c) => c.event_type(),
950 Self::RoomServerAcl(c) => c.event_type(),
951 Self::RoomThirdPartyInvite(c) => c.event_type(),
952 Self::RoomTombstone(c) => c.event_type(),
953 Self::RoomTopic(c) => c.event_type(),
954 Self::SpaceChild(c) => c.event_type(),
955 Self::SpaceParent(c) => c.event_type(),
956 Self::_Custom { event_type } => event_type.as_str().into(),
957 }
958 }
959
960 fn redact(&self, room_version: &RoomVersionId) -> Self {
961 match self {
962 Self::PolicyRuleRoom(c) => Self::PolicyRuleRoom(FullStateEventContent::Redacted(
963 c.clone().redact(room_version),
964 )),
965 Self::PolicyRuleServer(c) => Self::PolicyRuleServer(FullStateEventContent::Redacted(
966 c.clone().redact(room_version),
967 )),
968 Self::PolicyRuleUser(c) => Self::PolicyRuleUser(FullStateEventContent::Redacted(
969 c.clone().redact(room_version),
970 )),
971 Self::RoomAliases(c) => {
972 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(room_version)))
973 }
974 Self::RoomAvatar(c) => {
975 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(room_version)))
976 }
977 Self::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(
978 FullStateEventContent::Redacted(c.clone().redact(room_version)),
979 ),
980 Self::RoomCreate(c) => {
981 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(room_version)))
982 }
983 Self::RoomEncryption(c) => Self::RoomEncryption(FullStateEventContent::Redacted(
984 c.clone().redact(room_version),
985 )),
986 Self::RoomGuestAccess(c) => Self::RoomGuestAccess(FullStateEventContent::Redacted(
987 c.clone().redact(room_version),
988 )),
989 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
990 FullStateEventContent::Redacted(c.clone().redact(room_version)),
991 ),
992 Self::RoomJoinRules(c) => {
993 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(room_version)))
994 }
995 Self::RoomName(c) => {
996 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(room_version)))
997 }
998 Self::RoomPinnedEvents(c) => Self::RoomPinnedEvents(FullStateEventContent::Redacted(
999 c.clone().redact(room_version),
1000 )),
1001 Self::RoomPowerLevels(c) => Self::RoomPowerLevels(FullStateEventContent::Redacted(
1002 c.clone().redact(room_version),
1003 )),
1004 Self::RoomServerAcl(c) => {
1005 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1006 }
1007 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
1008 FullStateEventContent::Redacted(c.clone().redact(room_version)),
1009 ),
1010 Self::RoomTombstone(c) => {
1011 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1012 }
1013 Self::RoomTopic(c) => {
1014 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1015 }
1016 Self::SpaceChild(c) => {
1017 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1018 }
1019 Self::SpaceParent(c) => {
1020 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1021 }
1022 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
1023 }
1024 }
1025}
1026
1027#[derive(Clone, Debug)]
1029pub struct OtherState {
1030 pub(in crate::timeline) state_key: String,
1031 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
1032}
1033
1034impl OtherState {
1035 pub fn state_key(&self) -> &str {
1037 &self.state_key
1038 }
1039
1040 pub fn content(&self) -> &AnyOtherFullStateEventContent {
1042 &self.content
1043 }
1044
1045 fn redact(&self, room_version: &RoomVersionId) -> Self {
1046 Self { state_key: self.state_key.clone(), content: self.content.redact(room_version) }
1047 }
1048}
1049
1050#[cfg(test)]
1051mod tests {
1052 use assert_matches2::assert_let;
1053 use matrix_sdk_test::ALICE;
1054 use ruma::{
1055 assign,
1056 events::{
1057 room::member::{MembershipState, RoomMemberEventContent},
1058 FullStateEventContent,
1059 },
1060 RoomVersionId,
1061 };
1062
1063 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1064
1065 #[test]
1066 fn redact_membership_change() {
1067 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1068 user_id: ALICE.to_owned(),
1069 content: FullStateEventContent::Original {
1070 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1071 reason: Some("🤬".to_owned()),
1072 }),
1073 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
1074 },
1075 change: Some(MembershipChange::Banned),
1076 });
1077
1078 let redacted = content.redact(&RoomVersionId::V11);
1079 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1080 assert_eq!(inner.change, Some(MembershipChange::Banned));
1081 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
1082 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1083 }
1084}