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::{
43 Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
44 SyncRoomMessageEvent,
45 },
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 AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent,
57 MessageLikeEventType, StateEventType,
58 },
59 html::RemoveReplyFallback,
60 OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, RoomVersionId, UserId,
61};
62use tracing::warn;
63
64mod message;
65mod msg_like;
66pub(crate) mod pinned_events;
67mod polls;
68mod reply;
69
70pub use pinned_events::RoomPinnedEventsChange;
71
72pub(in crate::timeline) use self::message::{
73 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
74};
75pub use self::{
76 message::Message,
77 msg_like::{MsgLikeContent, MsgLikeKind},
78 polls::{PollResult, PollState},
79 reply::{InReplyToDetails, RepliedToEvent},
80};
81use super::ReactionsByKeyBySender;
82
83#[derive(Clone, Debug)]
85pub enum TimelineItemContent {
86 MsgLike(MsgLikeContent),
87
88 MembershipChange(RoomMembershipChange),
90
91 ProfileChange(MemberProfileChange),
93
94 OtherState(OtherState),
96
97 FailedToParseMessageLike {
99 event_type: MessageLikeEventType,
101
102 error: Arc<serde_json::Error>,
104 },
105
106 FailedToParseState {
108 event_type: StateEventType,
110
111 state_key: String,
113
114 error: Arc<serde_json::Error>,
116 },
117
118 CallInvite,
120
121 CallNotify,
123}
124
125impl TimelineItemContent {
126 pub(crate) fn from_latest_event_content(
130 event: AnySyncTimelineEvent,
131 power_levels_info: Option<(&UserId, &RoomPowerLevels)>,
132 ) -> Option<TimelineItemContent> {
133 match is_suitable_for_latest_event(&event, power_levels_info) {
134 PossibleLatestEvent::YesRoomMessage(m) => {
135 Some(Self::from_suitable_latest_event_content(m))
136 }
137 PossibleLatestEvent::YesSticker(s) => {
138 Some(Self::from_suitable_latest_sticker_content(s))
139 }
140 PossibleLatestEvent::YesPoll(poll) => {
141 Some(Self::from_suitable_latest_poll_event_content(poll))
142 }
143 PossibleLatestEvent::YesCallInvite(call_invite) => {
144 Some(Self::from_suitable_latest_call_invite_content(call_invite))
145 }
146 PossibleLatestEvent::YesCallNotify(call_notify) => {
147 Some(Self::from_suitable_latest_call_notify_content(call_notify))
148 }
149 PossibleLatestEvent::NoUnsupportedEventType => {
150 warn!("Found a state event cached as latest_event! ID={}", event.event_id());
152 None
153 }
154 PossibleLatestEvent::NoUnsupportedMessageLikeType => {
155 warn!(
157 "Found an event cached as latest_event, but I don't know how \
158 to wrap it in a TimelineItemContent. type={}, ID={}",
159 event.event_type().to_string(),
160 event.event_id()
161 );
162 None
163 }
164 PossibleLatestEvent::YesKnockedStateEvent(member) => {
165 Some(Self::from_suitable_latest_knock_state_event_content(member))
166 }
167 PossibleLatestEvent::NoEncrypted => {
168 warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
169 None
170 }
171 }
172 }
173
174 fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent {
178 match event {
179 SyncRoomMessageEvent::Original(event) => {
180 let event_content = event.content.clone();
182
183 let edit = event
185 .unsigned
186 .relations
187 .replace
188 .as_ref()
189 .and_then(|boxed| match &boxed.content.relates_to {
190 Some(Relation::Replacement(re)) => Some(re.new_content.clone()),
191 _ => {
192 warn!("got m.room.message event with an edit without a valid m.replace relation");
193 None
194 }
195 });
196
197 let reactions = Default::default();
199 let thread_root = None;
200 let in_reply_to = None;
201
202 let msglike = MsgLikeContent {
203 kind: MsgLikeKind::Message(Message::from_event(
204 event_content,
205 edit,
206 RemoveReplyFallback::Yes,
207 )),
208 reactions,
209 thread_root,
210 in_reply_to,
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
255 let msglike = MsgLikeContent {
256 kind: MsgLikeKind::Sticker(Sticker { content: event_content }),
257 reactions,
258 thread_root,
259 in_reply_to,
260 };
261
262 TimelineItemContent::MsgLike(msglike)
263 }
264 SyncStickerEvent::Redacted(_) => {
265 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
266 }
267 }
268 }
269
270 fn from_suitable_latest_poll_event_content(
273 event: &SyncUnstablePollStartEvent,
274 ) -> TimelineItemContent {
275 let SyncUnstablePollStartEvent::Original(event) = event else {
276 return TimelineItemContent::MsgLike(MsgLikeContent::redacted());
277 };
278
279 let edit =
281 event.unsigned.relations.replace.as_ref().and_then(|boxed| match &boxed.content {
282 UnstablePollStartEventContent::Replacement(re) => {
283 Some(re.relates_to.new_content.clone())
284 }
285 _ => {
286 warn!("got poll event with an edit without a valid m.replace relation");
287 None
288 }
289 });
290
291 let reactions = Default::default();
293 let thread_root = None;
294 let in_reply_to = None;
295
296 let msglike = MsgLikeContent {
297 kind: MsgLikeKind::Poll(PollState::new(
298 NewUnstablePollStartEventContent::new(event.content.poll_start().clone()),
299 edit,
300 )),
301 reactions,
302 thread_root,
303 in_reply_to,
304 };
305
306 TimelineItemContent::MsgLike(msglike)
307 }
308
309 fn from_suitable_latest_call_invite_content(
310 event: &SyncCallInviteEvent,
311 ) -> TimelineItemContent {
312 match event {
313 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
314 SyncCallInviteEvent::Redacted(_) => {
315 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
316 }
317 }
318 }
319
320 fn from_suitable_latest_call_notify_content(
321 event: &SyncCallNotifyEvent,
322 ) -> TimelineItemContent {
323 match event {
324 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
325 SyncCallNotifyEvent::Redacted(_) => {
326 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
327 }
328 }
329 }
330
331 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
332 as_variant!(self, TimelineItemContent::MsgLike)
333 }
334
335 pub fn as_message(&self) -> Option<&Message> {
338 as_variant!(self, Self::MsgLike(MsgLikeContent {
339 kind: MsgLikeKind::Message(message),
340 ..
341 }) => message)
342 }
343
344 pub fn is_message(&self) -> bool {
347 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
348 }
349
350 pub fn as_poll(&self) -> Option<&PollState> {
353 as_variant!(self, Self::MsgLike(MsgLikeContent {
354 kind: MsgLikeKind::Poll(poll_state),
355 ..
356 }) => poll_state)
357 }
358
359 pub fn is_poll(&self) -> bool {
362 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
363 }
364
365 pub fn as_sticker(&self) -> Option<&Sticker> {
366 as_variant!(
367 self,
368 Self::MsgLike(MsgLikeContent {
369 kind: MsgLikeKind::Sticker(sticker),
370 ..
371 }) => sticker
372 )
373 }
374
375 pub fn is_sticker(&self) -> bool {
378 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
379 }
380
381 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
384 as_variant!(
385 self,
386 Self::MsgLike(MsgLikeContent {
387 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
388 ..
389 }) => encrypted_message
390 )
391 }
392
393 pub fn is_unable_to_decrypt(&self) -> bool {
396 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
397 }
398
399 pub fn is_redacted(&self) -> bool {
400 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
401 }
402
403 pub(crate) fn message(
406 c: RoomMessageEventContent,
407 edit: Option<RoomMessageEventContentWithoutRelation>,
408 reactions: ReactionsByKeyBySender,
409 thread_root: Option<OwnedEventId>,
410 in_reply_to: Option<InReplyToDetails>,
411 ) -> Self {
412 let remove_reply_fallback =
413 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
414
415 Self::MsgLike(MsgLikeContent {
416 kind: MsgLikeKind::Message(Message::from_event(c, edit, remove_reply_fallback)),
417 reactions,
418 thread_root,
419 in_reply_to,
420 })
421 }
422
423 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
425 match self {
426 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
427 TimelineItemContent::MembershipChange(_) => "a membership change",
428 TimelineItemContent::ProfileChange(_) => "a profile change",
429 TimelineItemContent::OtherState(_) => "a state event",
430 TimelineItemContent::FailedToParseMessageLike { .. }
431 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
432 TimelineItemContent::CallInvite => "a call invite",
433 TimelineItemContent::CallNotify => "a call notification",
434 }
435 }
436
437 pub(crate) fn room_member(
438 user_id: OwnedUserId,
439 full_content: FullStateEventContent<RoomMemberEventContent>,
440 sender: OwnedUserId,
441 ) -> Self {
442 use ruma::events::room::member::MembershipChange as MChange;
443 match &full_content {
444 FullStateEventContent::Original { content, prev_content } => {
445 let membership_change = content.membership_change(
446 prev_content.as_ref().map(|c| c.details()),
447 &sender,
448 &user_id,
449 );
450
451 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
452 membership_change
453 {
454 Self::ProfileChange(MemberProfileChange {
455 user_id,
456 displayname_change: displayname_change.map(|c| Change {
457 new: c.new.map(ToOwned::to_owned),
458 old: c.old.map(ToOwned::to_owned),
459 }),
460 avatar_url_change: avatar_url_change.map(|c| Change {
461 new: c.new.map(ToOwned::to_owned),
462 old: c.old.map(ToOwned::to_owned),
463 }),
464 })
465 } else {
466 let change = match membership_change {
467 MChange::None => MembershipChange::None,
468 MChange::Error => MembershipChange::Error,
469 MChange::Joined => MembershipChange::Joined,
470 MChange::Left => MembershipChange::Left,
471 MChange::Banned => MembershipChange::Banned,
472 MChange::Unbanned => MembershipChange::Unbanned,
473 MChange::Kicked => MembershipChange::Kicked,
474 MChange::Invited => MembershipChange::Invited,
475 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
476 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
477 MChange::InvitationRejected => MembershipChange::InvitationRejected,
478 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
479 MChange::Knocked => MembershipChange::Knocked,
480 MChange::KnockAccepted => MembershipChange::KnockAccepted,
481 MChange::KnockRetracted => MembershipChange::KnockRetracted,
482 MChange::KnockDenied => MembershipChange::KnockDenied,
483 MChange::ProfileChanged { .. } => unreachable!(),
484 _ => MembershipChange::NotImplemented,
485 };
486
487 Self::MembershipChange(RoomMembershipChange {
488 user_id,
489 content: full_content,
490 change: Some(change),
491 })
492 }
493 }
494 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
495 user_id,
496 content: full_content,
497 change: None,
498 }),
499 }
500 }
501
502 pub(in crate::timeline) fn redact(&self, room_version: &RoomVersionId) -> Self {
503 match self {
504 Self::MsgLike(_) | Self::CallInvite | Self::CallNotify => {
505 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
506 }
507 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
508 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
509 Self::OtherState(ev) => Self::OtherState(ev.redact(room_version)),
510 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
511 }
512 }
513
514 pub fn thread_root(&self) -> Option<OwnedEventId> {
516 as_variant!(self, Self::MsgLike)?.thread_root.clone()
517 }
518
519 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
521 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
522 }
523
524 pub fn reactions(&self) -> ReactionsByKeyBySender {
527 match self {
528 TimelineItemContent::MsgLike(msglike) => msglike.reactions.clone(),
529
530 TimelineItemContent::MembershipChange(..)
531 | TimelineItemContent::ProfileChange(..)
532 | TimelineItemContent::OtherState(..)
533 | TimelineItemContent::FailedToParseMessageLike { .. }
534 | TimelineItemContent::FailedToParseState { .. }
535 | TimelineItemContent::CallInvite
536 | TimelineItemContent::CallNotify => {
537 Default::default()
539 }
540 }
541 }
542
543 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
547 match self {
548 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
549
550 TimelineItemContent::MembershipChange(..)
551 | TimelineItemContent::ProfileChange(..)
552 | TimelineItemContent::OtherState(..)
553 | TimelineItemContent::FailedToParseMessageLike { .. }
554 | TimelineItemContent::FailedToParseState { .. }
555 | TimelineItemContent::CallInvite
556 | TimelineItemContent::CallNotify => {
557 None
559 }
560 }
561 }
562
563 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
564 let mut cloned = self.clone();
565 if let Some(r) = cloned.reactions_mut() {
566 *r = reactions;
567 }
568 cloned
569 }
570}
571
572#[derive(Clone, Debug)]
574pub enum EncryptedMessage {
575 OlmV1Curve25519AesSha2 {
578 sender_key: String,
580 },
581 MegolmV1AesSha2 {
583 #[deprecated = "this field still needs to be sent but should not be used when received"]
585 #[doc(hidden)] sender_key: String,
587
588 #[deprecated = "this field still needs to be sent but should not be used when received"]
590 #[doc(hidden)] device_id: OwnedDeviceId,
592
593 session_id: String,
595
596 cause: UtdCause,
599 },
600 Unknown,
602}
603
604impl EncryptedMessage {
605 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
606 match content.scheme {
607 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
608 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
609 }
610 #[allow(deprecated)]
611 EncryptedEventScheme::MegolmV1AesSha2(s) => {
612 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
613
614 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
615 }
616 _ => Self::Unknown,
617 }
618 }
619
620 pub(crate) fn session_id(&self) -> Option<&str> {
623 match self {
624 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
625 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
626 EncryptedMessage::Unknown => None,
627 }
628 }
629}
630
631#[derive(Clone, Debug)]
633pub struct Sticker {
634 pub(in crate::timeline) content: StickerEventContent,
635}
636
637impl Sticker {
638 pub fn content(&self) -> &StickerEventContent {
640 &self.content
641 }
642}
643
644#[derive(Clone, Debug)]
646pub struct RoomMembershipChange {
647 pub(in crate::timeline) user_id: OwnedUserId,
648 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
649 pub(in crate::timeline) change: Option<MembershipChange>,
650}
651
652impl RoomMembershipChange {
653 pub fn user_id(&self) -> &UserId {
655 &self.user_id
656 }
657
658 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
660 &self.content
661 }
662
663 pub fn display_name(&self) -> Option<String> {
666 if let FullStateEventContent::Original { content, prev_content } = &self.content {
667 content
668 .displayname
669 .as_ref()
670 .or_else(|| {
671 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
672 })
673 .cloned()
674 } else {
675 None
676 }
677 }
678
679 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
682 if let FullStateEventContent::Original { content, prev_content } = &self.content {
683 content
684 .avatar_url
685 .as_ref()
686 .or_else(|| {
687 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
688 })
689 .cloned()
690 } else {
691 None
692 }
693 }
694
695 pub fn change(&self) -> Option<MembershipChange> {
703 self.change
704 }
705
706 fn redact(&self, room_version: &RoomVersionId) -> Self {
707 Self {
708 user_id: self.user_id.clone(),
709 content: FullStateEventContent::Redacted(self.content.clone().redact(room_version)),
710 change: self.change,
711 }
712 }
713}
714
715#[derive(Clone, Copy, Debug, PartialEq, Eq)]
717pub enum MembershipChange {
718 None,
720
721 Error,
723
724 Joined,
726
727 Left,
729
730 Banned,
732
733 Unbanned,
735
736 Kicked,
738
739 Invited,
741
742 KickedAndBanned,
744
745 InvitationAccepted,
747
748 InvitationRejected,
750
751 InvitationRevoked,
753
754 Knocked,
756
757 KnockAccepted,
759
760 KnockRetracted,
762
763 KnockDenied,
765
766 NotImplemented,
768}
769
770#[derive(Clone, Debug)]
775pub struct MemberProfileChange {
776 pub(in crate::timeline) user_id: OwnedUserId,
777 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
778 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
779}
780
781impl MemberProfileChange {
782 pub fn user_id(&self) -> &UserId {
784 &self.user_id
785 }
786
787 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
789 self.displayname_change.as_ref()
790 }
791
792 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
794 self.avatar_url_change.as_ref()
795 }
796
797 fn redact(&self) -> Self {
798 Self {
799 user_id: self.user_id.clone(),
800 displayname_change: None,
805 avatar_url_change: None,
806 }
807 }
808}
809
810#[derive(Clone, Debug)]
813pub enum AnyOtherFullStateEventContent {
814 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
816
817 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
819
820 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
822
823 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
825
826 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
828
829 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
831
832 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
834
835 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
837
838 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
840
841 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
843
844 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
846
847 RoomName(FullStateEventContent<RoomNameEventContent>),
849
850 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
852
853 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
855
856 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
858
859 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
861
862 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
864
865 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
867
868 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
870
871 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
873
874 #[doc(hidden)]
875 _Custom { event_type: String },
876}
877
878impl AnyOtherFullStateEventContent {
879 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
885 let event_type = content.event_type();
886
887 match content {
888 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
889 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
890 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
891 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
892 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
893 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
894 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
895 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
896 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
897 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
898 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
899 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
900 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
901 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
902 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
903 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
904 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
905 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
906 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
907 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
908 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
909 _ => Self::_Custom { event_type: event_type.to_string() },
910 }
911 }
912
913 pub fn event_type(&self) -> StateEventType {
915 match self {
916 Self::PolicyRuleRoom(c) => c.event_type(),
917 Self::PolicyRuleServer(c) => c.event_type(),
918 Self::PolicyRuleUser(c) => c.event_type(),
919 Self::RoomAliases(c) => c.event_type(),
920 Self::RoomAvatar(c) => c.event_type(),
921 Self::RoomCanonicalAlias(c) => c.event_type(),
922 Self::RoomCreate(c) => c.event_type(),
923 Self::RoomEncryption(c) => c.event_type(),
924 Self::RoomGuestAccess(c) => c.event_type(),
925 Self::RoomHistoryVisibility(c) => c.event_type(),
926 Self::RoomJoinRules(c) => c.event_type(),
927 Self::RoomName(c) => c.event_type(),
928 Self::RoomPinnedEvents(c) => c.event_type(),
929 Self::RoomPowerLevels(c) => c.event_type(),
930 Self::RoomServerAcl(c) => c.event_type(),
931 Self::RoomThirdPartyInvite(c) => c.event_type(),
932 Self::RoomTombstone(c) => c.event_type(),
933 Self::RoomTopic(c) => c.event_type(),
934 Self::SpaceChild(c) => c.event_type(),
935 Self::SpaceParent(c) => c.event_type(),
936 Self::_Custom { event_type } => event_type.as_str().into(),
937 }
938 }
939
940 fn redact(&self, room_version: &RoomVersionId) -> Self {
941 match self {
942 Self::PolicyRuleRoom(c) => Self::PolicyRuleRoom(FullStateEventContent::Redacted(
943 c.clone().redact(room_version),
944 )),
945 Self::PolicyRuleServer(c) => Self::PolicyRuleServer(FullStateEventContent::Redacted(
946 c.clone().redact(room_version),
947 )),
948 Self::PolicyRuleUser(c) => Self::PolicyRuleUser(FullStateEventContent::Redacted(
949 c.clone().redact(room_version),
950 )),
951 Self::RoomAliases(c) => {
952 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(room_version)))
953 }
954 Self::RoomAvatar(c) => {
955 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(room_version)))
956 }
957 Self::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(
958 FullStateEventContent::Redacted(c.clone().redact(room_version)),
959 ),
960 Self::RoomCreate(c) => {
961 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(room_version)))
962 }
963 Self::RoomEncryption(c) => Self::RoomEncryption(FullStateEventContent::Redacted(
964 c.clone().redact(room_version),
965 )),
966 Self::RoomGuestAccess(c) => Self::RoomGuestAccess(FullStateEventContent::Redacted(
967 c.clone().redact(room_version),
968 )),
969 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
970 FullStateEventContent::Redacted(c.clone().redact(room_version)),
971 ),
972 Self::RoomJoinRules(c) => {
973 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(room_version)))
974 }
975 Self::RoomName(c) => {
976 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(room_version)))
977 }
978 Self::RoomPinnedEvents(c) => Self::RoomPinnedEvents(FullStateEventContent::Redacted(
979 c.clone().redact(room_version),
980 )),
981 Self::RoomPowerLevels(c) => Self::RoomPowerLevels(FullStateEventContent::Redacted(
982 c.clone().redact(room_version),
983 )),
984 Self::RoomServerAcl(c) => {
985 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(room_version)))
986 }
987 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
988 FullStateEventContent::Redacted(c.clone().redact(room_version)),
989 ),
990 Self::RoomTombstone(c) => {
991 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(room_version)))
992 }
993 Self::RoomTopic(c) => {
994 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(room_version)))
995 }
996 Self::SpaceChild(c) => {
997 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(room_version)))
998 }
999 Self::SpaceParent(c) => {
1000 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1001 }
1002 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
1003 }
1004 }
1005}
1006
1007#[derive(Clone, Debug)]
1009pub struct OtherState {
1010 pub(in crate::timeline) state_key: String,
1011 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
1012}
1013
1014impl OtherState {
1015 pub fn state_key(&self) -> &str {
1017 &self.state_key
1018 }
1019
1020 pub fn content(&self) -> &AnyOtherFullStateEventContent {
1022 &self.content
1023 }
1024
1025 fn redact(&self, room_version: &RoomVersionId) -> Self {
1026 Self { state_key: self.state_key.clone(), content: self.content.redact(room_version) }
1027 }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use assert_matches2::assert_let;
1033 use matrix_sdk_test::ALICE;
1034 use ruma::{
1035 assign,
1036 events::{
1037 room::member::{MembershipState, RoomMemberEventContent},
1038 FullStateEventContent,
1039 },
1040 RoomVersionId,
1041 };
1042
1043 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1044
1045 #[test]
1046 fn redact_membership_change() {
1047 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1048 user_id: ALICE.to_owned(),
1049 content: FullStateEventContent::Original {
1050 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1051 reason: Some("🤬".to_owned()),
1052 }),
1053 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
1054 },
1055 change: Some(MembershipChange::Banned),
1056 });
1057
1058 let redacted = content.redact(&RoomVersionId::V11);
1059 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1060 assert_eq!(inner.change, Some(MembershipChange::Banned));
1061 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
1062 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1063 }
1064}