From 8b79989faef1f69bf2a8250581fa49fe370541f7 Mon Sep 17 00:00:00 2001 From: kaizo Date: Fri, 5 Sep 2025 20:47:53 -0400 Subject: [PATCH 1/7] yeah --- src/base.rs | 113 ++++++++++++++++++++------------------- src/message/mod.rs | 73 ++++++++++++++----------- src/windows/room/chat.rs | 3 ++ src/worker.rs | 3 ++ 4 files changed, 105 insertions(+), 87 deletions(-) diff --git a/src/base.rs b/src/base.rs index bb4f491d..b7ed7dbc 100644 --- a/src/base.rs +++ b/src/base.rs @@ -13,6 +13,7 @@ use std::time::{Duration, Instant}; use emojis::Emoji; use matrix_sdk::ruma::events::receipt::ReceiptThread; +use matrix_sdk::ruma::events::sticker::StickerEvent; use ratatui::{ buffer::Buffer, layout::{Alignment, Rect}, @@ -21,12 +22,7 @@ use ratatui::{ }; use ratatui_image::picker::{Picker, ProtocolType}; use serde::{ - de::Error as SerdeError, - de::Visitor, - Deserialize, - Deserializer, - Serialize, - Serializer, + de::Error as SerdeError, de::Visitor, Deserialize, Deserializer, Serialize, Serializer, }; use tokio::sync::Mutex as AsyncMutex; use url::Url; @@ -40,25 +36,15 @@ use matrix_sdk::{ relation::{Replacement, Thread}, room::encrypted::RoomEncryptedEvent, room::message::{ - OriginalRoomMessageEvent, - Relation, - RoomMessageEvent, - RoomMessageEventContent, + OriginalRoomMessageEvent, Relation, RoomMessageEvent, RoomMessageEventContent, RoomMessageEventContentWithoutRelation, }, room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent}, tag::{TagName, Tags}, - AnySyncStateEvent, - MessageLikeEvent, + AnySyncStateEvent, MessageLikeEvent, }, presence::PresenceState, - EventId, - OwnedEventId, - OwnedRoomId, - OwnedUserId, - RoomId, - RoomVersionId, - UserId, + EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UserId, }, RoomState as MatrixRoomState, }; @@ -67,12 +53,8 @@ use modalkit::{ actions::Action, editing::{ application::{ - ApplicationAction, - ApplicationContentId, - ApplicationError, - ApplicationInfo, - ApplicationStore, - ApplicationWindowId, + ApplicationAction, ApplicationContentId, ApplicationError, ApplicationInfo, + ApplicationStore, ApplicationWindowId, }, completion::{complete_path, Completer, CompletionMap}, context::EditContext, @@ -109,10 +91,10 @@ pub const MATRIX_ID_WORD: WordStyle = WordStyle::CharSet(is_mxid_char); /// in the server name, but in practice that should be uncommon, and people /// can just use `gf` and friends in Visual mode instead. fn is_mxid_char(c: char) -> bool { - return c >= 'a' && c <= 'z' || - c >= 'A' && c <= 'Z' || - c >= '0' && c <= '9' || - ":-./@_#!".contains(c); + return c >= 'a' && c <= 'z' + || c >= 'A' && c <= 'Z' + || c >= '0' && c <= '9' + || ":-./@_#!".contains(c); } const ROOM_FETCH_DEBOUNCE: Duration = Duration::from_secs(2); @@ -842,6 +824,9 @@ pub enum EventLocation { /// The [EventId] belongs to a state event in the main timeline of the room. State(MessageKey), + + /// The [EventId] belongs to a sticker event in the main scrollback + Sticker(MessageKey), } impl EventLocation { @@ -1057,6 +1042,9 @@ impl RoomInfo { self.keys.remove(redacts); }, + Some(EventLocation::Sticker(event_id)) => { + self.keys.remove(redacts); + }, } } @@ -1082,6 +1070,25 @@ impl RoomInfo { } } + /// Insert a sticker + pub fn insert_sticker(&mut self, sticker: StickerEvent) { + match sticker { + MessageLikeEvent::Original(ref sticker_content) => { + //TODO + let key = (sticker_content.origin_server_ts.into(), sticker_content.event_id.clone()); + + let loc = EventLocation::Sticker(key.clone()); + + self.keys.insert(sticker_content.event_id.clone(), loc); + + self.messages.insert_message(key, sticker); + }, + MessageLikeEvent::Redacted(_) => { + return; + }, + } + } + /// Insert an edit. pub fn insert_edit(&mut self, msg: Replacement) { let event_id = msg.event_id; @@ -1110,10 +1117,11 @@ impl RoomInfo { MessageEvent::Local(_, content) => { content.apply_replacement(new_msgtype); }, - MessageEvent::Redacted(_) | - MessageEvent::State(_) | - MessageEvent::EncryptedOriginal(_) | - MessageEvent::EncryptedRedacted(_) => { + MessageEvent::Redacted(_) + | MessageEvent::State(_) + | MessageEvent::Sticker(_) + | MessageEvent::EncryptedOriginal(_) + | MessageEvent::EncryptedRedacted(_) => { return; }, } @@ -1189,16 +1197,14 @@ impl RoomInfo { RoomMessageEvent::Original(OriginalRoomMessageEvent { content: RoomMessageEventContent { relates_to: Some(ref relates_to), .. }, .. - }) => { - match relates_to { - Relation::Replacement(repl) => self.insert_edit(repl.clone()), - Relation::Thread(Thread { event_id, .. }) => { - let event_id = event_id.clone(); - self.insert_thread(msg, event_id); - }, - Relation::Reply { .. } => self.insert_message(msg), - _ => self.insert_message(msg), - } + }) => match relates_to { + Relation::Replacement(repl) => self.insert_edit(repl.clone()), + Relation::Thread(Thread { event_id, .. }) => { + let event_id = event_id.clone(); + self.insert_thread(msg, event_id); + }, + Relation::Reply { .. } => self.insert_message(msg), + _ => self.insert_message(msg), }, _ => self.insert_message(msg), } @@ -2121,10 +2127,7 @@ pub mod tests { use crate::tests::*; use matrix_sdk::ruma::{ events::{reaction::ReactionEventContent, relation::Annotation, MessageLikeUnsigned}, - owned_event_id, - owned_room_id, - owned_user_id, - MilliSecondsSinceUnixEpoch, + owned_event_id, owned_room_id, owned_user_id, MilliSecondsSinceUnixEpoch, }; use pretty_assertions::assert_eq; use ratatui::style::Color; @@ -2185,10 +2188,10 @@ pub mod tests { )); } - assert_eq!(info.get_reactions(&owned_event_id!("$my_reaction")), vec![ - ("🏠", 1), - ("🙂", 2) - ]); + assert_eq!( + info.get_reactions(&owned_event_id!("$my_reaction")), + vec![("🏠", 1), ("🙂", 2)] + ); } #[test] @@ -2277,10 +2280,10 @@ pub mod tests { need_load.insert(room_id.clone(), Need::MESSAGES); need_load.insert(room_id.clone(), Need::MEMBERS); - assert_eq!(need_load.into_iter().collect::>(), vec![( - room_id, - Need::MESSAGES | Need::MEMBERS, - )],); + assert_eq!( + need_load.into_iter().collect::>(), + vec![(room_id, Need::MESSAGES | Need::MEMBERS,)], + ); } #[tokio::test] diff --git a/src/message/mod.rs b/src/message/mod.rs index c3d144b4..8a7e77f3 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -11,6 +11,8 @@ use std::ops::{Deref, DerefMut}; use chrono::{DateTime, Local as LocalTz}; use humansize::{format_size, DECIMAL}; use matrix_sdk::ruma::events::receipt::ReceiptThread; +use matrix_sdk::ruma::events::sticker::StickerEvent; +use matrix_sdk::ruma::events::MessageLikeEvent; use serde_json::json; use unicode_width::UnicodeWidthStr; @@ -19,32 +21,17 @@ use matrix_sdk::ruma::{ relation::Thread, room::{ encrypted::{ - OriginalRoomEncryptedEvent, - RedactedRoomEncryptedEvent, - RoomEncryptedEvent, + OriginalRoomEncryptedEvent, RedactedRoomEncryptedEvent, RoomEncryptedEvent, }, message::{ - FormattedBody, - MessageFormat, - MessageType, - OriginalRoomMessageEvent, - RedactedRoomMessageEvent, - Relation, - RoomMessageEvent, - RoomMessageEventContent, + FormattedBody, MessageFormat, MessageType, OriginalRoomMessageEvent, + RedactedRoomMessageEvent, Relation, RoomMessageEvent, RoomMessageEventContent, }, redaction::SyncRoomRedactionEvent, }, - AnySyncStateEvent, - RedactContent, - RedactedUnsigned, + AnySyncStateEvent, RedactContent, RedactedUnsigned, }, - EventId, - MilliSecondsSinceUnixEpoch, - OwnedEventId, - OwnedUserId, - RoomVersionId, - UInt, + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomVersionId, UInt, }; use ratatui::{ @@ -445,6 +432,7 @@ pub enum MessageEvent { Original(Box), Redacted(Box), State(Box), + Sticker(Box), Local(OwnedEventId, Box), } @@ -456,6 +444,7 @@ impl MessageEvent { MessageEvent::Original(ev) => ev.event_id.as_ref(), MessageEvent::Redacted(ev) => ev.event_id.as_ref(), MessageEvent::State(ev) => ev.event_id(), + MessageEvent::Sticker(ev) => ev.event_id(), MessageEvent::Local(event_id, _) => event_id.as_ref(), } } @@ -467,6 +456,7 @@ impl MessageEvent { MessageEvent::EncryptedRedacted(_) => None, MessageEvent::Redacted(_) => None, MessageEvent::State(_) => None, + MessageEvent::Sticker(_) => None, MessageEvent::Local(_, content) => Some(content), } } @@ -484,6 +474,7 @@ impl MessageEvent { MessageEvent::Original(ev) => body_cow_content(&ev.content), MessageEvent::EncryptedRedacted(ev) => body_cow_reason(&ev.unsigned), MessageEvent::Redacted(ev) => body_cow_reason(&ev.unsigned), + MessageEvent::Sticker(ev) => body_cow_sticker(ev), MessageEvent::State(ev) => body_cow_state(ev), MessageEvent::Local(_, content) => body_cow_content(content), } @@ -496,6 +487,7 @@ impl MessageEvent { MessageEvent::Original(ev) => &ev.content, MessageEvent::Redacted(_) => return None, MessageEvent::State(ev) => return Some(html_state(ev)), + MessageEvent::Sticker(_) => return None, MessageEvent::Local(_, content) => content, }; @@ -516,6 +508,7 @@ impl MessageEvent { MessageEvent::EncryptedRedacted(_) => return, MessageEvent::Redacted(_) => return, MessageEvent::State(_) => return, + MessageEvent::Sticker(_) => return, MessageEvent::Local(_, _) => return, MessageEvent::Original(ev) => { let redacted = RedactedRoomMessageEvent { @@ -579,6 +572,15 @@ fn body_cow_content(content: &RoomMessageEventContent) -> Cow<'_, str> { Cow::Borrowed(s) } +fn body_cow_sticker(content: &StickerEvent) -> Cow<'_, str> { + match content { + MessageLikeEvent::Original(sticker) => { + Cow::Owned(format!("* sent a sticker: {}", sticker.content.body)) + }, + MessageLikeEvent::Redacted(_) => Cow::Borrowed("[Redacted]"), + } +} + fn body_cow_reason(unsigned: &RedactedUnsigned) -> Cow<'_, str> { let reason = unsigned.redacted_because.content.reason.as_ref(); @@ -869,6 +871,7 @@ impl Message { MessageEvent::Original(ev) => &ev.content, MessageEvent::Redacted(_) => return None, MessageEvent::State(_) => return None, + MessageEvent::Sticker(_) => return None, }; match &content.relates_to { @@ -890,6 +893,7 @@ impl Message { MessageEvent::Original(ev) => &ev.content, MessageEvent::Redacted(_) => return None, MessageEvent::State(_) => return None, + MessageEvent::Sticker(_) => return None, }; match &content.relates_to { @@ -936,8 +940,8 @@ impl Message { }; let user_gutter = settings.tunables.user_gutter_width; - if user_gutter + TIME_GUTTER + READ_GUTTER + MIN_MSG_LEN <= width && - settings.tunables.read_receipt_display + if user_gutter + TIME_GUTTER + READ_GUTTER + MIN_MSG_LEN <= width + && settings.tunables.read_receipt_display { let cols = MessageColumns::Four; let fill = width - user_gutter - TIME_GUTTER - READ_GUTTER; @@ -1106,9 +1110,9 @@ impl Message { settings: &'a ApplicationSettings, ) -> Option> { if let Some(prev) = prev { - if self.sender == prev.sender && - self.timestamp.same_day(&prev.timestamp) && - !self.event.is_emote() + if self.sender == prev.sender + && self.timestamp.same_day(&prev.timestamp) + && !self.event.is_emote() { return None; } @@ -1188,6 +1192,16 @@ impl From for Message { } } +impl From for Message { + fn from(event: StickerEvent) -> Self { + let timestamp = event.origin_server_ts().into(); + let user_id = event.sender().to_owned(); + let event = MessageEvent::Sticker(event.into()); + + Message::new(event, user_id, timestamp) + } +} + impl Display for Message { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.event.body()) @@ -1198,13 +1212,8 @@ impl Display for Message { pub mod tests { use matrix_sdk::ruma::events::room::{ message::{ - AudioInfo, - AudioMessageEventContent, - FileInfo, - FileMessageEventContent, - ImageMessageEventContent, - VideoInfo, - VideoMessageEventContent, + AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent, + ImageMessageEventContent, VideoInfo, VideoMessageEventContent, }, ImageInfo, }; diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index c92d4109..df416057 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -390,6 +390,7 @@ impl ChatState { MessageEvent::Original(ev) => ev.event_id.clone(), MessageEvent::Local(event_id, _) => event_id.clone(), MessageEvent::State(ev) => ev.event_id().to_owned(), + MessageEvent::Sticker(ev) => ev.event_id().to_owned(), MessageEvent::Redacted(_) => { let msg = "Cannot react to a redacted message"; let err = UIError::Failure(msg.into()); @@ -428,6 +429,7 @@ impl ChatState { MessageEvent::Original(ev) => ev.event_id.clone(), MessageEvent::Local(event_id, _) => event_id.clone(), MessageEvent::State(ev) => ev.event_id().to_owned(), + MessageEvent::Sticker(ev) => ev.event_id().to_owned(), MessageEvent::Redacted(_) => { let msg = "Cannot redact already redacted message"; let err = UIError::Failure(msg.into()); @@ -476,6 +478,7 @@ impl ChatState { MessageEvent::Original(ev) => ev.event_id.clone(), MessageEvent::Local(event_id, _) => event_id.clone(), MessageEvent::State(ev) => ev.event_id().to_owned(), + MessageEvent::Sticker(ev) => ev.event_id().to_owned(), MessageEvent::Redacted(_) => { let msg = "Cannot unreact to a redacted message"; let err = UIError::Failure(msg.into()); diff --git a/src/worker.rs b/src/worker.rs index 22a6fdbf..bd214a32 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -358,6 +358,9 @@ fn load_insert( AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Reaction(ev)) => { info.insert_reaction(ev); }, + AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Sticker(ev)) => { + info.insert_sticker(ev); + }, AnyTimelineEvent::MessageLike(_) => { continue; }, From dede30ddc647aed91c76a240916a9170dc57e5a4 Mon Sep 17 00:00:00 2001 From: kaizo Date: Fri, 5 Sep 2025 20:58:30 -0400 Subject: [PATCH 2/7] properly handle redactions --- src/base.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/base.rs b/src/base.rs index b7ed7dbc..ee26cb61 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1042,8 +1042,11 @@ impl RoomInfo { self.keys.remove(redacts); }, - Some(EventLocation::Sticker(event_id)) => { - self.keys.remove(redacts); + Some(EventLocation::Sticker(key)) => { + if let Some(msg) = self.messages.get_mut(key) { + let ev = SyncRoomRedactionEvent::Original(ev); + msg.redact(ev, room_version); + } }, } } @@ -1075,7 +1078,8 @@ impl RoomInfo { match sticker { MessageLikeEvent::Original(ref sticker_content) => { //TODO - let key = (sticker_content.origin_server_ts.into(), sticker_content.event_id.clone()); + let key = + (sticker_content.origin_server_ts.into(), sticker_content.event_id.clone()); let loc = EventLocation::Sticker(key.clone()); From eb2c8fe889971dea41010a9b180e8433079d05ce Mon Sep 17 00:00:00 2001 From: kaizo Date: Sat, 6 Sep 2025 00:31:17 -0400 Subject: [PATCH 3/7] add previews for stickers --- src/base.rs | 39 +++++++++++++++++++++++++++++++-------- src/worker.rs | 9 ++++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/base.rs b/src/base.rs index ee26cb61..9efd9bcc 100644 --- a/src/base.rs +++ b/src/base.rs @@ -831,10 +831,10 @@ pub enum EventLocation { impl EventLocation { fn to_message_key(&self) -> Option<&MessageKey> { - if let EventLocation::Message(_, key) = self { - Some(key) - } else { - None + match self { + EventLocation::Message(_, key) => Some(key), + EventLocation::Sticker(key) => Some(key), + _ => None, } } } @@ -1074,18 +1074,41 @@ impl RoomInfo { } /// Insert a sticker - pub fn insert_sticker(&mut self, sticker: StickerEvent) { + pub fn insert_sticker( + &mut self, + room_id: OwnedRoomId, + store: AsyncProgramStore, + picker: Option, + sticker: StickerEvent, + settings: &mut ApplicationSettings, + media: matrix_sdk::Media, + ) { match sticker { MessageLikeEvent::Original(ref sticker_content) => { - //TODO let key = (sticker_content.origin_server_ts.into(), sticker_content.event_id.clone()); let loc = EventLocation::Sticker(key.clone()); self.keys.insert(sticker_content.event_id.clone(), loc); - - self.messages.insert_message(key, sticker); + self.messages.insert_message(key.clone(), sticker.clone()); + + if picker.is_some() { + if let (Some(msg), Some(image_preview)) = ( + self.get_event_mut(&sticker_content.event_id), + &settings.tunables.image_preview, + ) { + msg.image_preview = ImageStatus::Downloading(image_preview.size.clone()); + spawn_insert_preview( + store, + room_id, + sticker_content.event_id.clone(), + sticker_content.content.source.clone().into(), + media, + settings.dirs.image_previews.clone(), + ) + } + } }, MessageLikeEvent::Redacted(_) => { return; diff --git a/src/worker.rs b/src/worker.rs index bd214a32..fb9c1604 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -359,7 +359,14 @@ fn load_insert( info.insert_reaction(ev); }, AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Sticker(ev)) => { - info.insert_sticker(ev); + info.insert_sticker( + room_id.clone(), + store.clone(), + picker.clone(), + ev, + settings, + client.media(), + ); }, AnyTimelineEvent::MessageLike(_) => { continue; From a19dff97d1d9a33ddfa7b873dac03b6f77e94a7a Mon Sep 17 00:00:00 2001 From: kaizo Date: Sat, 6 Sep 2025 13:05:04 -0400 Subject: [PATCH 4/7] properly use the expected formatting --- src/base.rs | 88 +++++++++++++++++++++++++++++----------------- src/message/mod.rs | 44 ++++++++++++++++------- 2 files changed, 88 insertions(+), 44 deletions(-) diff --git a/src/base.rs b/src/base.rs index 9efd9bcc..22f21219 100644 --- a/src/base.rs +++ b/src/base.rs @@ -22,7 +22,12 @@ use ratatui::{ }; use ratatui_image::picker::{Picker, ProtocolType}; use serde::{ - de::Error as SerdeError, de::Visitor, Deserialize, Deserializer, Serialize, Serializer, + de::Error as SerdeError, + de::Visitor, + Deserialize, + Deserializer, + Serialize, + Serializer, }; use tokio::sync::Mutex as AsyncMutex; use url::Url; @@ -36,15 +41,25 @@ use matrix_sdk::{ relation::{Replacement, Thread}, room::encrypted::RoomEncryptedEvent, room::message::{ - OriginalRoomMessageEvent, Relation, RoomMessageEvent, RoomMessageEventContent, + OriginalRoomMessageEvent, + Relation, + RoomMessageEvent, + RoomMessageEventContent, RoomMessageEventContentWithoutRelation, }, room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent}, tag::{TagName, Tags}, - AnySyncStateEvent, MessageLikeEvent, + AnySyncStateEvent, + MessageLikeEvent, }, presence::PresenceState, - EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UserId, + EventId, + OwnedEventId, + OwnedRoomId, + OwnedUserId, + RoomId, + RoomVersionId, + UserId, }, RoomState as MatrixRoomState, }; @@ -53,8 +68,12 @@ use modalkit::{ actions::Action, editing::{ application::{ - ApplicationAction, ApplicationContentId, ApplicationError, ApplicationInfo, - ApplicationStore, ApplicationWindowId, + ApplicationAction, + ApplicationContentId, + ApplicationError, + ApplicationInfo, + ApplicationStore, + ApplicationWindowId, }, completion::{complete_path, Completer, CompletionMap}, context::EditContext, @@ -91,10 +110,10 @@ pub const MATRIX_ID_WORD: WordStyle = WordStyle::CharSet(is_mxid_char); /// in the server name, but in practice that should be uncommon, and people /// can just use `gf` and friends in Visual mode instead. fn is_mxid_char(c: char) -> bool { - return c >= 'a' && c <= 'z' - || c >= 'A' && c <= 'Z' - || c >= '0' && c <= '9' - || ":-./@_#!".contains(c); + return c >= 'a' && c <= 'z' || + c >= 'A' && c <= 'Z' || + c >= '0' && c <= '9' || + ":-./@_#!".contains(c); } const ROOM_FETCH_DEBOUNCE: Duration = Duration::from_secs(2); @@ -1144,11 +1163,11 @@ impl RoomInfo { MessageEvent::Local(_, content) => { content.apply_replacement(new_msgtype); }, - MessageEvent::Redacted(_) - | MessageEvent::State(_) - | MessageEvent::Sticker(_) - | MessageEvent::EncryptedOriginal(_) - | MessageEvent::EncryptedRedacted(_) => { + MessageEvent::Redacted(_) | + MessageEvent::State(_) | + MessageEvent::Sticker(_) | + MessageEvent::EncryptedOriginal(_) | + MessageEvent::EncryptedRedacted(_) => { return; }, } @@ -1224,14 +1243,16 @@ impl RoomInfo { RoomMessageEvent::Original(OriginalRoomMessageEvent { content: RoomMessageEventContent { relates_to: Some(ref relates_to), .. }, .. - }) => match relates_to { - Relation::Replacement(repl) => self.insert_edit(repl.clone()), - Relation::Thread(Thread { event_id, .. }) => { - let event_id = event_id.clone(); - self.insert_thread(msg, event_id); - }, - Relation::Reply { .. } => self.insert_message(msg), - _ => self.insert_message(msg), + }) => { + match relates_to { + Relation::Replacement(repl) => self.insert_edit(repl.clone()), + Relation::Thread(Thread { event_id, .. }) => { + let event_id = event_id.clone(); + self.insert_thread(msg, event_id); + }, + Relation::Reply { .. } => self.insert_message(msg), + _ => self.insert_message(msg), + } }, _ => self.insert_message(msg), } @@ -2154,7 +2175,10 @@ pub mod tests { use crate::tests::*; use matrix_sdk::ruma::{ events::{reaction::ReactionEventContent, relation::Annotation, MessageLikeUnsigned}, - owned_event_id, owned_room_id, owned_user_id, MilliSecondsSinceUnixEpoch, + owned_event_id, + owned_room_id, + owned_user_id, + MilliSecondsSinceUnixEpoch, }; use pretty_assertions::assert_eq; use ratatui::style::Color; @@ -2215,10 +2239,10 @@ pub mod tests { )); } - assert_eq!( - info.get_reactions(&owned_event_id!("$my_reaction")), - vec![("🏠", 1), ("🙂", 2)] - ); + assert_eq!(info.get_reactions(&owned_event_id!("$my_reaction")), vec![ + ("🏠", 1), + ("🙂", 2) + ]); } #[test] @@ -2307,10 +2331,10 @@ pub mod tests { need_load.insert(room_id.clone(), Need::MESSAGES); need_load.insert(room_id.clone(), Need::MEMBERS); - assert_eq!( - need_load.into_iter().collect::>(), - vec![(room_id, Need::MESSAGES | Need::MEMBERS,)], - ); + assert_eq!(need_load.into_iter().collect::>(), vec![( + room_id, + Need::MESSAGES | Need::MEMBERS, + )],); } #[tokio::test] diff --git a/src/message/mod.rs b/src/message/mod.rs index 8a7e77f3..0b5cb3bb 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -21,17 +21,32 @@ use matrix_sdk::ruma::{ relation::Thread, room::{ encrypted::{ - OriginalRoomEncryptedEvent, RedactedRoomEncryptedEvent, RoomEncryptedEvent, + OriginalRoomEncryptedEvent, + RedactedRoomEncryptedEvent, + RoomEncryptedEvent, }, message::{ - FormattedBody, MessageFormat, MessageType, OriginalRoomMessageEvent, - RedactedRoomMessageEvent, Relation, RoomMessageEvent, RoomMessageEventContent, + FormattedBody, + MessageFormat, + MessageType, + OriginalRoomMessageEvent, + RedactedRoomMessageEvent, + Relation, + RoomMessageEvent, + RoomMessageEventContent, }, redaction::SyncRoomRedactionEvent, }, - AnySyncStateEvent, RedactContent, RedactedUnsigned, + AnySyncStateEvent, + RedactContent, + RedactedUnsigned, }, - EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomVersionId, UInt, + EventId, + MilliSecondsSinceUnixEpoch, + OwnedEventId, + OwnedUserId, + RoomVersionId, + UInt, }; use ratatui::{ @@ -940,8 +955,8 @@ impl Message { }; let user_gutter = settings.tunables.user_gutter_width; - if user_gutter + TIME_GUTTER + READ_GUTTER + MIN_MSG_LEN <= width - && settings.tunables.read_receipt_display + if user_gutter + TIME_GUTTER + READ_GUTTER + MIN_MSG_LEN <= width && + settings.tunables.read_receipt_display { let cols = MessageColumns::Four; let fill = width - user_gutter - TIME_GUTTER - READ_GUTTER; @@ -1110,9 +1125,9 @@ impl Message { settings: &'a ApplicationSettings, ) -> Option> { if let Some(prev) = prev { - if self.sender == prev.sender - && self.timestamp.same_day(&prev.timestamp) - && !self.event.is_emote() + if self.sender == prev.sender && + self.timestamp.same_day(&prev.timestamp) && + !self.event.is_emote() { return None; } @@ -1212,8 +1227,13 @@ impl Display for Message { pub mod tests { use matrix_sdk::ruma::events::room::{ message::{ - AudioInfo, AudioMessageEventContent, FileInfo, FileMessageEventContent, - ImageMessageEventContent, VideoInfo, VideoMessageEventContent, + AudioInfo, + AudioMessageEventContent, + FileInfo, + FileMessageEventContent, + ImageMessageEventContent, + VideoInfo, + VideoMessageEventContent, }, ImageInfo, }; From bc7ac7675142319a8cff6cc5a9b2793a820241aa Mon Sep 17 00:00:00 2001 From: kaizo Date: Tue, 9 Sep 2025 16:13:57 -0400 Subject: [PATCH 5/7] properly handle redactions --- src/base.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/base.rs b/src/base.rs index 22f21219..87037431 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1099,7 +1099,7 @@ impl RoomInfo { store: AsyncProgramStore, picker: Option, sticker: StickerEvent, - settings: &mut ApplicationSettings, + settings: &ApplicationSettings, media: matrix_sdk::Media, ) { match sticker { @@ -1129,8 +1129,9 @@ impl RoomInfo { } } }, - MessageLikeEvent::Redacted(_) => { - return; + MessageLikeEvent::Redacted(ref redaction) => { + let key = (redaction.origin_server_ts.into(), redaction.event_id.clone()); + self.messages.insert_message(key.clone(), sticker.clone()); }, } } From 25a581ae148d71d573975d7c405c459193380295 Mon Sep 17 00:00:00 2001 From: kaizo Date: Tue, 9 Sep 2025 16:26:52 -0400 Subject: [PATCH 6/7] add event handler for stickers --- src/worker.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/worker.rs b/src/worker.rs index fb9c1604..351c9ea4 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -13,6 +13,7 @@ use std::time::{Duration, Instant}; use futures::{stream::FuturesUnordered, StreamExt}; use gethostname::gethostname; +use matrix_sdk::ruma::events::sticker::StickerEventContent; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::Semaphore; use tokio::task::JoinHandle; @@ -1047,6 +1048,38 @@ impl ClientWorker { }, ); + let _ = self.client.add_event_handler( + |ev: SyncMessageLikeEvent, + room: MatrixRoom, + client: Client, + store: Ctx| { + async move { + let room_id = room.room_id(); + + let mut locked = store.lock().await; + + let sender = ev.sender().to_owned(); + let _ = locked.application.presences.get_or_default(sender); + + let ChatStore { rooms, picker, settings, .. } = &mut locked.application; + + let info = rooms.get_or_default(room_id.to_owned()); + + update_event_receipts(info, &room, ev.event_id()).await; + + let full_ev = ev.into_full_event(room_id.to_owned()); + info.insert_sticker( + room_id.to_owned(), + store.clone(), + picker.clone(), + full_ev, + settings, + client.media(), + ); + } + }, + ); + let _ = self.client.add_event_handler( |ev: SyncEphemeralRoomEvent, room: MatrixRoom, From f15f730a201555008f5fb7e7c4363de30c01ed1f Mon Sep 17 00:00:00 2001 From: kaizo Date: Tue, 9 Sep 2025 16:47:32 -0400 Subject: [PATCH 7/7] make listener on sticker redactions correct --- src/message/mod.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/message/mod.rs b/src/message/mod.rs index 0b5cb3bb..9f30a8a0 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -11,7 +11,8 @@ use std::ops::{Deref, DerefMut}; use chrono::{DateTime, Local as LocalTz}; use humansize::{format_size, DECIMAL}; use matrix_sdk::ruma::events::receipt::ReceiptThread; -use matrix_sdk::ruma::events::sticker::StickerEvent; +use matrix_sdk::ruma::events::room::message::RoomMessageEventContentWithoutRelation; +use matrix_sdk::ruma::events::sticker::{RedactedStickerEvent, StickerEvent}; use matrix_sdk::ruma::events::MessageLikeEvent; use serde_json::json; use unicode_width::UnicodeWidthStr; @@ -523,8 +524,24 @@ impl MessageEvent { MessageEvent::EncryptedRedacted(_) => return, MessageEvent::Redacted(_) => return, MessageEvent::State(_) => return, - MessageEvent::Sticker(_) => return, MessageEvent::Local(_, _) => return, + MessageEvent::Sticker(ev) => { + match ev.as_ref() { + MessageLikeEvent::Original(sticker) => { + let redacted = RedactedStickerEvent { + content: sticker.content.clone().redact(version), + event_id: ev.event_id().to_owned(), + sender: ev.sender().to_owned(), + origin_server_ts: ev.origin_server_ts(), + room_id: ev.room_id().to_owned(), + unsigned: redaction_unsigned(redaction), + }; + *self = + MessageEvent::Sticker(Box::new(MessageLikeEvent::Redacted(redacted))); + }, + MessageLikeEvent::Redacted(_) => {}, + } + }, MessageEvent::Original(ev) => { let redacted = RedactedRoomMessageEvent { content: ev.content.clone().redact(version),