diff --git a/Cargo.toml b/Cargo.toml index 6d26f1e..83cd98a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] -name = "imp" -version = "0.1.0" +name = "cwtch-imp" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libcwtch = "0.3.2" -serde_json = "1.0" +libcwtch = "0.4.0" chrono = "0.4.19" \ No newline at end of file diff --git a/README.md b/README.md index 6b18a6c..485c292 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ It is in the very early prototype stage with one prototype use in the Cwtch [upd Start with creating a `Behaviour` struct and populating it with your desired set of bot behaviours, then `imp::spawn` your bot with the behaviour. -Define a struct fulfilling the `imp::EventHandler` trait with all your custom event handling code. +To handle Cwtch events you can either +- Define a struct fulfilling the `imp::EventHandler::event_loop` function which has the capacity to support all the events libCwtch can emit +- Override specific `on_x_event` functions in `imp::EventHandler` such as `on_new_message_from_contact` + - This is newer and more will be defined in later versions -Finally, run the imp `my_imp.event_loop(Box::new(custom_event_handler));` +Finally, run the imp `my_imp.event_loop::(update_bot.borrow_mut());` ## Examples diff --git a/src/behaviour.rs b/src/behaviour.rs index fdadd17..06fd1df 100644 --- a/src/behaviour.rs +++ b/src/behaviour.rs @@ -1,16 +1,17 @@ +use libcwtch::event::{ContactIdentity, GroupID}; /// defines a locked list of allowed peers and groups the bot may communicate with /// others will be blocked, and peers listed here will be peered with actively pub struct AllowListMembers { /// list of peers to allow by handle - pub peers: Vec, + pub peers: Vec, /// list of groups to join and listen for peers in peer list from - pub groups: Vec, + pub groups: Vec, } impl AllowListMembers { /// constructs a new AllowListMembers struct - pub fn new(peers: Vec, groups: Vec) -> Self { + pub fn new(peers: Vec, groups: Vec) -> Self { AllowListMembers {peers: peers, groups: groups} } } diff --git a/src/event.rs b/src/event.rs deleted file mode 100644 index 8487588..0000000 --- a/src/event.rs +++ /dev/null @@ -1,72 +0,0 @@ -use chrono::{DateTime, FixedOffset}; -use libcwtch::structs::CwtchEvent; -use std::collections::HashMap; - -#[derive(Debug)] -pub enum Event { - CwtchStarted { - data: HashMap, - }, - NewPeer { - data: HashMap, - }, - - NewMessageFromPeer { - conversation_id: i32, - handle: String, - timestamp_received: DateTime, - message: String, - }, - AppError { - data: HashMap, - }, - ContactCreated { - data: HashMap, - }, - PeerStateChange { - data: HashMap, - }, - UpdateGlobalSettings { - data: HashMap, - }, - - ErrUnhandled { - name: String, - data: HashMap, - }, -} - -impl From<&CwtchEvent> for Event { - fn from(cwtch_event: &CwtchEvent) -> Self { - match cwtch_event.event_type.as_str() { - "CwtchStarted" => Event::CwtchStarted { - data: cwtch_event.data.clone(), - }, - "NewPeer" => Event::NewPeer { - data: cwtch_event.data.clone(), - }, - "NewMessageFromPeer" => Event::NewMessageFromPeer { - conversation_id: cwtch_event.data["ConversationID"].parse().unwrap_or(-2), - handle: cwtch_event.data["RemotePeer"].clone(), - timestamp_received: DateTime::parse_from_rfc3339(cwtch_event.data["TimestampReceived"].as_str()).unwrap(), - message: cwtch_event.data["Data"].clone(), - }, - "AppError" => Event::AppError { - data: cwtch_event.data.clone(), - }, - "ContactCreated" => Event::ContactCreated { - data: cwtch_event.data.clone(), - }, - "PeerStateChange" => Event::PeerStateChange { - data: cwtch_event.data.clone(), - }, - "UpdateGlobalSettings" => Event::UpdateGlobalSettings { - data: cwtch_event.data.clone(), - }, - x => Event::ErrUnhandled { - name: x.to_string(), - data: cwtch_event.data.clone(), - }, - } - } -} diff --git a/src/imp.rs b/src/imp.rs index 8860191..4fa6d04 100644 --- a/src/imp.rs +++ b/src/imp.rs @@ -1,15 +1,23 @@ -use crate::event::Event; +use chrono::{DateTime, FixedOffset}; use libcwtch::structs::*; use libcwtch::CwtchLib; +use libcwtch::event::{ConversationID, Event}; -use serde_json; use crate::behaviour::{Behaviour, NewContactPolicy}; use crate::behaviour::NewContactPolicy::AllowList; /// Trait to be used by implementors of imp bots to supply their custom event handling /// the handle function is called after the default imp automatic event handling has run on each new event pub trait EventHandler { - fn handle(&mut self, cwtch: &dyn libcwtch::CwtchLib, profile: Option<&Profile>, event: Event); + #[allow(unused_variables)] + fn handle(&mut self, cwtch: &dyn libcwtch::CwtchLib, profile: Option<&Profile>, event: &Event) {} + + #[allow(unused_variables)] + fn on_contact_online(&self, cwtch: &dyn libcwtch::CwtchLib, profile: &Profile, convo_id: ConversationID) {} + #[allow(unused_variables)] + fn on_new_contact(&self, cwtch: &dyn libcwtch::CwtchLib, profile: &Profile, convo_id: ConversationID) {} + #[allow(unused_variables)] + fn on_new_message_from_contact(&self, cwtch: &dyn libcwtch::CwtchLib, profile: &Profile, conversation_id: ConversationID, handle: String, timestamp_received: DateTime, message: Message) {} } /// Cwtch bot @@ -43,6 +51,7 @@ impl Imp { } /// The main event loop handler for the bot, supply your own customer handler to handle events after the imp's automatic handling has processed the event + #[allow(unused_variables, unused_mut)] pub fn event_loop(&mut self, handler: &mut T) where T: EventHandler, @@ -50,13 +59,10 @@ impl Imp { let mut initialized: bool = false; loop { - let event_str = self.cwtch.get_appbus_event(); - println!("bot: event: {}", event_str); + let event = self.cwtch.get_appbus_event(); - let event: CwtchEvent = serde_json::from_str(&event_str).expect("Error parsing Cwtch event"); - let event = Event::from(&event); match &event { - Event::CwtchStarted { data } => { + Event::CwtchStarted => { println!("event CwtchStarted!"); initialized = true; @@ -68,27 +74,24 @@ impl Imp { Some(_) => (), } } - Event::UpdateGlobalSettings { data } => { - println!("Loading settings froms {}", &data["Data"]); - let mut settings: Settings = match serde_json::from_str(&data["Data"]) { - Ok(s) => s, - Err(e) => panic!("invalid json: {:?}", e), - }; + Event::UpdateGlobalSettings { settings } => { + let mut local_settings = settings.clone(); + println!("Loading settings froms {:?}", local_settings); if self.behaviour.proto_experiments { - settings.ExperimentsEnabled = true; + local_settings.ExperimentsEnabled = true; } if self.behaviour.proto_experiment_fileshare { - settings + local_settings .Experiments .insert(Experiments::FileSharingExperiment.to_key_string(), true); } if self.behaviour.proto_experiment_groups { - settings + local_settings .Experiments .insert(Experiments::GroupExperiment.to_key_string(), true); } - match settings.save(self.cwtch.as_ref()) { + match local_settings.save(self.cwtch.as_ref()) { Ok(_) => (), Err(e) => println!("ERROR: could not save settings: {}", e), }; @@ -96,77 +99,67 @@ impl Imp { match self.profile.as_ref() { Some(profile) => { if let Some(profile_pic_path) = &self.behaviour.profile_pic_path { - self.cwtch.share_file(&profile.handle, -1, profile_pic_path); + self.cwtch.share_file(&profile.profile_id, ConversationID(-1), profile_pic_path); } } None => (), }; - self.settings = Some(settings); + self.settings = Some(local_settings); } - Event::NewPeer { data } => { - println!("\n***** {} at {} *****\n", data["name"], data["Identity"]); + Event::NewPeer { profile_id, tag, created, name, default_picture, picture, online, profile_data} => { + + if let Err(e) = profile_data { + panic!("error parsing profile: {}", e); + } - // process json for profile, conversations and servers...else { - let profile = match Profile::new( - &data["Identity"], - &data["name"], - &data["picture"], - &data["ContactsJson"], - &data["ServerList"], - ) { - Ok(p) => p, - Err(e) => panic!("error parsing profile: {}", e), - }; // Share profile image match self.settings.as_ref() { Some(_settings) => { - self.cwtch.share_file(&profile.handle, -1, "build_bot.png"); + self.cwtch.share_file(&profile_id, ConversationID(-1), "build_bot.png"); } None => (), }; self.cwtch.set_profile_attribute( - &profile.handle, + &profile_id, "profile.name", &self.behaviour.profile_name, ); - for (_id, conversation) in &profile.conversations { - self.process_contact(conversation.identifier); - } + if let Ok(ok_profile) = profile_data { + for (_id, conversation) in &ok_profile.conversations { + self.process_contact(conversation.identifier); + } - // Allow list should add all people in the list - if let AllowList(allow_list) = &self.behaviour.new_contant_policy { - for handle in &allow_list.peers { - if let None = profile.find_conversation_id_by_handle(handle.clone()) { - self.cwtch.import_bundle(&profile.handle.clone(), &handle.clone()); + // Allow list should add all people in the list + if let AllowList(allow_list) = &self.behaviour.new_contant_policy { + for contact_id in &allow_list.peers { + if let None = ok_profile.find_conversation_id_by_handle(contact_id.clone()) { + self.cwtch.import_bundle(&profile_id, contact_id.clone().as_str()); + } } } - } - self.profile = Some(profile); + + self.profile = Some(ok_profile.clone()); + } } - Event::AppError { data } => { - if initialized && data.contains_key("Error") && data["Error"] == "Loaded 0 profiles" { + Event::AppError { error } => { + if initialized && error == "Loaded 0 profiles" { self.cwtch .create_profile(&self.behaviour.profile_name, &self.password); } } - Event::ContactCreated { data } => { - println!("Contact Created"); - let convo_handle = data["RemotePeer"].to_string(); - - let acl: ACL = serde_json::from_str(&data["accessControlList"]).expect("Error parsing conversation"); - + Event::ContactCreated {profile_id, conversation_id, contact_id, nick, status, unread, picture, default_picture, num_messages, accepted, access_control_list, blocked, loading, last_msg_time, .. } => { let conversation = Conversation { - handle: convo_handle.clone(), - identifier: data["ConversationID"].parse::().unwrap(), - name: data["nick"].to_string(), - status: ConnectionState::new(&data["status"]), - blocked: data["blocked"] == "true", - accepted: data["accepted"] == "true", - access_control_list: acl, + contact_id: contact_id.clone(), + identifier: conversation_id.clone(), + name: nick.clone(), + status: status.clone(), + blocked: blocked.clone(), + accepted: accepted.clone(), + access_control_list: access_control_list.clone(), is_group: false, // by definition }; @@ -177,35 +170,55 @@ impl Imp { profile .conversations .insert(conversation.identifier, conversation); + handler.on_new_contact(self.cwtch.as_ref(), profile, conversation_id.clone()); + handler.on_contact_online(self.cwtch.as_ref(), profile, conversation_id.clone()); } None => (), }; } - Event::PeerStateChange { data } => {} + Event::PeerStateChange { profile_id, contact_id, connection_state } => { + if *connection_state == ConnectionState::Authenticated { + match self.profile.as_ref() { + Some(profile) => { + match profile.find_conversation_id_by_handle(contact_id.clone()) { + Some(conversation_id) => handler.on_contact_online(self.cwtch.as_ref(), profile,conversation_id), + None => {} + } + } + None => (), + }; + } + } + Event::NewMessageFromPeer {profile_id, conversation_id,contact_id, nick, timestamp_received, message, notification, picture } => { + match self.profile.as_ref() { + Some(profile) => handler.on_new_message_from_contact(self.cwtch.as_ref(), profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone()), + None => {}, + } + } Event::ErrUnhandled { name, data } => eprintln!("unhandled event: {}!", name), _ => (), }; - handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), event); + handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), &event); } } - fn process_contact(&self, conversation_id: i32) { + fn process_contact(&self, conversation_id: ConversationID) { match &self.profile { Some(profile) => { - let profile_handle = profile.handle.clone(); + let profile_handle = profile.profile_id.clone(); match &self.behaviour.new_contant_policy { NewContactPolicy::Accept => { self.cwtch - .accept_conversation(&profile_handle.clone(), conversation_id); + .accept_conversation(&profile.profile_id, conversation_id); } NewContactPolicy::Block => self.cwtch.block_contact(&profile_handle.clone(), conversation_id), NewContactPolicy::AllowList(allow_list) => { match profile.conversations.get(&conversation_id) { Some(conversation) => { - if allow_list.peers.contains(&conversation.handle) { + if allow_list.peers.contains(&conversation.contact_id) { self.cwtch .accept_conversation(&profile_handle.clone(), conversation_id); } else { diff --git a/src/lib.rs b/src/lib.rs index 0c2d3b6..994c8d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,2 @@ -pub mod event; pub mod imp; pub mod behaviour;