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::{
73 message::{
74 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
75 },
76 polls::ResponseData,
77};
78pub use self::{
79 message::{InReplyToDetails, Message, RepliedToEvent},
80 polls::{PollResult, PollState},
81};
82
83#[derive(Clone, Debug)]
85pub enum TimelineItemContent {
86 Message(Message),
88
89 RedactedMessage,
91
92 Sticker(Sticker),
94
95 UnableToDecrypt(EncryptedMessage),
97
98 MembershipChange(RoomMembershipChange),
100
101 ProfileChange(MemberProfileChange),
103
104 OtherState(OtherState),
106
107 FailedToParseMessageLike {
109 event_type: MessageLikeEventType,
111
112 error: Arc<serde_json::Error>,
114 },
115
116 FailedToParseState {
118 event_type: StateEventType,
120
121 state_key: String,
123
124 error: Arc<serde_json::Error>,
126 },
127
128 Poll(PollState),
130
131 CallInvite,
133
134 CallNotify,
136}
137
138impl TimelineItemContent {
139 pub(crate) fn from_latest_event_content(
143 event: AnySyncTimelineEvent,
144 power_levels_info: Option<(&UserId, &RoomPowerLevels)>,
145 ) -> Option<TimelineItemContent> {
146 match is_suitable_for_latest_event(&event, power_levels_info) {
147 PossibleLatestEvent::YesRoomMessage(m) => {
148 Some(Self::from_suitable_latest_event_content(m))
149 }
150 PossibleLatestEvent::YesSticker(s) => {
151 Some(Self::from_suitable_latest_sticker_content(s))
152 }
153 PossibleLatestEvent::YesPoll(poll) => {
154 Some(Self::from_suitable_latest_poll_event_content(poll))
155 }
156 PossibleLatestEvent::YesCallInvite(call_invite) => {
157 Some(Self::from_suitable_latest_call_invite_content(call_invite))
158 }
159 PossibleLatestEvent::YesCallNotify(call_notify) => {
160 Some(Self::from_suitable_latest_call_notify_content(call_notify))
161 }
162 PossibleLatestEvent::NoUnsupportedEventType => {
163 warn!("Found a state event cached as latest_event! ID={}", event.event_id());
165 None
166 }
167 PossibleLatestEvent::NoUnsupportedMessageLikeType => {
168 warn!(
170 "Found an event cached as latest_event, but I don't know how \
171 to wrap it in a TimelineItemContent. type={}, ID={}",
172 event.event_type().to_string(),
173 event.event_id()
174 );
175 None
176 }
177 PossibleLatestEvent::YesKnockedStateEvent(member) => {
178 Some(Self::from_suitable_latest_knock_state_event_content(member))
179 }
180 PossibleLatestEvent::NoEncrypted => {
181 warn!("Found an encrypted event cached as latest_event! ID={}", event.event_id());
182 None
183 }
184 }
185 }
186
187 fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent {
191 match event {
192 SyncRoomMessageEvent::Original(event) => {
193 let event_content = event.content.clone();
195
196 let edit = event
198 .unsigned
199 .relations
200 .replace
201 .as_ref()
202 .and_then(|boxed| match &boxed.content.relates_to {
203 Some(Relation::Replacement(re)) => Some(re.new_content.clone()),
204 _ => {
205 warn!("got m.room.message event with an edit without a valid m.replace relation");
206 None
207 }
208 });
209
210 let timeline_items = Vector::new();
216 TimelineItemContent::Message(Message::from_event(
217 event_content,
218 edit,
219 &timeline_items,
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 { content: event_content })
253 }
254 SyncStickerEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
255 }
256 }
257
258 fn from_suitable_latest_poll_event_content(
261 event: &SyncUnstablePollStartEvent,
262 ) -> TimelineItemContent {
263 let SyncUnstablePollStartEvent::Original(event) = event else {
264 return TimelineItemContent::RedactedMessage;
265 };
266
267 let edit =
269 event.unsigned.relations.replace.as_ref().and_then(|boxed| match &boxed.content {
270 UnstablePollStartEventContent::Replacement(re) => {
271 Some(re.relates_to.new_content.clone())
272 }
273 _ => {
274 warn!("got poll event with an edit without a valid m.replace relation");
275 None
276 }
277 });
278
279 TimelineItemContent::Poll(PollState::new(
280 NewUnstablePollStartEventContent::new(event.content.poll_start().clone()),
281 edit,
282 ))
283 }
284
285 fn from_suitable_latest_call_invite_content(
286 event: &SyncCallInviteEvent,
287 ) -> TimelineItemContent {
288 match event {
289 SyncCallInviteEvent::Original(_) => TimelineItemContent::CallInvite,
290 SyncCallInviteEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
291 }
292 }
293
294 fn from_suitable_latest_call_notify_content(
295 event: &SyncCallNotifyEvent,
296 ) -> TimelineItemContent {
297 match event {
298 SyncCallNotifyEvent::Original(_) => TimelineItemContent::CallNotify,
299 SyncCallNotifyEvent::Redacted(_) => TimelineItemContent::RedactedMessage,
300 }
301 }
302
303 pub fn as_message(&self) -> Option<&Message> {
306 as_variant!(self, Self::Message)
307 }
308
309 pub fn as_poll(&self) -> Option<&PollState> {
312 as_variant!(self, Self::Poll)
313 }
314
315 pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> {
318 as_variant!(self, Self::UnableToDecrypt)
319 }
320
321 pub(crate) fn message(
324 c: RoomMessageEventContent,
325 edit: Option<RoomMessageEventContentWithoutRelation>,
326 timeline_items: &Vector<Arc<TimelineItem>>,
327 ) -> Self {
328 Self::Message(Message::from_event(c, edit, timeline_items))
329 }
330
331 #[cfg(not(tarpaulin_include))] pub(crate) fn debug_string(&self) -> &'static str {
333 match self {
334 TimelineItemContent::Message(_) => "a message",
335 TimelineItemContent::RedactedMessage => "a redacted messages",
336 TimelineItemContent::Sticker(_) => "a sticker",
337 TimelineItemContent::UnableToDecrypt(_) => "an encrypted message we couldn't decrypt",
338 TimelineItemContent::MembershipChange(_) => "a membership change",
339 TimelineItemContent::ProfileChange(_) => "a profile change",
340 TimelineItemContent::OtherState(_) => "a state event",
341 TimelineItemContent::FailedToParseMessageLike { .. }
342 | TimelineItemContent::FailedToParseState { .. } => "an event that couldn't be parsed",
343 TimelineItemContent::Poll(_) => "a poll",
344 TimelineItemContent::CallInvite => "a call invite",
345 TimelineItemContent::CallNotify => "a call notification",
346 }
347 }
348
349 pub(crate) fn unable_to_decrypt(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
350 Self::UnableToDecrypt(EncryptedMessage::from_content(content, cause))
351 }
352
353 pub(crate) fn room_member(
354 user_id: OwnedUserId,
355 full_content: FullStateEventContent<RoomMemberEventContent>,
356 sender: OwnedUserId,
357 ) -> Self {
358 use ruma::events::room::member::MembershipChange as MChange;
359 match &full_content {
360 FullStateEventContent::Original { content, prev_content } => {
361 let membership_change = content.membership_change(
362 prev_content.as_ref().map(|c| c.details()),
363 &sender,
364 &user_id,
365 );
366
367 if let MChange::ProfileChanged { displayname_change, avatar_url_change } =
368 membership_change
369 {
370 Self::ProfileChange(MemberProfileChange {
371 user_id,
372 displayname_change: displayname_change.map(|c| Change {
373 new: c.new.map(ToOwned::to_owned),
374 old: c.old.map(ToOwned::to_owned),
375 }),
376 avatar_url_change: avatar_url_change.map(|c| Change {
377 new: c.new.map(ToOwned::to_owned),
378 old: c.old.map(ToOwned::to_owned),
379 }),
380 })
381 } else {
382 let change = match membership_change {
383 MChange::None => MembershipChange::None,
384 MChange::Error => MembershipChange::Error,
385 MChange::Joined => MembershipChange::Joined,
386 MChange::Left => MembershipChange::Left,
387 MChange::Banned => MembershipChange::Banned,
388 MChange::Unbanned => MembershipChange::Unbanned,
389 MChange::Kicked => MembershipChange::Kicked,
390 MChange::Invited => MembershipChange::Invited,
391 MChange::KickedAndBanned => MembershipChange::KickedAndBanned,
392 MChange::InvitationAccepted => MembershipChange::InvitationAccepted,
393 MChange::InvitationRejected => MembershipChange::InvitationRejected,
394 MChange::InvitationRevoked => MembershipChange::InvitationRevoked,
395 MChange::Knocked => MembershipChange::Knocked,
396 MChange::KnockAccepted => MembershipChange::KnockAccepted,
397 MChange::KnockRetracted => MembershipChange::KnockRetracted,
398 MChange::KnockDenied => MembershipChange::KnockDenied,
399 MChange::ProfileChanged { .. } => unreachable!(),
400 _ => MembershipChange::NotImplemented,
401 };
402
403 Self::MembershipChange(RoomMembershipChange {
404 user_id,
405 content: full_content,
406 change: Some(change),
407 })
408 }
409 }
410 FullStateEventContent::Redacted(_) => Self::MembershipChange(RoomMembershipChange {
411 user_id,
412 content: full_content,
413 change: None,
414 }),
415 }
416 }
417
418 pub(in crate::timeline) fn redact(&self, room_version: &RoomVersionId) -> Self {
419 match self {
420 Self::Message(_)
421 | Self::RedactedMessage
422 | Self::Sticker(_)
423 | Self::Poll(_)
424 | Self::CallInvite
425 | Self::CallNotify
426 | Self::UnableToDecrypt(_) => Self::RedactedMessage,
427 Self::MembershipChange(ev) => Self::MembershipChange(ev.redact(room_version)),
428 Self::ProfileChange(ev) => Self::ProfileChange(ev.redact()),
429 Self::OtherState(ev) => Self::OtherState(ev.redact(room_version)),
430 Self::FailedToParseMessageLike { .. } | Self::FailedToParseState { .. } => self.clone(),
431 }
432 }
433}
434
435#[derive(Clone, Debug)]
437pub enum EncryptedMessage {
438 OlmV1Curve25519AesSha2 {
441 sender_key: String,
443 },
444 MegolmV1AesSha2 {
446 #[deprecated = "this field still needs to be sent but should not be used when received"]
448 #[doc(hidden)] sender_key: String,
450
451 #[deprecated = "this field still needs to be sent but should not be used when received"]
453 #[doc(hidden)] device_id: OwnedDeviceId,
455
456 session_id: String,
458
459 cause: UtdCause,
462 },
463 Unknown,
465}
466
467impl EncryptedMessage {
468 fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
469 match content.scheme {
470 EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
471 Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
472 }
473 #[allow(deprecated)]
474 EncryptedEventScheme::MegolmV1AesSha2(s) => {
475 let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
476
477 Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
478 }
479 _ => Self::Unknown,
480 }
481 }
482}
483
484#[derive(Clone, Debug)]
486pub struct Sticker {
487 pub(in crate::timeline) content: StickerEventContent,
488}
489
490impl Sticker {
491 pub fn content(&self) -> &StickerEventContent {
493 &self.content
494 }
495}
496
497#[derive(Clone, Debug)]
499pub struct RoomMembershipChange {
500 pub(in crate::timeline) user_id: OwnedUserId,
501 pub(in crate::timeline) content: FullStateEventContent<RoomMemberEventContent>,
502 pub(in crate::timeline) change: Option<MembershipChange>,
503}
504
505impl RoomMembershipChange {
506 pub fn user_id(&self) -> &UserId {
508 &self.user_id
509 }
510
511 pub fn content(&self) -> &FullStateEventContent<RoomMemberEventContent> {
513 &self.content
514 }
515
516 pub fn display_name(&self) -> Option<String> {
519 if let FullStateEventContent::Original { content, prev_content } = &self.content {
520 content
521 .displayname
522 .as_ref()
523 .or_else(|| {
524 prev_content.as_ref().and_then(|prev_content| prev_content.displayname.as_ref())
525 })
526 .cloned()
527 } else {
528 None
529 }
530 }
531
532 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
535 if let FullStateEventContent::Original { content, prev_content } = &self.content {
536 content
537 .avatar_url
538 .as_ref()
539 .or_else(|| {
540 prev_content.as_ref().and_then(|prev_content| prev_content.avatar_url.as_ref())
541 })
542 .cloned()
543 } else {
544 None
545 }
546 }
547
548 pub fn change(&self) -> Option<MembershipChange> {
556 self.change
557 }
558
559 fn redact(&self, room_version: &RoomVersionId) -> Self {
560 Self {
561 user_id: self.user_id.clone(),
562 content: FullStateEventContent::Redacted(self.content.clone().redact(room_version)),
563 change: self.change,
564 }
565 }
566}
567
568#[derive(Clone, Copy, Debug, PartialEq, Eq)]
570pub enum MembershipChange {
571 None,
573
574 Error,
576
577 Joined,
579
580 Left,
582
583 Banned,
585
586 Unbanned,
588
589 Kicked,
591
592 Invited,
594
595 KickedAndBanned,
597
598 InvitationAccepted,
600
601 InvitationRejected,
603
604 InvitationRevoked,
606
607 Knocked,
609
610 KnockAccepted,
612
613 KnockRetracted,
615
616 KnockDenied,
618
619 NotImplemented,
621}
622
623#[derive(Clone, Debug)]
628pub struct MemberProfileChange {
629 pub(in crate::timeline) user_id: OwnedUserId,
630 pub(in crate::timeline) displayname_change: Option<Change<Option<String>>>,
631 pub(in crate::timeline) avatar_url_change: Option<Change<Option<OwnedMxcUri>>>,
632}
633
634impl MemberProfileChange {
635 pub fn user_id(&self) -> &UserId {
637 &self.user_id
638 }
639
640 pub fn displayname_change(&self) -> Option<&Change<Option<String>>> {
642 self.displayname_change.as_ref()
643 }
644
645 pub fn avatar_url_change(&self) -> Option<&Change<Option<OwnedMxcUri>>> {
647 self.avatar_url_change.as_ref()
648 }
649
650 fn redact(&self) -> Self {
651 Self {
652 user_id: self.user_id.clone(),
653 displayname_change: None,
658 avatar_url_change: None,
659 }
660 }
661}
662
663#[derive(Clone, Debug)]
666pub enum AnyOtherFullStateEventContent {
667 PolicyRuleRoom(FullStateEventContent<PolicyRuleRoomEventContent>),
669
670 PolicyRuleServer(FullStateEventContent<PolicyRuleServerEventContent>),
672
673 PolicyRuleUser(FullStateEventContent<PolicyRuleUserEventContent>),
675
676 RoomAliases(FullStateEventContent<RoomAliasesEventContent>),
678
679 RoomAvatar(FullStateEventContent<RoomAvatarEventContent>),
681
682 RoomCanonicalAlias(FullStateEventContent<RoomCanonicalAliasEventContent>),
684
685 RoomCreate(FullStateEventContent<RoomCreateEventContent>),
687
688 RoomEncryption(FullStateEventContent<RoomEncryptionEventContent>),
690
691 RoomGuestAccess(FullStateEventContent<RoomGuestAccessEventContent>),
693
694 RoomHistoryVisibility(FullStateEventContent<RoomHistoryVisibilityEventContent>),
696
697 RoomJoinRules(FullStateEventContent<RoomJoinRulesEventContent>),
699
700 RoomName(FullStateEventContent<RoomNameEventContent>),
702
703 RoomPinnedEvents(FullStateEventContent<RoomPinnedEventsEventContent>),
705
706 RoomPowerLevels(FullStateEventContent<RoomPowerLevelsEventContent>),
708
709 RoomServerAcl(FullStateEventContent<RoomServerAclEventContent>),
711
712 RoomThirdPartyInvite(FullStateEventContent<RoomThirdPartyInviteEventContent>),
714
715 RoomTombstone(FullStateEventContent<RoomTombstoneEventContent>),
717
718 RoomTopic(FullStateEventContent<RoomTopicEventContent>),
720
721 SpaceChild(FullStateEventContent<SpaceChildEventContent>),
723
724 SpaceParent(FullStateEventContent<SpaceParentEventContent>),
726
727 #[doc(hidden)]
728 _Custom { event_type: String },
729}
730
731impl AnyOtherFullStateEventContent {
732 pub(crate) fn with_event_content(content: AnyFullStateEventContent) -> Self {
738 let event_type = content.event_type();
739
740 match content {
741 AnyFullStateEventContent::PolicyRuleRoom(c) => Self::PolicyRuleRoom(c),
742 AnyFullStateEventContent::PolicyRuleServer(c) => Self::PolicyRuleServer(c),
743 AnyFullStateEventContent::PolicyRuleUser(c) => Self::PolicyRuleUser(c),
744 AnyFullStateEventContent::RoomAliases(c) => Self::RoomAliases(c),
745 AnyFullStateEventContent::RoomAvatar(c) => Self::RoomAvatar(c),
746 AnyFullStateEventContent::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(c),
747 AnyFullStateEventContent::RoomCreate(c) => Self::RoomCreate(c),
748 AnyFullStateEventContent::RoomEncryption(c) => Self::RoomEncryption(c),
749 AnyFullStateEventContent::RoomGuestAccess(c) => Self::RoomGuestAccess(c),
750 AnyFullStateEventContent::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(c),
751 AnyFullStateEventContent::RoomJoinRules(c) => Self::RoomJoinRules(c),
752 AnyFullStateEventContent::RoomName(c) => Self::RoomName(c),
753 AnyFullStateEventContent::RoomPinnedEvents(c) => Self::RoomPinnedEvents(c),
754 AnyFullStateEventContent::RoomPowerLevels(c) => Self::RoomPowerLevels(c),
755 AnyFullStateEventContent::RoomServerAcl(c) => Self::RoomServerAcl(c),
756 AnyFullStateEventContent::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(c),
757 AnyFullStateEventContent::RoomTombstone(c) => Self::RoomTombstone(c),
758 AnyFullStateEventContent::RoomTopic(c) => Self::RoomTopic(c),
759 AnyFullStateEventContent::SpaceChild(c) => Self::SpaceChild(c),
760 AnyFullStateEventContent::SpaceParent(c) => Self::SpaceParent(c),
761 AnyFullStateEventContent::RoomMember(_) => unreachable!(),
762 _ => Self::_Custom { event_type: event_type.to_string() },
763 }
764 }
765
766 pub fn event_type(&self) -> StateEventType {
768 match self {
769 Self::PolicyRuleRoom(c) => c.event_type(),
770 Self::PolicyRuleServer(c) => c.event_type(),
771 Self::PolicyRuleUser(c) => c.event_type(),
772 Self::RoomAliases(c) => c.event_type(),
773 Self::RoomAvatar(c) => c.event_type(),
774 Self::RoomCanonicalAlias(c) => c.event_type(),
775 Self::RoomCreate(c) => c.event_type(),
776 Self::RoomEncryption(c) => c.event_type(),
777 Self::RoomGuestAccess(c) => c.event_type(),
778 Self::RoomHistoryVisibility(c) => c.event_type(),
779 Self::RoomJoinRules(c) => c.event_type(),
780 Self::RoomName(c) => c.event_type(),
781 Self::RoomPinnedEvents(c) => c.event_type(),
782 Self::RoomPowerLevels(c) => c.event_type(),
783 Self::RoomServerAcl(c) => c.event_type(),
784 Self::RoomThirdPartyInvite(c) => c.event_type(),
785 Self::RoomTombstone(c) => c.event_type(),
786 Self::RoomTopic(c) => c.event_type(),
787 Self::SpaceChild(c) => c.event_type(),
788 Self::SpaceParent(c) => c.event_type(),
789 Self::_Custom { event_type } => event_type.as_str().into(),
790 }
791 }
792
793 fn redact(&self, room_version: &RoomVersionId) -> Self {
794 match self {
795 Self::PolicyRuleRoom(c) => Self::PolicyRuleRoom(FullStateEventContent::Redacted(
796 c.clone().redact(room_version),
797 )),
798 Self::PolicyRuleServer(c) => Self::PolicyRuleServer(FullStateEventContent::Redacted(
799 c.clone().redact(room_version),
800 )),
801 Self::PolicyRuleUser(c) => Self::PolicyRuleUser(FullStateEventContent::Redacted(
802 c.clone().redact(room_version),
803 )),
804 Self::RoomAliases(c) => {
805 Self::RoomAliases(FullStateEventContent::Redacted(c.clone().redact(room_version)))
806 }
807 Self::RoomAvatar(c) => {
808 Self::RoomAvatar(FullStateEventContent::Redacted(c.clone().redact(room_version)))
809 }
810 Self::RoomCanonicalAlias(c) => Self::RoomCanonicalAlias(
811 FullStateEventContent::Redacted(c.clone().redact(room_version)),
812 ),
813 Self::RoomCreate(c) => {
814 Self::RoomCreate(FullStateEventContent::Redacted(c.clone().redact(room_version)))
815 }
816 Self::RoomEncryption(c) => Self::RoomEncryption(FullStateEventContent::Redacted(
817 c.clone().redact(room_version),
818 )),
819 Self::RoomGuestAccess(c) => Self::RoomGuestAccess(FullStateEventContent::Redacted(
820 c.clone().redact(room_version),
821 )),
822 Self::RoomHistoryVisibility(c) => Self::RoomHistoryVisibility(
823 FullStateEventContent::Redacted(c.clone().redact(room_version)),
824 ),
825 Self::RoomJoinRules(c) => {
826 Self::RoomJoinRules(FullStateEventContent::Redacted(c.clone().redact(room_version)))
827 }
828 Self::RoomName(c) => {
829 Self::RoomName(FullStateEventContent::Redacted(c.clone().redact(room_version)))
830 }
831 Self::RoomPinnedEvents(c) => Self::RoomPinnedEvents(FullStateEventContent::Redacted(
832 c.clone().redact(room_version),
833 )),
834 Self::RoomPowerLevels(c) => Self::RoomPowerLevels(FullStateEventContent::Redacted(
835 c.clone().redact(room_version),
836 )),
837 Self::RoomServerAcl(c) => {
838 Self::RoomServerAcl(FullStateEventContent::Redacted(c.clone().redact(room_version)))
839 }
840 Self::RoomThirdPartyInvite(c) => Self::RoomThirdPartyInvite(
841 FullStateEventContent::Redacted(c.clone().redact(room_version)),
842 ),
843 Self::RoomTombstone(c) => {
844 Self::RoomTombstone(FullStateEventContent::Redacted(c.clone().redact(room_version)))
845 }
846 Self::RoomTopic(c) => {
847 Self::RoomTopic(FullStateEventContent::Redacted(c.clone().redact(room_version)))
848 }
849 Self::SpaceChild(c) => {
850 Self::SpaceChild(FullStateEventContent::Redacted(c.clone().redact(room_version)))
851 }
852 Self::SpaceParent(c) => {
853 Self::SpaceParent(FullStateEventContent::Redacted(c.clone().redact(room_version)))
854 }
855 Self::_Custom { event_type } => Self::_Custom { event_type: event_type.clone() },
856 }
857 }
858}
859
860#[derive(Clone, Debug)]
862pub struct OtherState {
863 pub(in crate::timeline) state_key: String,
864 pub(in crate::timeline) content: AnyOtherFullStateEventContent,
865}
866
867impl OtherState {
868 pub fn state_key(&self) -> &str {
870 &self.state_key
871 }
872
873 pub fn content(&self) -> &AnyOtherFullStateEventContent {
875 &self.content
876 }
877
878 fn redact(&self, room_version: &RoomVersionId) -> Self {
879 Self { state_key: self.state_key.clone(), content: self.content.redact(room_version) }
880 }
881}
882
883#[cfg(test)]
884mod tests {
885 use assert_matches2::assert_let;
886 use matrix_sdk_test::ALICE;
887 use ruma::{
888 assign,
889 events::{
890 room::member::{MembershipState, RoomMemberEventContent},
891 FullStateEventContent,
892 },
893 RoomVersionId,
894 };
895
896 use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
897
898 #[test]
899 fn redact_membership_change() {
900 let content = TimelineItemContent::MembershipChange(RoomMembershipChange {
901 user_id: ALICE.to_owned(),
902 content: FullStateEventContent::Original {
903 content: assign!(RoomMemberEventContent::new(MembershipState::Ban), {
904 reason: Some("🤬".to_owned()),
905 }),
906 prev_content: Some(RoomMemberEventContent::new(MembershipState::Join)),
907 },
908 change: Some(MembershipChange::Banned),
909 });
910
911 let redacted = content.redact(&RoomVersionId::V11);
912 assert_let!(TimelineItemContent::MembershipChange(inner) = redacted);
913 assert_eq!(inner.change, Some(MembershipChange::Banned));
914 assert_let!(FullStateEventContent::Redacted(inner_content_redacted) = inner.content);
915 assert_eq!(inner_content_redacted.membership, MembershipState::Ban);
916 }
917}