From 52b799ef7c14d35f69de1062c5cb91ab40a1f061 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 21 Jul 2022 01:04:21 -0700 Subject: [PATCH] improvements for use with imp; migrate echobot to --- Cargo.lock | 12 +++ Cargo.toml | 1 + examples/echobot.rs | 37 +++----- src/bindings_go.rs | 59 +++++++------ src/event.rs | 210 ++++++++++++++++++++++++-------------------- src/lib.rs | 52 +++++------ src/structs.rs | 86 +++++++++++++----- 7 files changed, 262 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e7149e..6b100ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,6 +149,7 @@ dependencies = [ "libc", "serde", "serde_json", + "serde_repr", "serde_with", "sha2", ] @@ -233,6 +234,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_with" version = "1.11.0" diff --git a/Cargo.toml b/Cargo.toml index c63047d..2962911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ libc = "0.2" serde_json = "1.0" serde = { version = "1.0.127", features = ["derive"] } serde_with = { version = "1.10.0" } +serde_repr = "0.1" chrono = "0.4.19" diff --git a/examples/echobot.rs b/examples/echobot.rs index 93f8b40..92770b3 100644 --- a/examples/echobot.rs +++ b/examples/echobot.rs @@ -3,6 +3,7 @@ use std::thread; use libcwtch; use libcwtch::structs::*; use libcwtch::CwtchLib; +use libcwtch::event::Event; fn main() { let bot_home = "example_cwtch_dir"; @@ -19,46 +20,30 @@ fn main() { let event_loop_handle = thread::spawn(move || { loop { - let event_str = cwtch.get_appbus_event(); - println!("event: {}", event_str); + let event = cwtch.get_appbus_event(); + println!("event: {:?}", event); - let event: CwtchEvent = - serde_json::from_str(&event_str).expect("Error parsing Cwtch event"); - match event.event_type.as_str() { - "CwtchStarted" => { + match event { + Event::CwtchStarted => { println!("event CwtchStarted!"); println!("Creating bot"); cwtch.create_profile("Echobot", "be gay do crime"); } - "NewPeer" => { + Event::NewPeer { profile_id, tag, created, name, default_picture, picture, online, profile_data } => { println!( "\n***** {} at {} *****\n", - event.data["name"], event.data["Identity"] + name, profile_id.as_str() ); // process json for profile, contacts and servers...else { - let profile = match Profile::new( - &event.data["Identity"], - &event.data["name"], - &event.data["picture"], - &event.data["ContactsJson"], - &event.data["ServerList"], - ) { - Ok(p) => p, - Err(e) => panic!("error parsing profile: {}", e), - }; + let profile = profile_data; print!("profile: {:?}", profile); } - "NewMessageFromPeer" => { - let to = &event.data["ProfileOnion"]; - let conversation_id = event.data["ConversationID"].parse::().unwrap(); - let message: Message = - serde_json::from_str(&event.data["Data"]).expect("Error parsing message"); - - let response = Message { o: 1, d: message.d }; + Event::NewMessageFromPeer { profile_id, conversation_id, contact_id: contact, nick, timestamp_received, message, notification, picture } => { + let response = Message { o: message.o.into(), d: message.d }; let response_json = serde_json::to_string(&response).expect("Error parsing json response"); - cwtch.send_message(&to, conversation_id, &response_json); + cwtch.send_message( &profile_id, conversation_id, &response_json); } _ => eprintln!("unhandled event!"), }; diff --git a/src/bindings_go.rs b/src/bindings_go.rs index 1acb34c..10e5141 100644 --- a/src/bindings_go.rs +++ b/src/bindings_go.rs @@ -122,48 +122,49 @@ impl CwtchLib for CwtchLibGo { c_bind!(start_cwtch(app_dir: &str, tor_path: &str;;) c_StartCwtch -> i32); c_bind!(started(;;) c_Started -> i32); c_bind!(send_app_event(event_json: &str;;) c_SendAppEvent); - fn send_profile_event(&self, profile: ProfileIdentity, event_jason: &str) { - self._send_profile_event(String::from(profile).as_str(), event_jason) + fn send_profile_event(&self, profile: &ProfileIdentity, event_jason: &str) { + self._send_profile_event(profile.as_str(), event_jason) + } c_bind!(create_profile(nick: &str, pass: &str;;) c_CreateProfile); c_bind!(load_profiles(pass: &str;;) c_LoadProfiles); - fn accept_conversation(&self, profile: ProfileIdentity, conversation_id: ConversationID) { - self._accept_conversation(String::from(profile).as_str(), conversation_id.into()) + fn accept_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { + self._accept_conversation(profile.as_str(), conversation_id.into()) } - fn block_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID) { + fn block_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._block_contact(String::from(profile).as_str(), conversation_id.into()) } - fn unblock_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID) { + fn unblock_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._unblock_contact(String::from(profile).as_str(), conversation_id.into()) } - fn get_message(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String { + fn get_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String { self._get_message(String::from(profile).as_str(), conversation_id.into(), message_index) } - fn get_message_by_id(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String { + fn get_message_by_id(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String { self._get_message_by_id(String::from(profile).as_str(), conversation_id.into(), message_id) } - fn get_message_by_content_hash(&self, profile: ProfileIdentity, conversation_id: ConversationID, hash: &str) -> String { + fn get_message_by_content_hash(&self, profile: &ProfileIdentity, conversation_id: ConversationID, hash: &str) -> String { self._get_message_by_content_hash(String::from(profile).as_str(), conversation_id.into(), hash) } - fn get_messages(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String { + fn get_messages(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String { self._get_messages(String::from(profile).as_str(), conversation_id.into(), message_index, count) } - fn send_message(&self, profile: ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String { + fn send_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String { self._send_message(String::from(profile).as_str(), conversation_id.into(), msg) } - fn send_invitation(&self, profile: ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String { + fn send_invitation(&self, profile: &ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String { self._send_invitation(String::from(profile).as_str(), conversation_id.into(), target_id) } - fn share_file(&self, profile: ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String { + fn share_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String { self._share_file(String::from(profile).as_str(), conversation_id.into(), file_path) } - fn download_file(&self, profile: ProfileIdentity, conversation_id: ConversationID, file_path: &str, manifest_path: &str, file_key: FileKey) { + fn download_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str, manifest_path: &str, file_key: FileKey) { self._download_file(String::from(profile).as_str(), conversation_id.into(), file_path, manifest_path, String::from(file_key).as_str()) } - fn check_download_status(&self, profile: ProfileIdentity, file_key: FileKey) { + fn check_download_status(&self, profile: &ProfileIdentity, file_key: FileKey) { self._check_download_status(String::from(profile).as_str(), String::from(file_key).as_str()) } - fn verify_or_resume_download(&self, profile: ProfileIdentity, conversation_id: ConversationID, file_key: FileKey) { + fn verify_or_resume_download(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_key: FileKey) { self._verify_or_resume_download(String::from(profile).as_str(), conversation_id.into(), String::from(file_key).as_str()) } @@ -172,26 +173,26 @@ impl CwtchLib for CwtchLibGo { bindings::c_ResetTor(); } } - fn create_group(&self, profile: ProfileIdentity, server: &str, name: &str) { + fn create_group(&self, profile: &ProfileIdentity, server: &str, name: &str) { self._create_group(String::from(profile).as_str(), server, name) } - fn delete_profile(&self, profile: ProfileIdentity, pass: &str) { + fn delete_profile(&self, profile: &ProfileIdentity, pass: &str) { self._delete_profile(String::from(profile).as_str(), pass) } - fn archive_conversation(&self, profile: ProfileIdentity, conversation_id: ConversationID) { + fn archive_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { self._archive_conversation(String::from(profile).as_str(), conversation_id.into()) } - fn delete_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID) { - self._delete_contact(String::from(profile).as_str(), conversation_id.into()) + fn delete_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID) { + self._delete_contact(profile.as_str(), conversation_id.into()) } - fn import_bundle(&self, profile: ProfileIdentity, bundle: &str) { + fn import_bundle(&self, profile: &ProfileIdentity, bundle: &str) { self._import_bundle(String::from(profile).as_str(), bundle) } - fn set_profile_attribute(&self, profile: ProfileIdentity, key: &str, val: &str) { + fn set_profile_attribute(&self, profile: &ProfileIdentity, key: &str, val: &str) { self._set_profile_attribute(String::from(profile).as_str(), key, val) } - fn get_profile_attribute(&self, profile: ProfileIdentity, key: &str) -> Result, CwtchError> { + fn get_profile_attribute(&self, profile: &ProfileIdentity, key: &str) -> Result, CwtchError> { let resp = self._get_profile_attribute(String::from(profile).as_str(), key); let attr: Attribute = match serde_json::from_str(&resp) { Ok(attr) => attr, @@ -202,10 +203,10 @@ impl CwtchLib for CwtchLibGo { false => Ok(None), } } - fn set_conversation_attribute(&self, profile: ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str) { + fn set_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str) { self._set_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), key, val) } - fn get_conversation_attribute(&self, profile: ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result, CwtchError> { + fn get_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result, CwtchError> { let resp = self._get_conversation_attribute(String::from(profile).as_str(), conversation_id.into(), key); let attr: Attribute = match serde_json::from_str(&resp) { Ok(attr) => attr, @@ -216,13 +217,13 @@ impl CwtchLib for CwtchLibGo { false => Ok(None), } } - fn set_message_attribute(&self, profile: ProfileIdentity, conversation_id: ConversationID, channel_id: i32, message_id: i32, key: &str, val: &str) { + fn set_message_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, channel_id: i32, message_id: i32, key: &str, val: &str) { self._set_message_attribute(String::from(profile).as_str(), conversation_id.into(), channel_id, message_id, key, val) } - fn change_password(&self, profile: ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str) { + fn change_password(&self, profile: &ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str) { self._change_password(String::from(profile).as_str(), old_pass, new_pass, new_pass_again) } - fn export_profile(&self, profile: ProfileIdentity, filename: &str) { + fn export_profile(&self, profile: &ProfileIdentity, filename: &str) { self._export_profile(String::from(profile).as_str(), filename) } diff --git a/src/event.rs b/src/event.rs index 78ff705..6987b09 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,12 +1,14 @@ use std::collections::HashMap; +use serde::{Deserialize, Serialize}; use chrono::{DateTime, FixedOffset}; use chrono::prelude::*; use std::convert::From; -use crate::structs::{ACL, ConnectionState, CwtchEvent, Message, Profile}; +use crate::structs::{ACL, ConnectionState, CwtchEvent, Message, Profile, Settings}; -#[derive(Debug)] + +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Profile ID used to refer to profiles in Cwtch pub struct ProfileIdentity(String); @@ -28,7 +30,21 @@ impl From<&str> for ProfileIdentity { } } -#[derive(Debug)] +impl From<&ProfileIdentity> for String { + fn from(x: &ProfileIdentity) -> Self { + x.0.clone() + } +} + +impl ProfileIdentity { + /// Get &str of ProfileIdentity String + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + + +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Contact ID used to refer to contacts in Cwtch pub struct ContactIdentity(String); @@ -38,9 +54,16 @@ impl From for ContactIdentity { } } -#[derive(Debug)] +impl ContactIdentity { + /// Get &str of ContactIdentity String + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Conversation ID user to refer to a conversation with a Contact or Group in Cwtch -pub struct ConversationID(i32) ; +pub struct ConversationID(pub i32) ; impl From for ConversationID { fn from(x: i32) -> Self { @@ -54,7 +77,8 @@ impl From for i32 { } } -#[derive(Debug)] + +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Group ID used to refer to a Group in Cwtch pub struct GroupID(String) ; @@ -64,7 +88,8 @@ impl From for GroupID { } } -#[derive(Debug)] + +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Server ID user to refer to a server in Cwtch pub struct ServerIdentity(String); @@ -86,7 +111,7 @@ impl From for String { } } -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// FileKey ID user to refer to a file share in Cwtch pub struct FileKey(String); @@ -102,7 +127,7 @@ impl From for String { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Enum for type of notification a UI/client should emit for a new message pub enum MessageNotification { /// means no notification @@ -124,7 +149,7 @@ impl From for MessageNotification { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Enum for results from NetworkCheck plugin on profiles pub enum NetworkCheckStatus { /// There was an error, this profile cannot connect to itself @@ -143,7 +168,7 @@ impl From for NetworkCheckStatus { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Enum denoting server storage mode pub enum ServerStorageType { /// indicates some app supplied default password is being used on this server for storage encryption @@ -165,7 +190,7 @@ impl From for ServerStorageType { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Enum used by servers to declare their intent to be running or stopped pub enum ServerIntent { /// a server intends to be running @@ -184,7 +209,7 @@ impl From for ServerIntent { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Enum for events from Cwtch appbus pub enum Event { @@ -195,7 +220,7 @@ pub enum Event { /// A new peer has been loaded, details of peer. Identity should be stored so further API calls can be made NewPeer { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// optional client specified tag tag: String, /// is this a newly created profile event, or a load @@ -219,7 +244,7 @@ pub enum Event { /// Global settings being emited from lcg, usually in response to them being sent to be saved by client UpdateGlobalSettings { /// map of setting names to values (https://git.openprivacy.ca/cwtch.im/libcwtch-go/src/branch/trunk/utils/settings.go) - settings: HashMap, + settings: Settings, }, /// A profile has an error, usually emited in response to an API call PeerError { @@ -229,7 +254,7 @@ pub enum Event { /// A profile was successfully deleted, it is no longer usable PeerDeleted { /// identity of deleted peer - profile: ProfileIdentity + profile_id: ProfileIdentity }, /// Cwtch is shutting down, stop making API calls Shutdown, @@ -298,17 +323,17 @@ pub enum Event { /// A new message was received NewMessageFromPeer { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// name of contact nick: String, /// time message was received timestamp_received: DateTime, /// the message - message: Result, + message: Message, /// notification instructions (based on settings) notification: MessageNotification, /// path to picture for the contact @@ -317,11 +342,11 @@ pub enum Event { /// A new contact has been created (imported, added, or contacted by) ContactCreated { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// name of group nick: String, /// connection status to group server @@ -349,16 +374,16 @@ pub enum Event { /// A peer has changed state PeerStateChange { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// connection status to contact connection_state: ConnectionState, }, /// Notice from the network check plugin, a profile self check test by attempting to connecting to itself NetworkStatus { /// profile the check was performed on - profile: ProfileIdentity, + profile_id: ProfileIdentity, // it's technically a profile self check /// error if there was one (can be empty) error: String, @@ -368,9 +393,9 @@ pub enum Event { /// Information from the ACN about a peer ACNInfo { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// key of info key: String, /// data of info @@ -379,7 +404,7 @@ pub enum Event { /// a profile attribute has been updated with a new value UpdatedProfileAttribute { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// attribute key key: String, /// attribute new value @@ -388,7 +413,7 @@ pub enum Event { /// emited to confirm ack of a message succeeded IndexedAcknowledgement { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: i32, /// index of message acked @@ -397,41 +422,41 @@ pub enum Event { /// emited to signal failure to ack a message IndexedFailure { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// index of failure of message to ack index: i32, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// error string error: String, }, /// a peer has acked a message PeerAcknowledgement { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// message id this is an ack to event_id: String, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// conversation id conversation_id: i32, }, /// New message received on a group NewMessageFromGroup { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// time of message timestamp_sent: DateTime, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// message index index: i32, /// the message - message: Result, + message: Message, /// hash of the message content_hash: String, /// path to picture for sender @@ -444,7 +469,7 @@ pub enum Event { /// notice a group has been created GroupCreated { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// group id @@ -461,7 +486,7 @@ pub enum Event { /// notice a new group exists NewGroup { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id conversation_id: ConversationID, /// group id @@ -480,7 +505,7 @@ pub enum Event { /// a server connection state has changed ServerStateChange { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// server the group is on group_server: String, /// state of connection to server @@ -489,9 +514,9 @@ pub enum Event { /// A getval call to a peer has returned a response NewRetValMessageFromPeer { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// conversation id - contact: ContactIdentity, + contact_id: ContactIdentity, /// scope of the val scope: String, /// path of the val (zone.key) @@ -506,7 +531,7 @@ pub enum Event { /// result of a call to share a file, the filekey and manifest to operate on it ShareManifest { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// filekey filekey: FileKey, /// serialized manifest of the share @@ -515,29 +540,29 @@ pub enum Event { /// Information on a peer fileshare has been received ManifestSizeReceived { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// filekey filekey: FileKey, /// size of manifest received for a share manifest_size: i32, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, }, /// An error has occured while trying to parse a peer sharefile ManifestError { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// filekey filekey: FileKey, }, /// A peer message about a shared file has been received ManifestReceived { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// filekey filekey: FileKey, /// serialized manifest @@ -546,9 +571,9 @@ pub enum Event { /// a received manfiest has been saved ManifestSaved { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// contact id - contact: ContactIdentity, + contact_id: ContactIdentity, /// filekey filekey: FileKey, /// serialized manifest @@ -561,7 +586,7 @@ pub enum Event { /// periodically emited status updates about an active download of a shared file FileDownloadProgressUpdate { /// identity field - profile: ProfileIdentity, + profile_id: ProfileIdentity, /// filekey filekey: FileKey, /// progress of download of share in chunks @@ -585,10 +610,11 @@ pub enum Event { impl From<&CwtchEvent> for Event { fn from(cwtch_event: &CwtchEvent) -> Self { + println!("EVENT: {:?}", cwtch_event); match cwtch_event.event_type.as_str() { "CwtchStarted" => Event::CwtchStarted, "NewPeer" => Event::NewPeer { - profile: cwtch_event.data["Identity"].clone().into(), + profile_id: cwtch_event.data["Identity"].clone().into(), tag: cwtch_event.data["tag"].clone(), created: cwtch_event.data["Created"] == "true", name: cwtch_event.data["name"].clone(), @@ -596,7 +622,7 @@ impl From<&CwtchEvent> for Event { picture: cwtch_event.data["picture"].clone(), online: cwtch_event.data["Online"].clone(), profile_data: Profile::new( - &cwtch_event.data["Identity"], + cwtch_event.data["Identity"].clone().into(), &cwtch_event.data["name"], &cwtch_event.data["picture"], &cwtch_event.data["ContactsJson"], @@ -605,23 +631,23 @@ impl From<&CwtchEvent> for Event { }, "NewMessageFromPeer" => Event::NewMessageFromPeer { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(), - contact: cwtch_event.data["RemotePeer"].clone().into(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), nick: cwtch_event.data["Nick"].clone(), timestamp_received: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampReceived"].as_str()).unwrap_or( DateTime::from(Utc::now())), - message: serde_json::from_str(&cwtch_event.data["Data"]), + message: Message::from_json(&cwtch_event.data["Data"]), notification: MessageNotification::from(cwtch_event.data["notification"].clone()), - picture: cwtch_event.data["Picture"].clone(), + picture: cwtch_event.data["picture"].clone(), }, "PeerError" => Event::PeerError { error: cwtch_event.data["Error"].clone() }, "AppError" => Event::AppError { error: cwtch_event.data["Error"].clone(), }, "ContactCreated" => Event::ContactCreated { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), conversation_id: cwtch_event.data["ConversationID"].clone().parse().unwrap_or(-2).into(), - contact: cwtch_event.data["RemotePeer"].clone().into(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), unread: cwtch_event.data["unread"].clone().parse().unwrap_or(0), picture: cwtch_event.data["picture"].clone(), default_picture: cwtch_event.data["defaultPicture"].clone(), @@ -635,14 +661,14 @@ impl From<&CwtchEvent> for Event { last_msg_time: DateTime::parse_from_rfc3339(cwtch_event.data["lastMsgTime"].as_str()).unwrap_or(DateTime::from(Utc::now())), }, "PeerStateChange" => Event::PeerStateChange { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["RemotePeer"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), connection_state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()), }, "UpdateGlobalSettings" => Event::UpdateGlobalSettings { - settings: serde_json::from_str(cwtch_event.data["Data"].as_str()).unwrap_or(HashMap::new()), + settings: serde_json::from_str(cwtch_event.data["Data"].as_str()).expect("could not parse settings from libCwtch-go"), }, - "PeerDeleted" => Event::PeerDeleted { profile: cwtch_event.data["Identity"].clone().into() }, + "PeerDeleted" => Event::PeerDeleted { profile_id: cwtch_event.data["Identity"].clone().into() }, "ACNStatus" => Event::ACNStatus { progress: cwtch_event.data["Progress"].parse().unwrap_or(0), @@ -650,30 +676,28 @@ impl From<&CwtchEvent> for Event { }, "ACNVersion" => Event::ACNVersion { version: cwtch_event.data["Data"].clone()}, "NetworkError" => Event::NetworkStatus { - profile: cwtch_event.data["Onion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), error: cwtch_event.data["Error"].clone(), status: NetworkCheckStatus::from(cwtch_event.data["Status"].clone()) }, "ACNInfo" => Event::ACNInfo { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["Handle"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["Handle"].clone().into(), key: cwtch_event.data["Key"].clone(), data: cwtch_event.data["Data"].clone(), }, "UpdatedProfileAttribute" => Event::UpdatedProfileAttribute { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), key: cwtch_event.data["Key"].clone(), value: cwtch_event.data["Data"].clone(), }, - "IndexedAcknowledgement" => Event::IndexedFailure { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + "IndexedAcknowledgement" => Event::IndexedAcknowledgement { + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-1).into(), index: cwtch_event.data["Index"].parse().unwrap_or(-1), - contact: cwtch_event.data["Handle"].clone().into(), - error: cwtch_event.data["Error"].clone(), }, "ShareManifest" => Event::ShareManifest { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), serialized_manifest: cwtch_event.data["SerializedManifest"].clone(), }, @@ -703,57 +727,57 @@ impl From<&CwtchEvent> for Event { connections: cwtch_event.data["Connections"].parse().unwrap_or(0), }, "ManifestSizeReceived" => Event::ManifestSizeReceived { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), manifest_size: cwtch_event.data["ManifestSize"].parse().unwrap_or(0), - contact: cwtch_event.data["Handle"].clone().into(), + contact_id: cwtch_event.data["Handle"].clone().into(), }, "ManifestError" => Event::ManifestError { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["Handle"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["Handle"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), }, "ManifestReceived" => Event::ManifestReceived { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["Handle"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["Handle"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), serialized_manifest: cwtch_event.data["SerializedManifest"].clone(), }, "ManifestSaved" => Event::ManifestSaved { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["Handle"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["Handle"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), serialized_manifest: cwtch_event.data["SerializedManifest"].clone(), temp_file: cwtch_event.data["TempFile"].clone(), name_suggestion: cwtch_event.data["NameSuggestion"].clone(), }, "FileDownloadProgressUpdate" => Event::FileDownloadProgressUpdate { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), filekey: cwtch_event.data["FileKey"].clone().into(), progress: cwtch_event.data["Progress"].parse().unwrap_or(0), filesize_in_chunks: cwtch_event.data["FileSizeInChunks"].parse().unwrap_or(0), name_suggestion: cwtch_event.data["NameSuggestion"].clone(), }, "PeerAcknowledgement" => Event::PeerAcknowledgement { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - event_id: cwtch_event.data["EventId"].clone(), - contact: cwtch_event.data["RemotePeer"].clone().into(), - conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(0) + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + event_id: cwtch_event.data["EventID"].clone(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), + conversation_id: cwtch_event.data.get("ConversationID").unwrap_or(&"0".to_string()).parse().unwrap_or(0) }, "NewMessageFromGroup" => Event::NewMessageFromGroup { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), timestamp_sent: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampSent"].as_str()).unwrap_or( DateTime::from(Utc::now())), index: cwtch_event.data["RemotePeer"].parse().unwrap_or(-1), content_hash: cwtch_event.data["ContentHash"].clone(), conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(), - contact: cwtch_event.data["RemotePeer"].clone().into(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), nick: cwtch_event.data["Nick"].clone(), - message: serde_json::from_str(&cwtch_event.data["Data"]), + message: Message::from_json(&cwtch_event.data["Data"]), notification: MessageNotification::from(cwtch_event.data["notification"].clone()), - picture: cwtch_event.data["Picture"].clone(), + picture: cwtch_event.data["picture"].clone(), }, "GroupCreated" => Event::GroupCreated { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(), group_id: cwtch_event.data["GroupID"].clone().into(), group_server: cwtch_event.data["GroupServer"].clone(), @@ -762,7 +786,7 @@ impl From<&CwtchEvent> for Event { access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()), }, "NewGroup" => Event::NewGroup { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2).into(), group_id: cwtch_event.data["GroupID"].clone().into(), group_server: cwtch_event.data["GroupServer"].clone(), @@ -772,13 +796,13 @@ impl From<&CwtchEvent> for Event { access_control_list: serde_json::from_str(cwtch_event.data["accessControlList"].as_str()).unwrap_or(ACL::new()), }, "ServerStateChange" => Event::ServerStateChange { - profile: cwtch_event.data["ProfileOnion"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), group_server: cwtch_event.data["GroupServer"].clone(), state: ConnectionState::from(cwtch_event.data["ConnectionState"].as_str()), }, "NewRetValMessageFromPeer" => Event::NewRetValMessageFromPeer { - profile: cwtch_event.data["ProfileOnion"].clone().into(), - contact: cwtch_event.data["RemotePeer"].clone().into(), + profile_id: cwtch_event.data["ProfileOnion"].clone().into(), + contact_id: cwtch_event.data["RemotePeer"].clone().into(), scope: cwtch_event.data["Scope"].clone(), path: cwtch_event.data["Path"].clone(), exists: cwtch_event.data["Exists"].parse().unwrap_or(false), diff --git a/src/lib.rs b/src/lib.rs index 52dd6f7..ee92cc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub trait CwtchLib { fn send_app_event(&self, event_json: &str); /// Send json of a structs::CwtchEvent to a cwtch profile - fn send_profile_event(&self, profile: ProfileIdentity, event_json: &str); + fn send_profile_event(&self, profile: &ProfileIdentity, event_json: &str); /// Pull json of a structs::CwtchEvent off the appbus for responding to fn get_appbus_event(&self) -> Event; @@ -40,44 +40,44 @@ pub trait CwtchLib { fn load_profiles(&self, pass: &str); /// Cause profile to accept conversation - fn accept_conversation(&self, profile: ProfileIdentity, conversation_id: ConversationID); + fn accept_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID); /// Cause profile to block conversation - fn block_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID); + fn block_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID); /// Cause profile to unblock contact - fn unblock_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID); + fn unblock_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID); /// Get a specific message for conversation of profile by index - fn get_message(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String; + fn get_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32) -> String; /// Get a specific message for a conversation by its id - fn get_message_by_id(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String; + fn get_message_by_id(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_id: i32) -> String; /// Get a specific message for conversation of profile by hash fn get_message_by_content_hash( &self, - profile: ProfileIdentity, + profile: &ProfileIdentity, conversation_id: ConversationID, hash: &str, ) -> String; /// Bulk get messages starting at message index and of count amoung - fn get_messages(&self, profile: ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String; + fn get_messages(&self, profile: &ProfileIdentity, conversation_id: ConversationID, message_index: i32, count: i32) -> String; /// Send json of a structs::Message from profile to contact. Returns computed sent message (including index and hash values) - fn send_message(&self, profile: ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String; + fn send_message(&self, profile: &ProfileIdentity, conversation_id: ConversationID, msg: &str) -> String; /// Send profile's contact an invite for/to target. Returns computed sent message (including index and hash values) - fn send_invitation(&self, profile: ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String; + fn send_invitation(&self, profile: &ProfileIdentity, conversation_id: ConversationID, target_id: i32) -> String; /// share a file file_path with a conersation. Returns computed sent message (including index and hash values) - fn share_file(&self, profile: ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String; + fn share_file(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str) -> String; /// download a file from a conversation to the file_path fn download_file( &self, - profile: ProfileIdentity, + profile: &ProfileIdentity, conversation_id: ConversationID, file_path: &str, manifest_path: &str, @@ -85,42 +85,42 @@ pub trait CwtchLib { ); /// Query the status of a download - fn check_download_status(&self, profile: ProfileIdentity, file_key: FileKey); + fn check_download_status(&self, profile: &ProfileIdentity, file_key: FileKey); /// Verufy a download is done, and if not, resume it - fn verify_or_resume_download(&self, profile: ProfileIdentity, conversation_id: ConversationID, file_key: FileKey); + fn verify_or_resume_download(&self, profile: &ProfileIdentity, conversation_id: ConversationID, file_key: FileKey); /// Ask the ACN inside the Cwtch app to restart the tor connection fn reset_tor(&self); /// Cause profile to create a group on server with name - fn create_group(&self, profile: ProfileIdentity, server: &str, name: &str); + fn create_group(&self, profile: &ProfileIdentity, server: &str, name: &str); /// Delete profile with encryption/password check of pass - fn delete_profile(&self, profile: ProfileIdentity, pass: &str); + fn delete_profile(&self, profile: &ProfileIdentity, pass: &str); /// Cause profile to archive conversation with contact - fn archive_conversation(&self, profile: ProfileIdentity, conversation_id: ConversationID); + fn archive_conversation(&self, profile: &ProfileIdentity, conversation_id: ConversationID); /// Cause profile to delete contact/group identified by handle - fn delete_contact(&self, profile: ProfileIdentity, conversation_id: ConversationID); + fn delete_contact(&self, profile: &ProfileIdentity, conversation_id: ConversationID); /// Cuase profile to attempt to import a contact/group/keybundle identified by bundle - fn import_bundle(&self, profile: ProfileIdentity, bundle: &str); + fn import_bundle(&self, profile: &ProfileIdentity, bundle: &str); /// Set a profile attribute key to val - fn set_profile_attribute(&self, profile: ProfileIdentity, key: &str, val: &str); + fn set_profile_attribute(&self, profile: &ProfileIdentity, key: &str, val: &str); /// Get a profile attribute - fn get_profile_attribute(&self, profile: ProfileIdentity, key: &str) -> Result, CwtchError>; + fn get_profile_attribute(&self, profile: &ProfileIdentity, key: &str) -> Result, CwtchError>; /// Set a profile's contact's attribute of key to val - fn set_conversation_attribute(&self, profile: ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str); + fn set_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str, val: &str); /// Set an attribute on a message in a conversation fn set_message_attribute( &self, - profile: ProfileIdentity, + profile: &ProfileIdentity, conversation_id: ConversationID, channel_id: i32, message_id: i32, @@ -129,13 +129,13 @@ pub trait CwtchLib { ); /// Get an attribute for a conversation - fn get_conversation_attribute(&self, profile: ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result, CwtchError>; + fn get_conversation_attribute(&self, profile: &ProfileIdentity, conversation_id: ConversationID, key: &str) -> Result, CwtchError>; /// Change a profile's password to new_pass if old_pass is correct - fn change_password(&self, profile: ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str); + fn change_password(&self, profile: &ProfileIdentity, old_pass: &str, new_pass: &str, new_pass_again: &str); /// Export a profile to filename - fn export_profile(&self, profile: ProfileIdentity, filename: &str); + fn export_profile(&self, profile: &ProfileIdentity, filename: &str); /// Import a profile from a file with supplied password. Json of a profile struct returned on success fn import_profile(&self, filename: &str, password: &str) -> String; diff --git a/src/structs.rs b/src/structs.rs index e3c9b93..706c07f 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,10 +1,12 @@ use crate::structs::ConnectionState::Disconnected; -use crate::CwtchLib; +use crate::{ConversationID, CwtchLib, ProfileIdentity}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DefaultOnError}; +use serde_repr::*; use std::collections::HashMap; +use crate::event::ContactIdentity; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] /// Defines the states a Cwtch connection can be in pub enum ConnectionState { /// The Cwtch connection is not conected at all @@ -81,7 +83,7 @@ pub struct CwtchEvent { pub data: HashMap, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] #[allow(non_snake_case)] /// AccessControl is a type determining client assigned authorization to a peer @@ -98,15 +100,15 @@ pub struct AccessControl { pub type ACL = HashMap; #[serde_as] -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] /// Struct to serialize/deserialize conversations coming from libcwtch-go pub struct Conversation { /// onion address / id of the conversation #[serde(alias = "onion")] - pub handle: String, + pub contact_id: ContactIdentity, /// unique identifier of the contact/conversation to be used in API access - pub identifier: i32, + pub identifier: ConversationID, // TODO TEST does this work here for serde deserializtion /// display name of the conversation, as determined in libcwtch-go from name specified by contact pub name: String, #[serde_as(deserialize_as = "DefaultOnError")] @@ -125,7 +127,7 @@ pub struct Conversation { //attr: HashMap, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] /// Struct to serialize/deserialize servers coming from libcwtch-go pub struct Server { /// onion address of the server @@ -134,13 +136,11 @@ pub struct Server { pub status: ConnectionState, } -#[serde_as] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone)] /// Struct to serialize/deserialize profiles coming from libcwtch-go pub struct Profile { /// onion address / ID of the profile - #[serde(alias = "onion")] - pub handle: String, + pub profile_id: ProfileIdentity, /// nick name of the onion as supplied by libcwtch-go based on "name" attribute pub nick: String, /// path to a profile image, controled by "picture" attribute @@ -148,22 +148,66 @@ pub struct Profile { /// all profile attributes pub attr: HashMap, /// map of conversation [ onion => conversation ] - pub conversations: HashMap, + pub conversations: HashMap, /// map of servers [ onion => server ] pub servers: HashMap, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize_repr, Deserialize_repr, PartialEq, Clone)] +#[repr(i32)] +/// Enum matching message types and their overlays as defined in Cwtch +/// - https://git.openprivacy.ca/cwtch.im/cwtch/src/commit/907a7ca638fbcbaac5d652608d455d4217597fa9/model/overlay.go +/// - https://git.openprivacy.ca/cwtch.im/cwtch-ui/src/commit/3a752b73972cbfc53b6da26116fe018ee2ac2343/lib/models/message.dart +pub enum MessageType { + /// A standard Cwtch message of text + TextMessage = 1, + /// This message is a text message but it also contains a reference to the message it is quoting + QuotedMessage = 10, + /// A shared contact message + SuggestContact = 100, + /// A group invite message + InviteGroup = 101, + /// a share file message + FileShare = 200, + /// This message is of an unsupported type so malformed + MalformedMessage, +} + +impl From for MessageType { + fn from(x: i32) -> Self { + match x { + 1 => MessageType::TextMessage, + 10 => MessageType::QuotedMessage, + 100 => MessageType::SuggestContact, + 101 => MessageType::InviteGroup, + 200 => MessageType::FileShare, + _ => MessageType::MalformedMessage, + } + } +} + + +#[derive(Debug, Serialize, Deserialize, Clone)] /// Struct to serialize/deserialize messages sent over Cwtch between profiles / conversation pub struct Message { /// overlay id that the message is targeting as defined in cwtch/model/overlay.go /// [ OverlayChat = 1, OverlayInviteContact = 100, OverlayInviteGroup = 101, OverlayFileSharing = 200 ] - pub o: i64, + pub o: MessageType, /// data of the message pub d: String, } -#[derive(Debug, Serialize, Deserialize)] +impl Message { + /// parse json into a Message + pub fn from_json(json: &str) -> Self { + match serde_json::from_str(json) { + Ok(m) => m, + Err(e) => Message{o: MessageType::MalformedMessage, d: e.to_string()} + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] /// Settings as defined in libcwtch-go/utils/settings.go and should be populated by handeling the UpdateGlobalSettings event emited during cwtch.start() #[allow(non_snake_case)] pub struct Settings { @@ -265,7 +309,7 @@ impl Profile { /// contacts_json as supplied by libcwtch-go, a map of conversations /// server_list as supplied by libcwtch-go, a map of servers pub fn new( - identity: &str, + identity: ProfileIdentity, name: &str, picture: &str, conversations_json: &str, @@ -280,7 +324,7 @@ impl Profile { Err(e) => return Err(e), }; Ok(Profile { - handle: identity.to_string(), + profile_id: identity, nick: name.to_string(), image_path: picture.to_string(), attr: Default::default(), @@ -289,8 +333,8 @@ impl Profile { }) } - fn process_conversations(conversations_json: &str) -> Result, String> { - let mut conversations: HashMap = HashMap::new(); + fn process_conversations(conversations_json: &str) -> Result, String> { + let mut conversations: HashMap = HashMap::new(); if conversations_json == "null" { return Ok(conversations); } @@ -320,8 +364,8 @@ impl Profile { } /// Find a conversation_id associated with a remote handle (used for some events with no conversation id like PeerStateChange) - pub fn find_conversation_id_by_handle(&self, handle: String) -> Option { - match self.conversations.values().filter(|c| c.handle == handle).next() { + pub fn find_conversation_id_by_handle(&self, contact_id: ContactIdentity) -> Option { + match self.conversations.values().filter(|c| c.contact_id == contact_id).next() { Some(conversation) => Some(conversation.identifier), None => None }