1use std::sync::Arc;
16
17use as_variant::as_variant;
18use imbl::Vector;
19use matrix_sdk::crypto::types::events::UtdCause;
20use matrix_sdk_base::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent};
21use ruma::{
22 events::{
23 call::{invite::SyncCallInviteEvent, notify::SyncCallNotifyEvent},
24 policy::rule::{
25 room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
26 user::PolicyRuleUserEventContent,
27 },
28 poll::unstable_start::{
29 NewUnstablePollStartEventContent, SyncUnstablePollStartEvent,
30 UnstablePollStartEventContent,
31 },
32 room::{
33 aliases::RoomAliasesEventContent,
34 avatar::RoomAvatarEventContent,
35 canonical_alias::RoomCanonicalAliasEventContent,
36 create::RoomCreateEventContent,
37 encrypted::{EncryptedEventScheme, MegolmV1AesSha2Content, RoomEncryptedEventContent},
38 encryption::RoomEncryptionEventContent,
39 guest_access::RoomGuestAccessEventContent,
40 history_visibility::RoomHistoryVisibilityEventContent,
41 join_rules::RoomJoinRulesEventContent,
42 member::{Change, RoomMemberEventContent, SyncRoomMemberEvent},
43 message::{
44 Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
45 SyncRoomMessageEvent,
46 },
47 name::RoomNameEventContent,
48 pinned_events::RoomPinnedEventsEventContent,
49 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
50 server_acl::RoomServerAclEventContent,
51 third_party_invite::RoomThirdPartyInviteEventContent,
52 tombstone::RoomTombstoneEventContent,
53 topic::RoomTopicEventContent,
54 },
55 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
56 sticker::{StickerEventContent, SyncStickerEvent},
57 AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent,
58 MessageLikeEventType, StateEventType,
59 },
60 OwnedDeviceId, OwnedMxcUri, OwnedUserId, RoomVersionId, UserId,
61};
62use tracing::warn;
63
64use crate::timeline::TimelineItem;
65
66mod message;
67pub(crate) mod pinned_events;
68mod polls;
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::{InReplyToDetails, Message, RepliedToEvent},
77 polls::{PollResult, PollState},
78};
79use super::ReactionsByKeyBySender;
80
81#[derive(Clone, Debug)]
83pub enum TimelineItemContent {
84 Message(Message),
86
87 RedactedMessage,
89
90 Sticker(Sticker),
92
93 UnableToDecrypt(EncryptedMessage),
95
96 MembershipChange(RoomMembershipChange),
98
99 ProfileChange(MemberProfileChange),
101
102 OtherState(OtherState),
104
105 FailedToParseMessageLike {
107 event_type: MessageLikeEventType,
109
110 error: Arc<serde_json::Error>,
112 },
113
114 FailedToParseState {
116 event_type: StateEventType,
118
119 state_key: String,
121
122 error: Arc<serde_json::Error>,
124 },
125
126 Poll(PollState),
128
129 CallInvite,
131
132 CallNotify,
134}
135
136impl TimelineItemContent {
137 pub(crate) fn from_latest_event_content(
141 event: AnySyncTimelineEvent,
142 power_levels_info: Option<(&UserId, &RoomPowerLevels)>,
143 ) -> Option<TimelineItemContent> {
144 match is_suitable_for_latest_event(&event, power_levels_info) {
145 PossibleLatestEvent::YesRoomMessage(m) => {
146 Some(Self::from_suitable_latest_event_content(m))
147 }
148 PossibleLatestEvent::YesSticker(s) => {
149 Some(Self::from_suitable_latest_sticker_content(s))
150 }
151 PossibleLatestEvent::YesPoll(poll) => {
152 Some(Self::from_suitable_latest_poll_event_content(poll))
153 }
154 PossibleLatestEvent::YesCallInvite(call_invite) => {
155 Some(Self::from_suitable_latest_call_invite_content(call_invite))
156 }
157 PossibleLatestEvent::YesCallNotify(call_notify) => {
158 Some(Self::from_suitable_latest_call_notify_content(call_notify))
159 }
160 PossibleLatestEvent::NoUnsupportedEventType => {
161 warn!("Found a state event cached as latest_event! ID={}", event.event_id());
163 None
164 }
165 PossibleLatestEvent::NoUnsupportedMessageLikeType => {
166 warn!(
168 "Found an event cached as latest_event, but I don't know how \
169 to wrap it in a TimelineItemContent. type={}, ID={}",
170 event.event_type().to_string(),
171 event.event_id()
172 );
173 None
174 }
175 PossibleLatestEvent::YesKnockedStateEvent(member) => {
176 Some(Self::from_suitable_latest_knock_state_event_content(member))
177 }
178 PossibleLatestEvent::NoEncrypted => {
179 warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
180 None
181 }
182 }
183 }
184
185 fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent {
189 match event {
190 SyncRoomMessageEvent::Original(event) => {
191 let event_content = event.content.clone();
193
194 let edit = event
196 .unsigned
197 .relations
198 .replace
199 .as_ref()
200 .and_then(|boxed| match &boxed.content.relates_to {
201 Some(Relation::Replacement(re)) => Some(re.new_content.clone()),
202 _ => {
203 warn!("got m.room.message event with an edit without a valid m.replace relation");
204 None
205 }
206 });
207
208 let timeline_items = Vector::new();
214 let reactions = Default::default();
215 TimelineItemContent::Message(Message::from_event(
216 event_content,
217 edit,
218 &timeline_items,
219 reactions,
220 ))
221 }
222
223 SyncRoomMessageEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
224 }
225 }
226
227 fn from_suitable_latest_knock_state_event_content(
228 event: &SyncRoomMemberEvent,
229 ) -> TimelineItemContent {
230 match event {
231 SyncRoomMemberEvent::Original(event) => {
232 let content = event.content.clone();
233 let prev_content = event.prev_content().cloned();
234 TimelineItemContent::room_member(
235 event.state_key.to_owned(),
236 FullStateEventContent::Original { content, prev_content },
237 event.sender.to_owned(),
238 )
239 }
240 SyncRoomMemberEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
241 }
242 }
243
244 fn from_suitable_latest_sticker_content(event: &SyncStickerEvent) -> TimelineItemContent {
248 match event {
249 SyncStickerEvent::Original(event) => {
250 let event_content = event.content.clone();
252 TimelineItemContent::Sticker(Sticker {
253 content: event_content,
254 reactions: Default::default(),
255 })
256 }
257 SyncStickerEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
258 }
259 }
260
261 fn from_suitable_latest_poll_event_content(
264 event: &SyncUnstablePollStartEvent,
265 ) -> TimelineItemContent {
266 let SyncUnstablePollStartEvent::Original(event) = event else {
267 return TimelineItemContent::RedactedMessage;
268 };
269
270 let edit =
272 event.unsigned.relations.replace.as_ref().and_then(|boxed| match &boxed.content {
273 UnstablePollStartEventContent::Replacement(re) => {
274 Some(re.relates_to.new_content.clone())
275 }
276 _ => {
277 warn!("got poll event with an edit without a valid m.replace relation");
278 None
279 }
280 });
281
282 let reactions = Default::default();
284
285 TimelineItemContent::Poll(PollState::new(
286 NewUnstablePollStartEventContent::new(event.content.poll_start().clone()),
287 edit,
288 reactions,
289 ))
290 }
291
292 fn from_suitable_latest_call_invite_content(
293 event: &SyncCallInviteEvent,
294 ) -> TimelineItemContent {
295 match event {
296 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
297 SyncCallInviteEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
298 }
299 }
300
301 fn from_suitable_latest_call_notify_content(
302 event: &SyncCallNotifyEvent,
303 ) -> TimelineItemContent {
304 match event {
305 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
306 SyncCallNotifyEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
307 }
308 }
309
310 pub fn as_message(&self) -> Option<&Message> {
313 as_variant!(self, Self::Message)
314 }
315
316 pub fn as_poll(&self) -> Option<&PollState> {
319 as_variant!(self, Self::Poll)
320 }
321
322 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
325 as_variant!(self, Self::UnableToDecrypt)
326 }
327
328 pub(crate) fn message(
331 c: RoomMessageEventContent,
332 edit: Option<RoomMessageEventContentWithoutRelation>,
333 timeline_items: &Vector<Arc<TimelineItem>>,
334 reactions: ReactionsByKeyBySender,
335 ) -> Self {
336 Self::Message(Message::from_event(c, edit, timeline_items, reactions))
337 }
338
339 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
341 match self {
342 TimelineItemContent::Message(_) => "a message",
343 TimelineItemContent::RedactedMessage => "a redacted messages",
344 TimelineItemContent::Sticker(_) => "a sticker",
345 TimelineItemContent::UnableToDecrypt(_) => "an encrypted message we couldn't decrypt",
346 TimelineItemContent::MembershipChange(_) => "a membership change",
347 TimelineItemContent::ProfileChange(_) => "a profile change",
348 TimelineItemContent::OtherState(_) => "a state event",
349 TimelineItemContent::FailedToParseMessageLike { .. }
350 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
351 TimelineItemContent::Poll(_) => "a poll",
352 TimelineItemContent::CallInvite => "a call invite",
353 TimelineItemContent::CallNotify => "a call notification",
354 }
355 }
356
357 pub(crate) fn unable_to_decrypt(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
358 Self::UnableToDecrypt(EncryptedMessage::from_content(content, cause))
359 }
360
361 pub(crate) fn room_member(
362 user_id: OwnedUserId,
363 full_content: FullStateEventContent<RoomMemberEventContent>,
364 sender: OwnedUserId,
365 ) -> Self {
366 use ruma::events::room::member::MembershipChange as MChange;
367 match &full_content {
368 FullStateEventContent::Original { content, prev_content } => {
369 let membership_change = content.membership_change(
370 prev_content.as_ref().map(|c| c.details()),
371 &sender,
372 &user_id,
373 );
374
375 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
376 membership_change
377 {
378 Self::ProfileChange(MemberProfileChange {
379 user_id,
380 displayname_change: displayname_change.map(|c| Change {
381 new: c.new.map(ToOwned::to_owned),
382 old: c.old.map(ToOwned::to_owned),
383 }),
384 avatar_url_change: avatar_url_change.map(|c| Change {
385 new: c.new.map(ToOwned::to_owned),
386 old: c.old.map(ToOwned::to_owned),
387 }),
388 })
389 } else {
390 let change = match membership_change {
391 MChange::None => MembershipChange::None,
392 MChange::Error => MembershipChange::Error,
393 MChange::Joined => MembershipChange::Joined,
394 MChange::Left => MembershipChange::Left,
395 MChange::Banned => MembershipChange::Banned,
396 MChange::Unbanned => MembershipChange::Unbanned,
397 MChange::Kicked => MembershipChange::Kicked,
398 MChange::Invited => MembershipChange::Invited,
399 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
400 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
401 MChange::InvitationRejected => MembershipChange::InvitationRejected,
402 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
403 MChange::Knocked => MembershipChange::Knocked,
404 MChange::KnockAccepted => MembershipChange::KnockAccepted,
405 MChange::KnockRetracted => MembershipChange::KnockRetracted,
406 MChange::KnockDenied => MembershipChange::KnockDenied,
407 MChange::ProfileChanged { .. } => unreachable!(),
408 _ => MembershipChange::NotImplemented,
409 };
410
411 Self::MembershipChange(RoomMembershipChange {
412 user_id,
413 content: full_content,
414 change: Some(change),
415 })
416 }
417 }
418 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
419 user_id,
420 content: full_content,
421 change: None,
422 }),
423 }
424 }
425
426 pub(in crate::timeline) fn redact(&self, room_version: &RoomVersionId) -> Self {
427 match self {
428 Self::Message(_)
429 | Self::RedactedMessage
430 | Self::Sticker(_)
431 | Self::Poll(_)
432 | Self::CallInvite
433 | Self::CallNotify
434 | Self::UnableToDecrypt(_) => Self::RedactedMessage,
435 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
436 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
437 Self::OtherState(ev) => Self::OtherState(ev.redact(room_version)),
438 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
439 }
440 }
441
442 pub fn reactions(&self) -> ReactionsByKeyBySender {
448 match self {
449 TimelineItemContent::Message(message) => message.reactions.clone(),
450 TimelineItemContent::Sticker(sticker) => sticker.reactions.clone(),
451 TimelineItemContent::Poll(poll_state) => poll_state.reactions.clone(),
452
453 TimelineItemContent::UnableToDecrypt(..) | TimelineItemContent::RedactedMessage => {
454 Default::default()
456 }
457
458 TimelineItemContent::MembershipChange(..)
459 | TimelineItemContent::ProfileChange(..)
460 | TimelineItemContent::OtherState(..)
461 | TimelineItemContent::FailedToParseMessageLike { .. }
462 | TimelineItemContent::FailedToParseState { .. }
463 | TimelineItemContent::CallInvite
464 | TimelineItemContent::CallNotify => {
465 Default::default()
467 }
468 }
469 }
470
471 pub(crate) fn reactions_mut(&mut self) -> Option<&mut ReactionsByKeyBySender> {
475 match self {
476 TimelineItemContent::Message(message) => Some(&mut message.reactions),
477 TimelineItemContent::Sticker(sticker) => Some(&mut sticker.reactions),
478 TimelineItemContent::Poll(poll_state) => Some(&mut poll_state.reactions),
479
480 TimelineItemContent::UnableToDecrypt(..) | TimelineItemContent::RedactedMessage => {
481 None
483 }
484
485 TimelineItemContent::MembershipChange(..)
486 | TimelineItemContent::ProfileChange(..)
487 | TimelineItemContent::OtherState(..)
488 | TimelineItemContent::FailedToParseMessageLike { .. }
489 | TimelineItemContent::FailedToParseState { .. }
490 | TimelineItemContent::CallInvite
491 | TimelineItemContent::CallNotify => {
492 None
494 }
495 }
496 }
497
498 pub fn with_reactions(&self, reactions: ReactionsByKeyBySender) -> Self {
499 let mut cloned = self.clone();
500 if let Some(r) = cloned.reactions_mut() {
501 *r = reactions;
502 }
503 cloned
504 }
505}
506
507#[derive(Clone, Debug)]
509pub enum EncryptedMessage {
510 OlmV1Curve25519AesSha2 {
513 sender_key: String,
515 },
516 MegolmV1AesSha2 {
518 #[deprecated = "this field still needs to be sent but should not be used when received"]
520 #[doc(hidden)] sender_key: String,
522
523 #[deprecated = "this field still needs to be sent but should not be used when received"]
525 #[doc(hidden)] device_id: OwnedDeviceId,
527
528 session_id: String,
530
531 cause: UtdCause,
534 },
535 Unknown,
537}
538
539impl EncryptedMessage {
540 fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
541 match content.scheme {
542 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
543 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
544 }
545 #[allow(deprecated)]
546 EncryptedEventScheme::MegolmV1AesSha2(s) => {
547 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
548
549 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
550 }
551 _ => Self::Unknown,
552 }
553 }
554}
555
556#[derive(Clone, Debug)]
558pub struct Sticker {
559 pub(in crate::timeline) content: StickerEventContent,
560 pub(in crate::timeline) reactions: ReactionsByKeyBySender,
561}
562
563impl Sticker {
564 pub fn content(&self) -> &StickerEventContent {
566 &self.content
567 }
568}
569
570#[derive(Clone, Debug)]
572pub struct RoomMembershipChange {
573 pub(in crate::timeline) user_id: OwnedUserId,
574 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
575 pub(in crate::timeline) change: Option<MembershipChange>,
576}
577
578impl RoomMembershipChange {
579 pub fn user_id(&self) -> &UserId {
581 &self.user_id
582 }
583
584 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
586 &self.content
587 }
588
589 pub fn display_name(&self) -> Option<String> {
592 if let FullStateEventContent::Original { content, prev_content } = &self.content {
593 content
594 .displayname
595 .as_ref()
596 .or_else(|| {
597 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
598 })
599 .cloned()
600 } else {
601 None
602 }
603 }
604
605 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
608 if let FullStateEventContent::Original { content, prev_content } = &self.content {
609 content
610 .avatar_url
611 .as_ref()
612 .or_else(|| {
613 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
614 })
615 .cloned()
616 } else {
617 None
618 }
619 }
620
621 pub fn change(&self) -> Option<MembershipChange> {
629 self.change
630 }
631
632 fn redact(&self, room_version: &RoomVersionId) -> Self {
633 Self {
634 user_id: self.user_id.clone(),
635 content: FullStateEventContent::Redacted(self.content.clone().redact(room_version)),
636 change: self.change,
637 }
638 }
639}
640
641#[derive(Clone, Copy, Debug, PartialEq, Eq)]
643pub enum MembershipChange {
644 None,
646
647 Error,
649
650 Joined,
652
653 Left,
655
656 Banned,
658
659 Unbanned,
661
662 Kicked,
664
665 Invited,
667
668 KickedAndBanned,
670
671 InvitationAccepted,
673
674 InvitationRejected,
676
677 InvitationRevoked,
679
680 Knocked,
682
683 KnockAccepted,
685
686 KnockRetracted,
688
689 KnockDenied,
691
692 NotImplemented,
694}
695
696#[derive(Clone, Debug)]
701pub struct MemberProfileChange {
702 pub(in crate::timeline) user_id: OwnedUserId,
703 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
704 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
705}
706
707impl MemberProfileChange {
708 pub fn user_id(&self) -> &UserId {
710 &self.user_id
711 }
712
713 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
715 self.displayname_change.as_ref()
716 }
717
718 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
720 self.avatar_url_change.as_ref()
721 }
722
723 fn redact(&self) -> Self {
724 Self {
725 user_id: self.user_id.clone(),
726 displayname_change: None,
731 avatar_url_change: None,
732 }
733 }
734}
735
736#[derive(Clone, Debug)]
739pub enum AnyOtherFullStateEventContent {
740 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
742
743 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
745
746 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
748
749 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
751
752 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
754
755 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
757
758 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
760
761 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
763
764 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
766
767 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
769
770 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
772
773 RoomName(FullStateEventContent<RoomNameEventContent>),
775
776 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
778
779 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
781
782 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
784
785 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
787
788 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
790
791 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
793
794 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
796
797 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
799
800 #[doc(hidden)]
801 _Custom { event_type: String },
802}
803
804impl AnyOtherFullStateEventContent {
805 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
811 let event_type = content.event_type();
812
813 match content {
814 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
815 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
816 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
817 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
818 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
819 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
820 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
821 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
822 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
823 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
824 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
825 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
826 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
827 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
828 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
829 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
830 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
831 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
832 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
833 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
834 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
835 _ => Self::_Custom { event_type: event_type.to_string() },
836 }
837 }
838
839 pub fn event_type(&self) -> StateEventType {
841 match self {
842 Self::PolicyRuleRoom(c) => c.event_type(),
843 Self::PolicyRuleServer(c) => c.event_type(),
844 Self::PolicyRuleUser(c) => c.event_type(),
845 Self::RoomAliases(c) => c.event_type(),
846 Self::RoomAvatar(c) => c.event_type(),
847 Self::RoomCanonicalAlias(c) => c.event_type(),
848 Self::RoomCreate(c) => c.event_type(),
849 Self::RoomEncryption(c) => c.event_type(),
850 Self::RoomGuestAccess(c) => c.event_type(),
851 Self::RoomHistoryVisibility(c) => c.event_type(),
852 Self::RoomJoinRules(c) => c.event_type(),
853 Self::RoomName(c) => c.event_type(),
854 Self::RoomPinnedEvents(c) => c.event_type(),
855 Self::RoomPowerLevels(c) => c.event_type(),
856 Self::RoomServerAcl(c) => c.event_type(),
857 Self::RoomThirdPartyInvite(c) => c.event_type(),
858 Self::RoomTombstone(c) => c.event_type(),
859 Self::RoomTopic(c) => c.event_type(),
860 Self::SpaceChild(c) => c.event_type(),
861 Self::SpaceParent(c) => c.event_type(),
862 Self::_Custom { event_type } => event_type.as_str().into(),
863 }
864 }
865
866 fn redact(&self, room_version: &RoomVersionId) -> Self {
867 match self {
868 Self::PolicyRuleRoom(c) => Self::PolicyRuleRoom(FullStateEventContent::Redacted(
869 c.clone().redact(room_version),
870 )),
871 Self::PolicyRuleServer(c) => Self::PolicyRuleServer(FullStateEventContent::Redacted(
872 c.clone().redact(room_version),
873 )),
874 Self::PolicyRuleUser(c) => Self::PolicyRuleUser(FullStateEventContent::Redacted(
875 c.clone().redact(room_version),
876 )),
877 Self::RoomAliases(c) => {
878 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(room_version)))
879 }
880 Self::RoomAvatar(c) => {
881 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(room_version)))
882 }
883 Self::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(
884 FullStateEventContent::Redacted(c.clone().redact(room_version)),
885 ),
886 Self::RoomCreate(c) => {
887 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(room_version)))
888 }
889 Self::RoomEncryption(c) => Self::RoomEncryption(FullStateEventContent::Redacted(
890 c.clone().redact(room_version),
891 )),
892 Self::RoomGuestAccess(c) => Self::RoomGuestAccess(FullStateEventContent::Redacted(
893 c.clone().redact(room_version),
894 )),
895 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
896 FullStateEventContent::Redacted(c.clone().redact(room_version)),
897 ),
898 Self::RoomJoinRules(c) => {
899 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(room_version)))
900 }
901 Self::RoomName(c) => {
902 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(room_version)))
903 }
904 Self::RoomPinnedEvents(c) => Self::RoomPinnedEvents(FullStateEventContent::Redacted(
905 c.clone().redact(room_version),
906 )),
907 Self::RoomPowerLevels(c) => Self::RoomPowerLevels(FullStateEventContent::Redacted(
908 c.clone().redact(room_version),
909 )),
910 Self::RoomServerAcl(c) => {
911 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(room_version)))
912 }
913 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
914 FullStateEventContent::Redacted(c.clone().redact(room_version)),
915 ),
916 Self::RoomTombstone(c) => {
917 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(room_version)))
918 }
919 Self::RoomTopic(c) => {
920 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(room_version)))
921 }
922 Self::SpaceChild(c) => {
923 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(room_version)))
924 }
925 Self::SpaceParent(c) => {
926 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(room_version)))
927 }
928 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
929 }
930 }
931}
932
933#[derive(Clone, Debug)]
935pub struct OtherState {
936 pub(in crate::timeline) state_key: String,
937 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
938}
939
940impl OtherState {
941 pub fn state_key(&self) -> &str {
943 &self.state_key
944 }
945
946 pub fn content(&self) -> &AnyOtherFullStateEventContent {
948 &self.content
949 }
950
951 fn redact(&self, room_version: &RoomVersionId) -> Self {
952 Self { state_key: self.state_key.clone(), content: self.content.redact(room_version) }
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use assert_matches2::assert_let;
959 use matrix_sdk_test::ALICE;
960 use ruma::{
961 assign,
962 events::{
963 room::member::{MembershipState, RoomMemberEventContent},
964 FullStateEventContent,
965 },
966 RoomVersionId,
967 };
968
969 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
970
971 #[test]
972 fn redact_membership_change() {
973 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
974 user_id: ALICE.to_owned(),
975 content: FullStateEventContent::Original {
976 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
977 reason: Some("🤬".to_owned()),
978 }),
979 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
980 },
981 change: Some(MembershipChange::Banned),
982 });
983
984 let redacted = content.redact(&RoomVersionId::V11);
985 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
986 assert_eq!(inner.change, Some(MembershipChange::Banned));
987 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
988 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
989 }
990}