From 373311e826a06799c03a13a7520a766ce814afd7 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sat, 23 May 2020 22:49:15 +0100 Subject: [PATCH] HAXXX: A crude form of setup message acceptance --- README.md | 22 +++++++++- src/padfoot/channel.rs | 30 ++++++++++++++ src/padfoot/channel/messages.rs | 6 +++ src/padfoot/connection.rs | 71 ++++++++++++++++++--------------- src/padfoot/message.rs | 69 +++++++++++++++++++++----------- 5 files changed, 140 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 0a4feac..d296d77 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Here's where we're at right now: - [~] Contacts handling - [x] Text messages - [ ] Multimedia messages -- [ ] Setup messages +- [~] Setup messages - [ ] Import/Export - [ ] Group chats - [ ] Geolocation messages @@ -167,6 +167,26 @@ development. Just run the `telepathy-padfoot` binary as the same user that your chat client will be running as. It registers to the DBUS **session bus**, and will be picked up next time your chat client scans (which may need a restart). +### Setup messages + +If you send an autocrypt setup message while a padfoot connection is up, it will +notice it and open a channel asking you to reply with a message like: + +``` +IMEX: nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn +``` + +The ID is the delta-generated message ID, while the rest is the setup code. No +whitespace! + +This bit is still extremely janky; it should instead be a magic channel type or +action button of some kind. It is, however, functional. + +Delta wants us to enable the "Send copy to self" option in settings, but that +isn't exposed in padfoot yet, so messages you send from padfoot won't appear in +other clients. Messages from other clients won't appear either, because messages +from yourself are mostly ingnored by padfoot. Still, it's progress. + ### Autogenerated telepathy DBUS bindings It makes use of the `dbus-codegen-rust` crate to convert the diff --git a/src/padfoot/channel.rs b/src/padfoot/channel.rs index d312917..95e5fb7 100644 --- a/src/padfoot/channel.rs +++ b/src/padfoot/channel.rs @@ -162,4 +162,34 @@ impl Channel { .add(messages_iface) .add(type_text_iface) } + + fn try_process_setupmsg(self: &Self, text: String) { + if !text.starts_with("IMEX: ") { + return; + }; + + // Expected form: "IMEX: + let mut iter = text.split_whitespace(); + iter.next(); // Ignore the prefix + + let msg_id = match iter.next() { + Some(txt) => match txt.parse::() { + Ok(id) => id, + _ => return, + }, + _ => return, + }; + + let setup_code = match iter.next() { + Some(txt) => txt, + _ => return, + }; + + let ctx = self.ctx.read().unwrap(); + if let Err(e) = + dc::imex::continue_key_transfer(&ctx, dc::message::MsgId::new(msg_id), &setup_code) + { + println!("Failed to apply setup code {}: {}", msg_id, e); + } + } } diff --git a/src/padfoot/channel/messages.rs b/src/padfoot/channel/messages.rs index 76c22b0..6256b78 100644 --- a/src/padfoot/channel/messages.rs +++ b/src/padfoot/channel/messages.rs @@ -34,10 +34,16 @@ impl telepathy::ChannelInterfaceMessages for Channel { } let text_opt = content["content"].0.as_str().map(|s| s.to_string()); + let mut delta_msg = Message::new(Viewtype::Text); // FIXME: this won't always be plaintext delta_msg.set_text(text_opt.clone()); + if let Some(text) = text_opt.clone() { + self.try_process_setupmsg(text); + }; + let ctx = self.ctx.read().unwrap(); + let msg_id = match dc::chat::send_msg(&ctx, self.chat_id, &mut delta_msg) { Ok(msg_id) => msg_id, Err(e) => { diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index 39986c7..72b7440 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -377,43 +377,15 @@ impl Connection { let chan_path = Connection::build_channel_path(path.clone(), chat_id); let c2 = Arc::clone(&chans); let chans = c2.read().unwrap(); + let u_ctx = ctx.clone(); + let ctx = u_ctx.read().unwrap(); // Autocreate channel if it doesn't already exist // FIXME: unknown contacts need more care than this - if chans.contains_key(&chan_path) { - print!("Message {} received for {}...", msg_id, chan_path); - - // Emit new message signals for the channel - if let Ok(msg) = dc::message::Message::load_from_db( - &ctx.clone().read().unwrap(), - msg_id, - ) { - // Ignore messages that are self-originated. - // FIXME: special-case self-chats - if msg.get_from_id() == dc::constants::DC_CONTACT_ID_SELF { - println!("from ourselves, skipping"); - continue; - } - - let parts = convert_msg(&msg); - - let sig = telepathy::ChannelInterfaceMessagesMessageReceived { - message: parts, - } - .to_emit_message(&chan_path); - - actq.send(DbusAction::Signal(sig)).unwrap(); - - println!("OK!"); - } else { - println!(" couldn't find message, not sending signal"); - } - // FIXME: We MUST also send a Text.Received signal - } else { + if !chans.contains_key(&chan_path) { print!("Channel for {} doesn't exist yet, creating it...", chat_id); - let contacts = - dc::chat::get_chat_contacts(&ctx.clone().read().unwrap(), chat_id); + let contacts = dc::chat::get_chat_contacts(&ctx, chat_id); if contacts.len() > 1 { println!("...{} contacts in chat, ignoring!", contacts.len()); continue; @@ -426,7 +398,7 @@ impl Connection { let chan = Channel::new( actq.clone(), chat_id, - ctx.clone(), + u_ctx.clone(), *handle, // initiator is the remote contact chan_path, false, // FIXME: this needs to handle requested channels @@ -436,7 +408,40 @@ impl Connection { actq.send(act).unwrap(); println!("OK"); + continue; } + + // Since the channel exists, emit new message signals + print!("Message {} received for {}...", msg_id, chan_path); + + let msg = match dc::message::Message::load_from_db(&ctx, msg_id) { + Ok(m) => m, + Err(e) => { + println!("Can't load from database, skipping: {}", e); + continue; + } + }; + + // Ignore messages that are self-originated. + // FIXME: special-case self-chats + if msg.get_from_id() == dc::constants::DC_CONTACT_ID_SELF + && !msg.is_setupmessage() + { + println!("from ourselves, skipping"); + continue; + } + + let parts = convert_msg(&msg); + + let sig = + telepathy::ChannelInterfaceMessagesMessageReceived { message: parts } + .to_emit_message(&chan_path); + + actq.send(DbusAction::Signal(sig)).unwrap(); + + // FIXME: We MUST also send a Text.Received signal + + println!("OK!"); } DbusAction::FreshMessages => { diff --git a/src/padfoot/message.rs b/src/padfoot/message.rs index f2aece5..9f0ff31 100644 --- a/src/padfoot/message.rs +++ b/src/padfoot/message.rs @@ -6,32 +6,53 @@ use std::collections::HashMap; // Turns a deltachat::message::Message into a Telepathy Message_Part_List pub fn convert_msg(msg: &Message) -> Vec> { - let mut parts = Vec::new(); - let mut props = HashMap::new(); - let msg_id = msg.get_id(); + if msg.is_setupmessage() { + return convert_setupmessage(msg); + } - props.insert( + let mut parts = vec![build_props(msg)]; + + if let Some(text) = msg.get_text() { + parts.push(build_plain(&text)); + } + + // TODO: other parts. Can a deltachat message have multiple parts? + + parts +} + +fn convert_setupmessage(msg: &Message) -> Vec> { + let msg_id = msg.get_id(); + vec![ + build_props(msg), + build_plain(&format!("Setup message received. To apply it, reply with:\nIMEX: {} nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn-nnnn\nNo whitespace in the setup-code!", msg_id.to_u32())), + ] +} + +fn build_props(msg: &Message) -> HashMap { + let msg_id = msg.get_id(); + let mut out = HashMap::new(); + + out.insert( "message-token".to_string(), var_str(format!("{}", msg_id.to_u32())), ); - props.insert("message-sent".to_string(), var_i64(msg.get_timestamp())); - props.insert( + out.insert("message-sent".to_string(), var_i64(msg.get_timestamp())); + out.insert( "message-received".to_string(), var_i64(msg.get_received_timestamp()), ); - props.insert("message-sender".to_string(), var_u32(msg.get_from_id())); + out.insert("message-sender".to_string(), var_u32(msg.get_from_id())); // props.insert("message-sender-id", var_str()); // This doesn't need to be sent // props.insert("sender-nickname", var_str()); // Can we get away without this one? - props.insert("message-type".to_string(), var_u32(0)); // normal + out.insert("message-type".to_string(), var_u32(0)); // normal // These relate to superseded messages // props.insert("supersedes", var_str()); // props.insert("original-message-sent", var_i64()); // props.insert("original-message-received", var_i64()); - props.insert("pending-message-id".to_string(), var_u32(msg_id.to_u32())); - - parts.push(props); + out.insert("pending-message-id".to_string(), var_u32(msg_id.to_u32())); // Don't need these // props.insert("interface", var_str()); @@ -39,17 +60,17 @@ pub fn convert_msg(msg: &Message) -> Vec> { // props.insert("silent", var_bool()); // props.insert("rescued", var_bool()); - if let Some(text) = msg.get_text() { - let mut part = HashMap::new(); - part.insert( - "content-type".to_string(), - var_str("text/plain".to_string()), - ); - part.insert("content".to_string(), var_str(text)); - parts.push(part); - } - - // TODO: other parts. Can a deltachat message have multiple parts? - - parts + out +} + +fn build_plain(text: &str) -> HashMap { + let mut out = HashMap::new(); + + out.insert( + "content-type".to_string(), + var_str("text/plain".to_string()), + ); + out.insert("content".to_string(), var_str(text.to_string())); + + out }