Support redacting messages (#5)

This commit is contained in:
Ulyssa 2023-01-13 17:53:54 -08:00
parent d13d4b9f7f
commit 56ec90523c
No known key found for this signature in database
GPG key ID: 1B3965A3D18B9B64
6 changed files with 132 additions and 8 deletions

View file

@ -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: | | 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: | | 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: | | 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: | | Multiple Matrix Accounts | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: |
| New user registration | :x: | :x: | :x: | :heavy_check_mark: | | New user registration | :x: | :x: | :x: | :heavy_check_mark: |
| VOIP | :x: | :x: | :x: | :heavy_check_mark: | | VOIP | :x: | :x: | :x: | :heavy_check_mark: |

View file

@ -63,6 +63,7 @@ pub enum VerifyAction {
pub enum MessageAction { pub enum MessageAction {
Cancel, Cancel,
Download(Option<String>, bool), Download(Option<String>, bool),
Redact(Option<String>),
Reply, Reply,
} }

View file

@ -144,6 +144,19 @@ fn iamb_cancel(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
return Ok(step); 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 { fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
if !desc.arg.text.is_empty() { if !desc.arg.text.is_empty() {
return Result::Err(CommandError::InvalidArgument); 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!["invite".into()], f: iamb_invite });
cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join }); 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!["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!["reply".into()], f: iamb_reply });
cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms }); cmds.add_command(ProgramCommand { names: vec!["rooms".into()], f: iamb_rooms });
cmds.add_command(ProgramCommand { names: vec!["set".into()], f: iamb_set }); 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()); let res = cmds.input_cmd("invite @user:example.com", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument)); 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));
}
} }

View file

@ -11,16 +11,23 @@ use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use matrix_sdk::ruma::{ use matrix_sdk::ruma::{
events::room::message::{ events::{
MessageType, room::{
OriginalRoomMessageEvent, message::{
RedactedRoomMessageEvent, MessageType,
RoomMessageEvent, OriginalRoomMessageEvent,
RoomMessageEventContent, RedactedRoomMessageEvent,
RoomMessageEvent,
RoomMessageEventContent,
},
redaction::SyncRoomRedactionEvent,
},
Redact,
}, },
MilliSecondsSinceUnixEpoch, MilliSecondsSinceUnixEpoch,
OwnedEventId, OwnedEventId,
OwnedUserId, OwnedUserId,
RoomVersionId,
UInt, UInt,
}; };
@ -323,10 +330,34 @@ impl MessageEvent {
pub fn show(&self) -> Cow<'_, str> { pub fn show(&self) -> Cow<'_, str> {
match self { match self {
MessageEvent::Original(ev) => show_room_content(&ev.content), 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), 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> { fn show_room_content(content: &RoomMessageEventContent) -> Cow<'_, str> {

View file

@ -224,6 +224,33 @@ impl ChatState {
Err(IambError::NoAttachment.into()) 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 => { MessageAction::Reply => {
self.reply_to = self.scrollback.get_key(info); self.reply_to = self.scrollback.get_key(info);
self.focus = RoomFocus::MessageBar; self.focus = RoomFocus::MessageBar;

View file

@ -35,6 +35,7 @@ use matrix_sdk::{
room::{ room::{
message::{MessageType, RoomMessageEventContent}, message::{MessageType, RoomMessageEventContent},
name::RoomNameEventContent, name::RoomNameEventContent,
redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
topic::RoomTopicEventContent, topic::RoomTopicEventContent,
}, },
typing::SyncTypingEvent, typing::SyncTypingEvent,
@ -46,6 +47,7 @@ use matrix_sdk::{
OwnedRoomId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedRoomOrAliasId,
OwnedUserId, OwnedUserId,
RoomVersionId,
}, },
Client, Client,
DisplayName, DisplayName,
@ -547,6 +549,34 @@ impl ClientWorker {
}, },
); );
let _ = self.client.add_event_handler(
|ev: OriginalSyncRoomRedactionEvent,
room: MatrixRoom,
store: Ctx<AsyncProgramStore>| {
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( let _ = self.client.add_event_handler(
|ev: OriginalSyncKeyVerificationStartEvent, |ev: OriginalSyncKeyVerificationStartEvent,
client: Client, client: Client,