matrix_sdk_ui/timeline/event_item/content/
reply.rs
1use std::sync::Arc;
16
17use imbl::Vector;
18use matrix_sdk::{
19 crypto::types::events::UtdCause,
20 deserialized_responses::{TimelineEvent, TimelineEventKind},
21 Room,
22};
23use ruma::{
24 events::{
25 poll::unstable_start::UnstablePollStartEventContent, AnyMessageLikeEventContent,
26 AnySyncTimelineEvent,
27 },
28 html::RemoveReplyFallback,
29 OwnedEventId, OwnedUserId, UserId,
30};
31use tracing::{debug, instrument, warn};
32
33use super::TimelineItemContent;
34use crate::timeline::{
35 event_item::{
36 content::{MsgLikeContent, MsgLikeKind},
37 extract_room_msg_edit_content, EventTimelineItem, Profile, TimelineDetails,
38 },
39 traits::RoomDataProvider,
40 EncryptedMessage, Error as TimelineError, Message, PollState, ReactionsByKeyBySender, Sticker,
41 TimelineItem,
42};
43
44#[derive(Clone, Debug)]
46pub struct InReplyToDetails {
47 pub event_id: OwnedEventId,
49
50 pub event: TimelineDetails<Box<RepliedToEvent>>,
57}
58
59impl InReplyToDetails {
60 pub fn new(
61 event_id: OwnedEventId,
62 timeline_items: &Vector<Arc<TimelineItem>>,
63 ) -> InReplyToDetails {
64 let event = timeline_items
65 .iter()
66 .filter_map(|it| it.as_event())
67 .find(|it| it.event_id() == Some(&*event_id))
68 .map(|item| Box::new(RepliedToEvent::from_timeline_item(item)));
69
70 InReplyToDetails { event_id, event: TimelineDetails::from_initial_value(event) }
71 }
72}
73
74#[derive(Clone, Debug)]
76pub struct RepliedToEvent {
77 content: TimelineItemContent,
78 sender: OwnedUserId,
79 sender_profile: TimelineDetails<Profile>,
80}
81
82impl RepliedToEvent {
83 pub fn content(&self) -> &TimelineItemContent {
85 &self.content
86 }
87
88 pub fn sender(&self) -> &UserId {
90 &self.sender
91 }
92
93 pub fn sender_profile(&self) -> &TimelineDetails<Profile> {
95 &self.sender_profile
96 }
97
98 pub fn from_timeline_item(timeline_item: &EventTimelineItem) -> Self {
100 Self {
101 content: timeline_item.content.clone(),
102 sender: timeline_item.sender.clone(),
103 sender_profile: timeline_item.sender_profile.clone(),
104 }
105 }
106
107 pub async fn try_from_timeline_event_for_room(
110 timeline_event: TimelineEvent,
111 room_data_provider: &Room,
112 ) -> Result<Self, TimelineError> {
113 Self::try_from_timeline_event(timeline_event, room_data_provider).await
114 }
115
116 #[instrument(skip_all)]
117 pub(in crate::timeline) async fn try_from_timeline_event<P: RoomDataProvider>(
118 timeline_event: TimelineEvent,
119 room_data_provider: &P,
120 ) -> Result<Self, TimelineError> {
121 let event = match timeline_event.raw().deserialize() {
122 Ok(AnySyncTimelineEvent::MessageLike(event)) => event,
123 Ok(_) => {
124 warn!("can't get details, event isn't a message-like event");
125 return Err(TimelineError::UnsupportedEvent);
126 }
127 Err(err) => {
128 warn!("can't get details, event couldn't be deserialized: {err}");
129 return Err(TimelineError::UnsupportedEvent);
130 }
131 };
132
133 debug!(event_type = %event.event_type(), "got deserialized event");
134
135 let content = match event.original_content() {
136 Some(content) => match content {
137 AnyMessageLikeEventContent::RoomMessage(c) => {
138 let reactions = ReactionsByKeyBySender::default();
142 let thread_root = None;
143
144 TimelineItemContent::MsgLike(MsgLikeContent {
145 kind: MsgLikeKind::Message(Message::from_event(
146 c,
147 extract_room_msg_edit_content(event.relations()),
148 RemoveReplyFallback::Yes,
149 )),
150 reactions,
151 thread_root,
152 in_reply_to: None,
153 })
154 }
155
156 AnyMessageLikeEventContent::Sticker(content) => {
157 let reactions = ReactionsByKeyBySender::default();
160 let thread_root = None;
161
162 TimelineItemContent::MsgLike(MsgLikeContent {
163 kind: MsgLikeKind::Sticker(Sticker { content }),
164 reactions,
165 thread_root,
166 in_reply_to: None,
167 })
168 }
169
170 AnyMessageLikeEventContent::RoomEncrypted(content) => {
171 let utd_cause = match &timeline_event.kind {
172 TimelineEventKind::UnableToDecrypt { utd_info, .. } => UtdCause::determine(
173 timeline_event.raw(),
174 room_data_provider.crypto_context_info().await,
175 utd_info,
176 ),
177 _ => UtdCause::Unknown,
178 };
179
180 TimelineItemContent::MsgLike(MsgLikeContent::unable_to_decrypt(
181 EncryptedMessage::from_content(content, utd_cause),
182 ))
183 }
184
185 AnyMessageLikeEventContent::UnstablePollStart(
186 UnstablePollStartEventContent::New(content),
187 ) => {
188 let reactions = ReactionsByKeyBySender::default();
191 let thread_root = None;
192
193 let poll_state = PollState::new(content, None);
195 TimelineItemContent::MsgLike(MsgLikeContent {
196 kind: MsgLikeKind::Poll(poll_state),
197 reactions,
198 thread_root,
199 in_reply_to: None,
200 })
201 }
202
203 _ => {
204 warn!("unsupported event type");
205 return Err(TimelineError::UnsupportedEvent);
206 }
207 },
208
209 None => TimelineItemContent::MsgLike(MsgLikeContent::redacted()),
210 };
211
212 let sender = event.sender().to_owned();
213 let sender_profile = TimelineDetails::from_initial_value(
214 room_data_provider.profile_from_user_id(&sender).await,
215 );
216
217 Ok(Self { content, sender, sender_profile })
218 }
219}