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, ThreadSummary, ThreadSummaryLatestEvent},
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 let thread_summary = None;
202
203 let msglike = MsgLikeContent {
204 kind: MsgLikeKind::Message(Message::from_event(
205 event_content,
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 reactions = Default::default();
297 let thread_root = None;
298 let in_reply_to = None;
299 let thread_summary = None;
300
301 let msglike = MsgLikeContent {
302 kind: MsgLikeKind::Poll(PollState::new(
303 NewUnstablePollStartEventContent::new(event.content.poll_start().clone()),
304 edit,
305 )),
306 reactions,
307 thread_root,
308 in_reply_to,
309 thread_summary,
310 };
311
312 TimelineItemContent::MsgLike(msglike)
313 }
314
315 fn from_suitable_latest_call_invite_content(
316 event: &SyncCallInviteEvent,
317 ) -> TimelineItemContent {
318 match event {
319 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
320 SyncCallInviteEvent::Redacted(_) => {
321 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
322 }
323 }
324 }
325
326 fn from_suitable_latest_call_notify_content(
327 event: &SyncCallNotifyEvent,
328 ) -> TimelineItemContent {
329 match event {
330 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
331 SyncCallNotifyEvent::Redacted(_) => {
332 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
333 }
334 }
335 }
336
337 pub fn as_msglike(&self) -> Option<&MsgLikeContent> {
338 as_variant!(self, TimelineItemContent::MsgLike)
339 }
340
341 pub fn as_message(&self) -> Option<&Message> {
344 as_variant!(self, Self::MsgLike(MsgLikeContent {
345 kind: MsgLikeKind::Message(message),
346 ..
347 }) => message)
348 }
349
350 pub fn is_message(&self) -> bool {
353 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Message(_), .. }))
354 }
355
356 pub fn as_poll(&self) -> Option<&PollState> {
359 as_variant!(self, Self::MsgLike(MsgLikeContent {
360 kind: MsgLikeKind::Poll(poll_state),
361 ..
362 }) => poll_state)
363 }
364
365 pub fn is_poll(&self) -> bool {
368 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Poll(_), .. }))
369 }
370
371 pub fn as_sticker(&self) -> Option<&Sticker> {
372 as_variant!(
373 self,
374 Self::MsgLike(MsgLikeContent {
375 kind: MsgLikeKind::Sticker(sticker),
376 ..
377 }) => sticker
378 )
379 }
380
381 pub fn is_sticker(&self) -> bool {
384 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Sticker(_), .. }))
385 }
386
387 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
390 as_variant!(
391 self,
392 Self::MsgLike(MsgLikeContent {
393 kind: MsgLikeKind::UnableToDecrypt(encrypted_message),
394 ..
395 }) => encrypted_message
396 )
397 }
398
399 pub fn is_unable_to_decrypt(&self) -> bool {
402 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::UnableToDecrypt(_), .. }))
403 }
404
405 pub fn is_redacted(&self) -> bool {
406 matches!(self, Self::MsgLike(MsgLikeContent { kind: MsgLikeKind::Redacted, .. }))
407 }
408
409 pub(crate) fn message(
412 c: RoomMessageEventContent,
413 edit: Option<RoomMessageEventContentWithoutRelation>,
414 reactions: ReactionsByKeyBySender,
415 thread_root: Option<OwnedEventId>,
416 in_reply_to: Option<InReplyToDetails>,
417 thread_summary: Option<ThreadSummary>,
418 ) -> Self {
419 let remove_reply_fallback =
420 if in_reply_to.is_some() { RemoveReplyFallback::Yes } else { RemoveReplyFallback::No };
421
422 Self::MsgLike(MsgLikeContent {
423 kind: MsgLikeKind::Message(Message::from_event(c, edit, remove_reply_fallback)),
424 reactions,
425 thread_root,
426 in_reply_to,
427 thread_summary,
428 })
429 }
430
431 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
433 match self {
434 TimelineItemContent::MsgLike(msglike) => msglike.debug_string(),
435 TimelineItemContent::MembershipChange(_) => "a membership change",
436 TimelineItemContent::ProfileChange(_) => "a profile change",
437 TimelineItemContent::OtherState(_) => "a state event",
438 TimelineItemContent::FailedToParseMessageLike { .. }
439 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
440 TimelineItemContent::CallInvite => "a call invite",
441 TimelineItemContent::CallNotify => "a call notification",
442 }
443 }
444
445 pub(crate) fn room_member(
446 user_id: OwnedUserId,
447 full_content: FullStateEventContent<RoomMemberEventContent>,
448 sender: OwnedUserId,
449 ) -> Self {
450 use ruma::events::room::member::MembershipChange as MChange;
451 match &full_content {
452 FullStateEventContent::Original { content, prev_content } => {
453 let membership_change = content.membership_change(
454 prev_content.as_ref().map(|c| c.details()),
455 &sender,
456 &user_id,
457 );
458
459 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
460 membership_change
461 {
462 Self::ProfileChange(MemberProfileChange {
463 user_id,
464 displayname_change: displayname_change.map(|c| Change {
465 new: c.new.map(ToOwned::to_owned),
466 old: c.old.map(ToOwned::to_owned),
467 }),
468 avatar_url_change: avatar_url_change.map(|c| Change {
469 new: c.new.map(ToOwned::to_owned),
470 old: c.old.map(ToOwned::to_owned),
471 }),
472 })
473 } else {
474 let change = match membership_change {
475 MChange::None => MembershipChange::None,
476 MChange::Error => MembershipChange::Error,
477 MChange::Joined => MembershipChange::Joined,
478 MChange::Left => MembershipChange::Left,
479 MChange::Banned => MembershipChange::Banned,
480 MChange::Unbanned => MembershipChange::Unbanned,
481 MChange::Kicked => MembershipChange::Kicked,
482 MChange::Invited => MembershipChange::Invited,
483 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
484 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
485 MChange::InvitationRejected => MembershipChange::InvitationRejected,
486 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
487 MChange::Knocked => MembershipChange::Knocked,
488 MChange::KnockAccepted => MembershipChange::KnockAccepted,
489 MChange::KnockRetracted => MembershipChange::KnockRetracted,
490 MChange::KnockDenied => MembershipChange::KnockDenied,
491 MChange::ProfileChanged { .. } => unreachable!(),
492 _ => MembershipChange::NotImplemented,
493 };
494
495 Self::MembershipChange(RoomMembershipChange {
496 user_id,
497 content: full_content,
498 change: Some(change),
499 })
500 }
501 }
502 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
503 user_id,
504 content: full_content,
505 change: None,
506 }),
507 }
508 }
509
510 pub(in crate::timeline) fn redact(&self, room_version: &RoomVersionId) -> Self {
511 match self {
512 Self::MsgLike(_) | Self::CallInvite | Self::CallNotify => {
513 TimelineItemContent::MsgLike(MsgLikeContent::redacted())
514 }
515 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
516 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
517 Self::OtherState(ev) => Self::OtherState(ev.redact(room_version)),
518 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
519 }
520 }
521
522 pub fn thread_root(&self) -> Option<OwnedEventId> {
524 as_variant!(self, Self::MsgLike)?.thread_root.clone()
525 }
526
527 pub fn in_reply_to(&self) -> Option<InReplyToDetails> {
529 as_variant!(self, Self::MsgLike)?.in_reply_to.clone()
530 }
531
532 pub fn reactions(&self) -> ReactionsByKeyBySender {
535 match self {
536 TimelineItemContent::MsgLike(msglike) => msglike.reactions.clone(),
537
538 TimelineItemContent::MembershipChange(..)
539 | TimelineItemContent::ProfileChange(..)
540 | TimelineItemContent::OtherState(..)
541 | TimelineItemContent::FailedToParseMessageLike { .. }
542 | TimelineItemContent::FailedToParseState { .. }
543 | TimelineItemContent::CallInvite
544 | TimelineItemContent::CallNotify => {
545 Default::default()
547 }
548 }
549 }
550
551 pub fn thread_summary(&self) -> Option<ThreadSummary> {
553 as_variant!(self, Self::MsgLike)?.thread_summary.clone()
554 }
555
556 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
560 match self {
561 TimelineItemContent::MsgLike(msglike) => Some(&mut msglike.reactions),
562
563 TimelineItemContent::MembershipChange(..)
564 | TimelineItemContent::ProfileChange(..)
565 | TimelineItemContent::OtherState(..)
566 | TimelineItemContent::FailedToParseMessageLike { .. }
567 | TimelineItemContent::FailedToParseState { .. }
568 | TimelineItemContent::CallInvite
569 | TimelineItemContent::CallNotify => {
570 None
572 }
573 }
574 }
575
576 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
577 let mut cloned = self.clone();
578 if let Some(r) = cloned.reactions_mut() {
579 *r = reactions;
580 }
581 cloned
582 }
583}
584
585#[derive(Clone, Debug)]
587pub enum EncryptedMessage {
588 OlmV1Curve25519AesSha2 {
591 sender_key: String,
593 },
594 MegolmV1AesSha2 {
596 #[deprecated = "this field still needs to be sent but should not be used when received"]
598 #[doc(hidden)] sender_key: String,
600
601 #[deprecated = "this field still needs to be sent but should not be used when received"]
603 #[doc(hidden)] device_id: OwnedDeviceId,
605
606 session_id: String,
608
609 cause: UtdCause,
612 },
613 Unknown,
615}
616
617impl EncryptedMessage {
618 pub(crate) fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
619 match content.scheme {
620 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
621 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
622 }
623 #[allow(deprecated)]
624 EncryptedEventScheme::MegolmV1AesSha2(s) => {
625 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
626
627 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
628 }
629 _ => Self::Unknown,
630 }
631 }
632
633 pub(crate) fn session_id(&self) -> Option<&str> {
636 match self {
637 EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None,
638 EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id),
639 EncryptedMessage::Unknown => None,
640 }
641 }
642}
643
644#[derive(Clone, Debug)]
646pub struct Sticker {
647 pub(in crate::timeline) content: StickerEventContent,
648}
649
650impl Sticker {
651 pub fn content(&self) -> &StickerEventContent {
653 &self.content
654 }
655}
656
657#[derive(Clone, Debug)]
659pub struct RoomMembershipChange {
660 pub(in crate::timeline) user_id: OwnedUserId,
661 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
662 pub(in crate::timeline) change: Option<MembershipChange>,
663}
664
665impl RoomMembershipChange {
666 pub fn user_id(&self) -> &UserId {
668 &self.user_id
669 }
670
671 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
673 &self.content
674 }
675
676 pub fn display_name(&self) -> Option<String> {
679 if let FullStateEventContent::Original { content, prev_content } = &self.content {
680 content
681 .displayname
682 .as_ref()
683 .or_else(|| {
684 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
685 })
686 .cloned()
687 } else {
688 None
689 }
690 }
691
692 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
695 if let FullStateEventContent::Original { content, prev_content } = &self.content {
696 content
697 .avatar_url
698 .as_ref()
699 .or_else(|| {
700 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
701 })
702 .cloned()
703 } else {
704 None
705 }
706 }
707
708 pub fn change(&self) -> Option<MembershipChange> {
716 self.change
717 }
718
719 fn redact(&self, room_version: &RoomVersionId) -> Self {
720 Self {
721 user_id: self.user_id.clone(),
722 content: FullStateEventContent::Redacted(self.content.clone().redact(room_version)),
723 change: self.change,
724 }
725 }
726}
727
728#[derive(Clone, Copy, Debug, PartialEq, Eq)]
730pub enum MembershipChange {
731 None,
733
734 Error,
736
737 Joined,
739
740 Left,
742
743 Banned,
745
746 Unbanned,
748
749 Kicked,
751
752 Invited,
754
755 KickedAndBanned,
757
758 InvitationAccepted,
760
761 InvitationRejected,
763
764 InvitationRevoked,
766
767 Knocked,
769
770 KnockAccepted,
772
773 KnockRetracted,
775
776 KnockDenied,
778
779 NotImplemented,
781}
782
783#[derive(Clone, Debug)]
788pub struct MemberProfileChange {
789 pub(in crate::timeline) user_id: OwnedUserId,
790 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
791 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
792}
793
794impl MemberProfileChange {
795 pub fn user_id(&self) -> &UserId {
797 &self.user_id
798 }
799
800 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
802 self.displayname_change.as_ref()
803 }
804
805 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
807 self.avatar_url_change.as_ref()
808 }
809
810 fn redact(&self) -> Self {
811 Self {
812 user_id: self.user_id.clone(),
813 displayname_change: None,
818 avatar_url_change: None,
819 }
820 }
821}
822
823#[derive(Clone, Debug)]
826pub enum AnyOtherFullStateEventContent {
827 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
829
830 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
832
833 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
835
836 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
838
839 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
841
842 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
844
845 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
847
848 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
850
851 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
853
854 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
856
857 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
859
860 RoomName(FullStateEventContent<RoomNameEventContent>),
862
863 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
865
866 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
868
869 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
871
872 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
874
875 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
877
878 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
880
881 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
883
884 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
886
887 #[doc(hidden)]
888 _Custom { event_type: String },
889}
890
891impl AnyOtherFullStateEventContent {
892 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
898 let event_type = content.event_type();
899
900 match content {
901 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
902 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
903 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
904 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
905 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
906 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
907 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
908 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
909 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
910 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
911 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
912 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
913 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
914 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
915 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
916 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
917 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
918 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
919 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
920 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
921 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
922 _ => Self::_Custom { event_type: event_type.to_string() },
923 }
924 }
925
926 pub fn event_type(&self) -> StateEventType {
928 match self {
929 Self::PolicyRuleRoom(c) => c.event_type(),
930 Self::PolicyRuleServer(c) => c.event_type(),
931 Self::PolicyRuleUser(c) => c.event_type(),
932 Self::RoomAliases(c) => c.event_type(),
933 Self::RoomAvatar(c) => c.event_type(),
934 Self::RoomCanonicalAlias(c) => c.event_type(),
935 Self::RoomCreate(c) => c.event_type(),
936 Self::RoomEncryption(c) => c.event_type(),
937 Self::RoomGuestAccess(c) => c.event_type(),
938 Self::RoomHistoryVisibility(c) => c.event_type(),
939 Self::RoomJoinRules(c) => c.event_type(),
940 Self::RoomName(c) => c.event_type(),
941 Self::RoomPinnedEvents(c) => c.event_type(),
942 Self::RoomPowerLevels(c) => c.event_type(),
943 Self::RoomServerAcl(c) => c.event_type(),
944 Self::RoomThirdPartyInvite(c) => c.event_type(),
945 Self::RoomTombstone(c) => c.event_type(),
946 Self::RoomTopic(c) => c.event_type(),
947 Self::SpaceChild(c) => c.event_type(),
948 Self::SpaceParent(c) => c.event_type(),
949 Self::_Custom { event_type } => event_type.as_str().into(),
950 }
951 }
952
953 fn redact(&self, room_version: &RoomVersionId) -> Self {
954 match self {
955 Self::PolicyRuleRoom(c) => Self::PolicyRuleRoom(FullStateEventContent::Redacted(
956 c.clone().redact(room_version),
957 )),
958 Self::PolicyRuleServer(c) => Self::PolicyRuleServer(FullStateEventContent::Redacted(
959 c.clone().redact(room_version),
960 )),
961 Self::PolicyRuleUser(c) => Self::PolicyRuleUser(FullStateEventContent::Redacted(
962 c.clone().redact(room_version),
963 )),
964 Self::RoomAliases(c) => {
965 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(room_version)))
966 }
967 Self::RoomAvatar(c) => {
968 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(room_version)))
969 }
970 Self::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(
971 FullStateEventContent::Redacted(c.clone().redact(room_version)),
972 ),
973 Self::RoomCreate(c) => {
974 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(room_version)))
975 }
976 Self::RoomEncryption(c) => Self::RoomEncryption(FullStateEventContent::Redacted(
977 c.clone().redact(room_version),
978 )),
979 Self::RoomGuestAccess(c) => Self::RoomGuestAccess(FullStateEventContent::Redacted(
980 c.clone().redact(room_version),
981 )),
982 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
983 FullStateEventContent::Redacted(c.clone().redact(room_version)),
984 ),
985 Self::RoomJoinRules(c) => {
986 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(room_version)))
987 }
988 Self::RoomName(c) => {
989 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(room_version)))
990 }
991 Self::RoomPinnedEvents(c) => Self::RoomPinnedEvents(FullStateEventContent::Redacted(
992 c.clone().redact(room_version),
993 )),
994 Self::RoomPowerLevels(c) => Self::RoomPowerLevels(FullStateEventContent::Redacted(
995 c.clone().redact(room_version),
996 )),
997 Self::RoomServerAcl(c) => {
998 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(room_version)))
999 }
1000 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
1001 FullStateEventContent::Redacted(c.clone().redact(room_version)),
1002 ),
1003 Self::RoomTombstone(c) => {
1004 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1005 }
1006 Self::RoomTopic(c) => {
1007 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1008 }
1009 Self::SpaceChild(c) => {
1010 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1011 }
1012 Self::SpaceParent(c) => {
1013 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(room_version)))
1014 }
1015 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
1016 }
1017 }
1018}
1019
1020#[derive(Clone, Debug)]
1022pub struct OtherState {
1023 pub(in crate::timeline) state_key: String,
1024 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
1025}
1026
1027impl OtherState {
1028 pub fn state_key(&self) -> &str {
1030 &self.state_key
1031 }
1032
1033 pub fn content(&self) -> &AnyOtherFullStateEventContent {
1035 &self.content
1036 }
1037
1038 fn redact(&self, room_version: &RoomVersionId) -> Self {
1039 Self { state_key: self.state_key.clone(), content: self.content.redact(room_version) }
1040 }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045 use assert_matches2::assert_let;
1046 use matrix_sdk_test::ALICE;
1047 use ruma::{
1048 assign,
1049 events::{
1050 room::member::{MembershipState, RoomMemberEventContent},
1051 FullStateEventContent,
1052 },
1053 RoomVersionId,
1054 };
1055
1056 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
1057
1058 #[test]
1059 fn redact_membership_change() {
1060 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
1061 user_id: ALICE.to_owned(),
1062 content: FullStateEventContent::Original {
1063 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
1064 reason: Some("🤬".to_owned()),
1065 }),
1066 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
1067 },
1068 change: Some(MembershipChange::Banned),
1069 });
1070
1071 let redacted = content.redact(&RoomVersionId::V11);
1072 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
1073 assert_eq!(inner.change, Some(MembershipChange::Banned));
1074 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
1075 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
1076 }
1077}