1use std::{
16 ops::{Deref, DerefMut},
17 sync::Arc,
18};
19
20use as_variant::as_variant;
21use indexmap::IndexMap;
22use matrix_sdk::{
23 Error,
24 deserialized_responses::{EncryptionInfo, ShieldState},
25 send_queue::{SendHandle, SendReactionHandle},
26};
27use matrix_sdk_base::deserialized_responses::{SENT_IN_CLEAR, ShieldStateCode};
28use once_cell::sync::Lazy;
29use ruma::{
30 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedTransactionId,
31 OwnedUserId, TransactionId, UserId,
32 events::{AnySyncTimelineEvent, receipt::Receipt, room::message::MessageType},
33 room_version_rules::RedactionRules,
34 serde::Raw,
35};
36use unicode_segmentation::UnicodeSegmentation;
37
38mod content;
39mod local;
40mod remote;
41
42pub use self::{
43 content::{
44 AnyOtherFullStateEventContent, EmbeddedEvent, EncryptedMessage, InReplyToDetails,
45 MemberProfileChange, MembershipChange, Message, MsgLikeContent, MsgLikeKind,
46 OtherMessageLike, OtherState, PollResult, PollState, RoomMembershipChange,
47 RoomPinnedEventsChange, Sticker, ThreadSummary, TimelineItemContent,
48 },
49 local::{EventSendState, MediaUploadProgress},
50};
51pub(super) use self::{
52 content::{
53 extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
54 },
55 local::LocalEventTimelineItem,
56 remote::{RemoteEventOrigin, RemoteEventTimelineItem},
57};
58
59#[derive(Clone, Debug)]
65pub struct EventTimelineItem {
66 pub(super) sender: OwnedUserId,
68 pub(super) sender_profile: TimelineDetails<Profile>,
70 pub(super) timestamp: MilliSecondsSinceUnixEpoch,
72 pub(super) content: TimelineItemContent,
74 pub(super) kind: EventTimelineItemKind,
76 pub(super) is_room_encrypted: bool,
80}
81
82#[derive(Clone, Debug)]
83pub(super) enum EventTimelineItemKind {
84 Local(LocalEventTimelineItem),
86 Remote(RemoteEventTimelineItem),
88}
89
90#[derive(Clone, Debug, Eq, Hash, PartialEq)]
92pub enum TimelineEventItemId {
93 TransactionId(OwnedTransactionId),
96 EventId(OwnedEventId),
98}
99
100pub(crate) enum TimelineItemHandle<'a> {
106 Remote(&'a EventId),
107 Local(&'a SendHandle),
108}
109
110impl EventTimelineItem {
111 pub(super) fn new(
112 sender: OwnedUserId,
113 sender_profile: TimelineDetails<Profile>,
114 timestamp: MilliSecondsSinceUnixEpoch,
115 content: TimelineItemContent,
116 kind: EventTimelineItemKind,
117 is_room_encrypted: bool,
118 ) -> Self {
119 Self { sender, sender_profile, timestamp, content, kind, is_room_encrypted }
120 }
121
122 pub fn is_local_echo(&self) -> bool {
129 matches!(self.kind, EventTimelineItemKind::Local(_))
130 }
131
132 pub fn is_remote_event(&self) -> bool {
140 matches!(self.kind, EventTimelineItemKind::Remote(_))
141 }
142
143 pub(super) fn as_local(&self) -> Option<&LocalEventTimelineItem> {
145 as_variant!(&self.kind, EventTimelineItemKind::Local(local_event_item) => local_event_item)
146 }
147
148 pub(super) fn as_remote(&self) -> Option<&RemoteEventTimelineItem> {
150 as_variant!(&self.kind, EventTimelineItemKind::Remote(remote_event_item) => remote_event_item)
151 }
152
153 pub(super) fn as_remote_mut(&mut self) -> Option<&mut RemoteEventTimelineItem> {
156 as_variant!(&mut self.kind, EventTimelineItemKind::Remote(remote_event_item) => remote_event_item)
157 }
158
159 pub fn send_state(&self) -> Option<&EventSendState> {
161 as_variant!(&self.kind, EventTimelineItemKind::Local(local) => &local.send_state)
162 }
163
164 pub fn local_created_at(&self) -> Option<MilliSecondsSinceUnixEpoch> {
166 match &self.kind {
167 EventTimelineItemKind::Local(local) => local.send_handle.as_ref().map(|s| s.created_at),
168 EventTimelineItemKind::Remote(_) => None,
169 }
170 }
171
172 pub fn identifier(&self) -> TimelineEventItemId {
178 match &self.kind {
179 EventTimelineItemKind::Local(local) => local.identifier(),
180 EventTimelineItemKind::Remote(remote) => {
181 TimelineEventItemId::EventId(remote.event_id.clone())
182 }
183 }
184 }
185
186 pub fn transaction_id(&self) -> Option<&TransactionId> {
191 as_variant!(&self.kind, EventTimelineItemKind::Local(local) => &local.transaction_id)
192 }
193
194 pub fn event_id(&self) -> Option<&EventId> {
203 match &self.kind {
204 EventTimelineItemKind::Local(local_event) => local_event.event_id(),
205 EventTimelineItemKind::Remote(remote_event) => Some(&remote_event.event_id),
206 }
207 }
208
209 pub fn sender(&self) -> &UserId {
211 &self.sender
212 }
213
214 pub fn sender_profile(&self) -> &TimelineDetails<Profile> {
216 &self.sender_profile
217 }
218
219 pub fn content(&self) -> &TimelineItemContent {
221 &self.content
222 }
223
224 pub(crate) fn content_mut(&mut self) -> &mut TimelineItemContent {
226 &mut self.content
227 }
228
229 pub fn read_receipts(&self) -> &IndexMap<OwnedUserId, Receipt> {
236 static EMPTY_RECEIPTS: Lazy<IndexMap<OwnedUserId, Receipt>> = Lazy::new(Default::default);
237 match &self.kind {
238 EventTimelineItemKind::Local(_) => &EMPTY_RECEIPTS,
239 EventTimelineItemKind::Remote(remote_event) => &remote_event.read_receipts,
240 }
241 }
242
243 pub fn timestamp(&self) -> MilliSecondsSinceUnixEpoch {
249 self.timestamp
250 }
251
252 pub fn is_own(&self) -> bool {
254 match &self.kind {
255 EventTimelineItemKind::Local(_) => true,
256 EventTimelineItemKind::Remote(remote_event) => remote_event.is_own,
257 }
258 }
259
260 pub fn is_editable(&self) -> bool {
262 if !self.is_own() {
266 return false;
268 }
269
270 match self.content() {
271 TimelineItemContent::MsgLike(msglike) => match &msglike.kind {
272 MsgLikeKind::Message(message) => match message.msgtype() {
273 MessageType::Text(_)
274 | MessageType::Emote(_)
275 | MessageType::Audio(_)
276 | MessageType::File(_)
277 | MessageType::Image(_)
278 | MessageType::Video(_) => true,
279 #[cfg(feature = "unstable-msc4274")]
280 MessageType::Gallery(_) => true,
281 _ => false,
282 },
283 MsgLikeKind::Poll(poll) => {
284 poll.response_data.is_empty() && poll.end_event_timestamp.is_none()
285 }
286 _ => false,
288 },
289 _ => {
290 false
292 }
293 }
294 }
295
296 pub fn is_highlighted(&self) -> bool {
298 match &self.kind {
299 EventTimelineItemKind::Local(_) => false,
300 EventTimelineItemKind::Remote(remote_event) => remote_event.is_highlighted,
301 }
302 }
303
304 pub fn encryption_info(&self) -> Option<&EncryptionInfo> {
306 match &self.kind {
307 EventTimelineItemKind::Local(_) => None,
308 EventTimelineItemKind::Remote(remote_event) => remote_event.encryption_info.as_deref(),
309 }
310 }
311
312 pub fn get_shield(&self, strict: bool) -> Option<ShieldState> {
315 if !self.is_room_encrypted || self.is_local_echo() {
316 return None;
317 }
318
319 if self.content().is_unable_to_decrypt() {
321 return None;
322 }
323
324 match self.encryption_info() {
325 Some(info) => {
326 if strict {
327 Some(info.verification_state.to_shield_state_strict())
328 } else {
329 Some(info.verification_state.to_shield_state_lax())
330 }
331 }
332 None => Some(ShieldState::Red {
333 code: ShieldStateCode::SentInClear,
334 message: SENT_IN_CLEAR,
335 }),
336 }
337 }
338
339 pub fn can_be_replied_to(&self) -> bool {
341 if self.event_id().is_none() {
343 false
344 } else if self.content.is_message() {
345 true
346 } else {
347 self.latest_json().is_some()
348 }
349 }
350
351 pub fn original_json(&self) -> Option<&Raw<AnySyncTimelineEvent>> {
357 match &self.kind {
358 EventTimelineItemKind::Local(_) => None,
359 EventTimelineItemKind::Remote(remote_event) => remote_event.original_json.as_ref(),
360 }
361 }
362
363 pub fn latest_edit_json(&self) -> Option<&Raw<AnySyncTimelineEvent>> {
365 match &self.kind {
366 EventTimelineItemKind::Local(_) => None,
367 EventTimelineItemKind::Remote(remote_event) => remote_event.latest_edit_json.as_ref(),
368 }
369 }
370
371 pub fn latest_json(&self) -> Option<&Raw<AnySyncTimelineEvent>> {
374 self.latest_edit_json().or_else(|| self.original_json())
375 }
376
377 pub fn origin(&self) -> Option<EventItemOrigin> {
381 match &self.kind {
382 EventTimelineItemKind::Local(_) => Some(EventItemOrigin::Local),
383 EventTimelineItemKind::Remote(remote_event) => match remote_event.origin {
384 RemoteEventOrigin::Sync => Some(EventItemOrigin::Sync),
385 RemoteEventOrigin::Pagination => Some(EventItemOrigin::Pagination),
386 RemoteEventOrigin::Cache => Some(EventItemOrigin::Cache),
387 RemoteEventOrigin::Unknown => None,
388 },
389 }
390 }
391
392 pub(super) fn set_content(&mut self, content: TimelineItemContent) {
393 self.content = content;
394 }
395
396 pub(super) fn with_kind(&self, kind: impl Into<EventTimelineItemKind>) -> Self {
398 Self { kind: kind.into(), ..self.clone() }
399 }
400
401 pub(super) fn with_content(&self, new_content: TimelineItemContent) -> Self {
403 let mut new = self.clone();
404 new.content = new_content;
405 new
406 }
407
408 pub(super) fn with_content_and_latest_edit(
413 &self,
414 new_content: TimelineItemContent,
415 edit_json: Option<Raw<AnySyncTimelineEvent>>,
416 ) -> Self {
417 let mut new = self.clone();
418 new.content = new_content;
419 if let EventTimelineItemKind::Remote(r) = &mut new.kind {
420 r.latest_edit_json = edit_json;
421 }
422 new
423 }
424
425 pub(super) fn with_sender_profile(&self, sender_profile: TimelineDetails<Profile>) -> Self {
427 Self { sender_profile, ..self.clone() }
428 }
429
430 pub(super) fn with_encryption_info(
432 &self,
433 encryption_info: Option<Arc<EncryptionInfo>>,
434 ) -> Self {
435 let mut new = self.clone();
436 if let EventTimelineItemKind::Remote(r) = &mut new.kind {
437 r.encryption_info = encryption_info;
438 }
439
440 new
441 }
442
443 pub(super) fn redact(&self, rules: &RedactionRules) -> Self {
445 let content = self.content.redact(rules);
446 let kind = match &self.kind {
447 EventTimelineItemKind::Local(l) => EventTimelineItemKind::Local(l.clone()),
448 EventTimelineItemKind::Remote(r) => EventTimelineItemKind::Remote(r.redact()),
449 };
450 Self {
451 sender: self.sender.clone(),
452 sender_profile: self.sender_profile.clone(),
453 timestamp: self.timestamp,
454 content,
455 kind,
456 is_room_encrypted: self.is_room_encrypted,
457 }
458 }
459
460 pub(super) fn handle(&self) -> TimelineItemHandle<'_> {
461 match &self.kind {
462 EventTimelineItemKind::Local(local) => {
463 if let Some(event_id) = local.event_id() {
464 TimelineItemHandle::Remote(event_id)
465 } else {
466 TimelineItemHandle::Local(
467 local.send_handle.as_ref().expect("Unexpected missing send_handle"),
469 )
470 }
471 }
472 EventTimelineItemKind::Remote(remote) => TimelineItemHandle::Remote(&remote.event_id),
473 }
474 }
475
476 pub fn local_echo_send_handle(&self) -> Option<SendHandle> {
478 as_variant!(self.handle(), TimelineItemHandle::Local(handle) => handle.clone())
479 }
480
481 pub fn contains_only_emojis(&self) -> bool {
504 let body = match self.content() {
505 TimelineItemContent::MsgLike(msglike) => match &msglike.kind {
506 MsgLikeKind::Message(message) => match &message.msgtype {
507 MessageType::Text(text) => Some(text.body.as_str()),
508 MessageType::Audio(audio) => audio.caption(),
509 MessageType::File(file) => file.caption(),
510 MessageType::Image(image) => image.caption(),
511 MessageType::Video(video) => video.caption(),
512 _ => None,
513 },
514 MsgLikeKind::Sticker(_)
515 | MsgLikeKind::Poll(_)
516 | MsgLikeKind::Redacted
517 | MsgLikeKind::UnableToDecrypt(_)
518 | MsgLikeKind::Other(_) => None,
519 },
520 TimelineItemContent::MembershipChange(_)
521 | TimelineItemContent::ProfileChange(_)
522 | TimelineItemContent::OtherState(_)
523 | TimelineItemContent::FailedToParseMessageLike { .. }
524 | TimelineItemContent::FailedToParseState { .. }
525 | TimelineItemContent::CallInvite
526 | TimelineItemContent::RtcNotification => None,
527 };
528
529 if let Some(body) = body {
530 let graphemes = body.trim().graphemes(true).collect::<Vec<&str>>();
532
533 if graphemes.len() > 5 {
538 return false;
539 }
540
541 graphemes.iter().all(|g| emojis::get(g).is_some())
542 } else {
543 false
544 }
545 }
546}
547
548impl From<LocalEventTimelineItem> for EventTimelineItemKind {
549 fn from(value: LocalEventTimelineItem) -> Self {
550 EventTimelineItemKind::Local(value)
551 }
552}
553
554impl From<RemoteEventTimelineItem> for EventTimelineItemKind {
555 fn from(value: RemoteEventTimelineItem) -> Self {
556 EventTimelineItemKind::Remote(value)
557 }
558}
559
560#[derive(Clone, Debug, Default, PartialEq, Eq)]
562pub struct Profile {
563 pub display_name: Option<String>,
565
566 pub display_name_ambiguous: bool,
572
573 pub avatar_url: Option<OwnedMxcUri>,
575}
576
577#[derive(Clone, Debug)]
581pub enum TimelineDetails<T> {
582 Unavailable,
585
586 Pending,
588
589 Ready(T),
591
592 Error(Arc<Error>),
594}
595
596impl<T> TimelineDetails<T> {
597 pub(crate) fn from_initial_value(value: Option<T>) -> Self {
598 match value {
599 Some(v) => Self::Ready(v),
600 None => Self::Unavailable,
601 }
602 }
603
604 pub fn is_unavailable(&self) -> bool {
605 matches!(self, Self::Unavailable)
606 }
607
608 pub fn is_ready(&self) -> bool {
609 matches!(self, Self::Ready(_))
610 }
611}
612
613#[derive(Clone, Copy, Debug)]
615#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
616pub enum EventItemOrigin {
617 Local,
619 Sync,
621 Pagination,
623 Cache,
625}
626
627#[derive(Clone, Debug)]
629pub enum ReactionStatus {
630 LocalToLocal(Option<SendReactionHandle>),
634 LocalToRemote(Option<SendHandle>),
638 RemoteToRemote(OwnedEventId),
642}
643
644#[derive(Clone, Debug)]
646pub struct ReactionInfo {
647 pub timestamp: MilliSecondsSinceUnixEpoch,
648 pub status: ReactionStatus,
650}
651
652#[derive(Debug, Clone, Default)]
657pub struct ReactionsByKeyBySender(IndexMap<String, IndexMap<OwnedUserId, ReactionInfo>>);
658
659impl Deref for ReactionsByKeyBySender {
660 type Target = IndexMap<String, IndexMap<OwnedUserId, ReactionInfo>>;
661
662 fn deref(&self) -> &Self::Target {
663 &self.0
664 }
665}
666
667impl DerefMut for ReactionsByKeyBySender {
668 fn deref_mut(&mut self) -> &mut Self::Target {
669 &mut self.0
670 }
671}
672
673impl ReactionsByKeyBySender {
674 pub(crate) fn remove_reaction(
680 &mut self,
681 sender: &UserId,
682 annotation: &str,
683 ) -> Option<ReactionInfo> {
684 if let Some(by_user) = self.0.get_mut(annotation)
685 && let Some(info) = by_user.swap_remove(sender)
686 {
687 if by_user.is_empty() {
689 self.0.swap_remove(annotation);
690 }
691 return Some(info);
692 }
693 None
694 }
695}