diff --git a/src/base.rs b/src/base.rs index bb4f491d..87037431 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}, @@ -842,14 +843,17 @@ 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 { 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, } } } @@ -1057,6 +1061,12 @@ impl RoomInfo { 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); + } + }, } } @@ -1082,6 +1092,50 @@ impl RoomInfo { } } + /// Insert a sticker + pub fn insert_sticker( + &mut self, + room_id: OwnedRoomId, + store: AsyncProgramStore, + picker: Option, + sticker: StickerEvent, + settings: &ApplicationSettings, + media: matrix_sdk::Media, + ) { + match sticker { + MessageLikeEvent::Original(ref sticker_content) => { + 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.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(ref redaction) => { + let key = (redaction.origin_server_ts.into(), redaction.event_id.clone()); + self.messages.insert_message(key.clone(), sticker.clone()); + }, + } + } + /// Insert an edit. pub fn insert_edit(&mut self, msg: Replacement) { let event_id = msg.event_id; @@ -1112,6 +1166,7 @@ impl RoomInfo { }, MessageEvent::Redacted(_) | MessageEvent::State(_) | + MessageEvent::Sticker(_) | MessageEvent::EncryptedOriginal(_) | MessageEvent::EncryptedRedacted(_) => { return; diff --git a/src/message/mod.rs b/src/message/mod.rs index c3d144b4..9f30a8a0 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -11,6 +11,9 @@ 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::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; @@ -445,6 +448,7 @@ pub enum MessageEvent { Original(Box), Redacted(Box), State(Box), + Sticker(Box), Local(OwnedEventId, Box), } @@ -456,6 +460,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 +472,7 @@ impl MessageEvent { MessageEvent::EncryptedRedacted(_) => None, MessageEvent::Redacted(_) => None, MessageEvent::State(_) => None, + MessageEvent::Sticker(_) => None, MessageEvent::Local(_, content) => Some(content), } } @@ -484,6 +490,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 +503,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, }; @@ -517,6 +525,23 @@ impl MessageEvent { MessageEvent::Redacted(_) => return, MessageEvent::State(_) => 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), @@ -579,6 +604,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 +903,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 +925,7 @@ impl Message { MessageEvent::Original(ev) => &ev.content, MessageEvent::Redacted(_) => return None, MessageEvent::State(_) => return None, + MessageEvent::Sticker(_) => return None, }; match &content.relates_to { @@ -1188,6 +1224,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()) 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..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; @@ -358,6 +359,16 @@ fn load_insert( AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Reaction(ev)) => { info.insert_reaction(ev); }, + AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Sticker(ev)) => { + info.insert_sticker( + room_id.clone(), + store.clone(), + picker.clone(), + ev, + settings, + client.media(), + ); + }, AnyTimelineEvent::MessageLike(_) => { continue; }, @@ -1037,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,