diff --git a/README.md b/README.md index ceec578..d2e1f4a 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ two other TUI clients and Element Web: | Send formatted messages (markdown) | :x: ([#10]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Rich Text Editor for formatted messages | :x: | :x: | :x: | :heavy_check_mark: | | Display formatted messages | :x: ([#10]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Redacting | :x: ([#5]) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Redacting | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Multiple Matrix Accounts | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: | | New user registration | :x: | :x: | :x: | :heavy_check_mark: | | VOIP | :x: | :x: | :x: | :heavy_check_mark: | diff --git a/src/base.rs b/src/base.rs index 8435f02..be74d1b 100644 --- a/src/base.rs +++ b/src/base.rs @@ -63,6 +63,7 @@ pub enum VerifyAction { pub enum MessageAction { Cancel, Download(Option, bool), + Redact(Option), Reply, } diff --git a/src/commands.rs b/src/commands.rs index 988d49e..9ea350d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -144,6 +144,19 @@ fn iamb_cancel(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { return Ok(step); } +fn iamb_redact(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { + let args = desc.arg.strings()?; + + if args.len() > 1 { + return Result::Err(CommandError::InvalidArgument); + } + + let ract = IambAction::from(MessageAction::Redact(args.into_iter().next())); + let step = CommandStep::Continue(ract.into(), ctx.context.take()); + + return Ok(step); +} + fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { if !desc.arg.text.is_empty() { return Result::Err(CommandError::InvalidArgument); @@ -259,6 +272,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) { cmds.add_command(ProgramCommand { names: vec!["invite".into()], f: iamb_invite }); cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join }); cmds.add_command(ProgramCommand { names: vec!["members".into()], f: iamb_members }); + cmds.add_command(ProgramCommand { names: vec!["redact".into()], f: iamb_redact }); cmds.add_command(ProgramCommand { names: vec!["reply".into()], f: iamb_reply }); cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms }); cmds.add_command(ProgramCommand { names: vec!["set".into()], f: iamb_set }); @@ -429,4 +443,25 @@ mod tests { let res = cmds.input_cmd("invite @user:example.com", ctx.clone()); assert_eq!(res, Err(CommandError::InvalidArgument)); } + + #[test] + fn test_cmd_redact() { + let mut cmds = setup_commands(); + let ctx = ProgramContext::default(); + + let res = cmds.input_cmd("redact", ctx.clone()).unwrap(); + let act = IambAction::Message(MessageAction::Redact(None)); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("redact Removed", ctx.clone()).unwrap(); + let act = IambAction::Message(MessageAction::Redact(Some("Removed".into()))); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("redact \"Removed\"", ctx.clone()).unwrap(); + let act = IambAction::Message(MessageAction::Redact(Some("Removed".into()))); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let res = cmds.input_cmd("redact Removed Removed", ctx.clone()); + assert_eq!(res, Err(CommandError::InvalidArgument)); + } } diff --git a/src/message.rs b/src/message.rs index 240aa10..3fd649a 100644 --- a/src/message.rs +++ b/src/message.rs @@ -11,16 +11,23 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use matrix_sdk::ruma::{ - events::room::message::{ - MessageType, - OriginalRoomMessageEvent, - RedactedRoomMessageEvent, - RoomMessageEvent, - RoomMessageEventContent, + events::{ + room::{ + message::{ + MessageType, + OriginalRoomMessageEvent, + RedactedRoomMessageEvent, + RoomMessageEvent, + RoomMessageEventContent, + }, + redaction::SyncRoomRedactionEvent, + }, + Redact, }, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, + RoomVersionId, UInt, }; @@ -323,10 +330,34 @@ impl MessageEvent { pub fn show(&self) -> Cow<'_, str> { match self { MessageEvent::Original(ev) => show_room_content(&ev.content), - MessageEvent::Redacted(_) => Cow::Borrowed("[redacted]"), + MessageEvent::Redacted(ev) => { + let reason = ev + .unsigned + .redacted_because + .as_ref() + .and_then(|e| e.as_original()) + .and_then(|r| r.content.reason.as_ref()); + + if let Some(r) = reason { + Cow::Owned(format!("[Redacted: {:?}]", r)) + } else { + Cow::Borrowed("[Redacted]") + } + }, MessageEvent::Local(content) => show_room_content(content), } } + + pub fn redact(&mut self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) { + match self { + MessageEvent::Redacted(_) => return, + MessageEvent::Local(_) => return, + MessageEvent::Original(ev) => { + let redacted = ev.clone().redact(redaction, version); + *self = MessageEvent::Redacted(Box::new(redacted)); + }, + } + } } fn show_room_content(content: &RoomMessageEventContent) -> Cow<'_, str> { diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 7f131c6..705bf07 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -224,6 +224,33 @@ impl ChatState { Err(IambError::NoAttachment.into()) }, + MessageAction::Redact(reason) => { + let room = store + .application + .worker + .client + .get_joined_room(self.id()) + .ok_or(IambError::NotJoined)?; + + let event_id = match &msg.event { + MessageEvent::Original(ev) => ev.event_id.clone(), + MessageEvent::Local(_) => { + self.scrollback.get_key(info).ok_or(IambError::NoSelectedMessage)?.1 + }, + MessageEvent::Redacted(_) => { + let msg = ""; + let err = UIError::Failure(msg.into()); + + return Err(err); + }, + }; + + let event_id = event_id.as_ref(); + let reason = reason.as_deref(); + let _ = room.redact(event_id, reason, None).await.map_err(IambError::from)?; + + Ok(None) + }, MessageAction::Reply => { self.reply_to = self.scrollback.get_key(info); self.focus = RoomFocus::MessageBar; diff --git a/src/worker.rs b/src/worker.rs index c3963a6..d07c622 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -35,6 +35,7 @@ use matrix_sdk::{ room::{ message::{MessageType, RoomMessageEventContent}, name::RoomNameEventContent, + redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent}, topic::RoomTopicEventContent, }, typing::SyncTypingEvent, @@ -46,6 +47,7 @@ use matrix_sdk::{ OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, + RoomVersionId, }, Client, DisplayName, @@ -547,6 +549,34 @@ impl ClientWorker { }, ); + let _ = self.client.add_event_handler( + |ev: OriginalSyncRoomRedactionEvent, + room: MatrixRoom, + store: Ctx| { + async move { + let room_id = room.room_id(); + let room_info = room.clone_info(); + let room_version = room_info.room_version().unwrap_or(&RoomVersionId::V1); + + let mut locked = store.lock().await; + let info = locked.application.get_room_info(room_id.to_owned()); + + // XXX: need to store a mapping of EventId -> MessageKey somewhere + // to avoid having to iterate over the messages here. + for ((_, id), msg) in info.messages.iter_mut().rev() { + if id != &ev.redacts { + continue; + } + + let ev = SyncRoomRedactionEvent::Original(ev); + msg.event.redact(ev, room_version); + + break; + } + } + }, + ); + let _ = self.client.add_event_handler( |ev: OriginalSyncKeyVerificationStartEvent, client: Client,