commit ff8f8985d86f19f988befb50dee581e493cdab20 Author: Dan Ballard Date: Mon Apr 11 22:56:12 2022 -0700 initial import of imp bot framework from update_bot diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b471067 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d422518 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "imp" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libcwtch = {path = "../libcwtch-rs" } #"0.2.0" +serde_json = "1.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..00645bb --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# imp + +*small demon, a familiar of a witch* + +![](imp_color.jpg) + +imp is a set of bot creating utilities built on top of [libcwtch-rs](https://git.openprivacy.ca/cwtch.im/libcwtch-rs) + +It is in the very early prototype stage with one prototype use in the Cwtch [update bot](https://git.openprivacy.ca/dan/update_bot/) + +## Usage + +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. + +Finally, run the imp `my_imp.event_loop(Box::new(custom_event_handler));` \ No newline at end of file diff --git a/imp.jpg b/imp.jpg new file mode 100644 index 0000000..71eb6e7 Binary files /dev/null and b/imp.jpg differ diff --git a/imp_color.jpg b/imp_color.jpg new file mode 100644 index 0000000..d4cb39e Binary files /dev/null and b/imp_color.jpg differ diff --git a/imp_details.txt b/imp_details.txt new file mode 100644 index 0000000..44dfa44 --- /dev/null +++ b/imp_details.txt @@ -0,0 +1,12 @@ +https://en.m.wikipedia.org/wiki/File:Imp_with_cards_-_illustration_from_Le_grand_Etteilla.jpg + +Description +English: Illustration of an imp looking at a hand of playing cards. Published on page 193 of Le grand Etteilla, ou, l'art de tirer les cartes. +Date circa 1838 +Source Le grand Etteilla, ou, l'art de tirer les cartes, p. 193 +Author Simon Blocquel (1780–1863, under the pseudonym Julia Orsini) + +Licensing +Public domain +This work is in the public domain in its country of origin and other countries and areas where the copyright term is the author's life plus 70 years or fewer. +This work is in the public domain in the United States because it was published (or registered with the U.S. Copyright Office) before January 1, 1927. \ No newline at end of file diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..96477ed --- /dev/null +++ b/src/event.rs @@ -0,0 +1,27 @@ +#[derive(Debug)] +pub enum Event { + CwtchStarted, + NewPeer, + NewMessageFromPeer, + AppError, + ContactCreated, + PeerStateChange, + UpdateGlobalSettings, + + ErrUnhandled(String), +} + +impl From<&str> for Event { + fn from(name: &str) -> Self { + match name { + "CwtchStarted" => Event::CwtchStarted, + "NewPeer" => Event::NewPeer, + "NewMessageFromPeer" => Event::NewMessageFromPeer, + "AppError" => Event::AppError, + "ContactCreated" => Event::ContactCreated, + "PeerStateChange" => Event::PeerStateChange, + "UpdateGlobalSettings" => Event::UpdateGlobalSettings, + _ => Event::ErrUnhandled(name.to_string()), + } + } +} diff --git a/src/imp.rs b/src/imp.rs new file mode 100644 index 0000000..0fdbf6d --- /dev/null +++ b/src/imp.rs @@ -0,0 +1,210 @@ +use libcwtch::structs::*; +use libcwtch::CwtchLib; +use crate::event::Event; + +use serde_json; +use crate::imp::DefaultContactPolicy::Accept; + +/// How new contacts should be treated +pub enum DefaultContactPolicy { + /// Do not react, leave it for the custom event handler + Ignore, + /// Block all new contacts + Block, + /// Accept all new contacts + Accept, +} + +/// Settings for the bot on how it should automatically behave +pub struct Behaviour { + /// The bot will enable experimental feautres (required for any experiments to be used) + pub proto_experiments: bool, + /// The bot will use the file sharing experiment + pub proto_experiment_fileshare: bool, + + /// The profile name the bot will share with accepted conversations + pub profile_name: String, + /// The profile pic the bot with share with accepted conversations IF the file share exoeriment is enabled + pub profile_pic_path: Option, + + /// Policy dictacting how the bot should automatically handle ContactCreated events + pub default_contant_policy: DefaultContactPolicy, +} + +impl Behaviour { + /// Returns a named, profile pic using, always accepting conversations bot + pub fn new_default_acceptor(name: String, profile_pic_path: String) -> Self { + return Behaviour{ + proto_experiments: true, + proto_experiment_fileshare: true, + default_contant_policy: Accept, + profile_name: name, + profile_pic_path: Some(profile_pic_path), + } + } +} + +/// 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(&self, cwtch: &dyn CwtchLib, profile: Option<&Profile>, event: CwtchEvent); +} + +/// Cwtch bot +pub struct Imp { + cwtch: Box, + behaviour: Behaviour, + password: String, + home_dir: String, + + settings: Option, + profile: Option, +} + +impl Imp { + /// Create a new imp bot with the specified behaviour + /// start_cwtch is called on it + pub fn spawn(behaviour: Behaviour, password: String, home_dir: String) -> Self { + let cwtch = libcwtch::new_cwtchlib_go(); + println!("start_cwtch"); + let ret = cwtch.start_cwtch(&home_dir, ""); + println!("start_cwtch returned {}", ret); + + return Imp {behaviour, cwtch: Box::new(cwtch), password, home_dir, profile: None, settings: None} + } + + /// 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 + pub fn event_loop(&mut self, handler: Box) { + let mut initialized: bool = false; + + loop { + let event_str = self.cwtch.get_appbus_event(); + println!("bot: event: {}", event_str); + + let event: CwtchEvent = + serde_json::from_str(&event_str).expect("Error parsing Cwtch event"); + let event_type = Event::from(event.event_type.as_str()) ; + match event_type { + Event::CwtchStarted => { + println!("event CwtchStarted!"); + initialized = true; + + match self.profile { + None => { + println!("Creating bot"); + self.cwtch.load_profiles(&self.password); + } + Some(_) => (), + } + } + Event::UpdateGlobalSettings => { + println!("Loading settings froms {}", &event.data["Data"]); + let mut settings: Settings = match serde_json::from_str(&event.data["Data"]) { + Ok(s) => s, + Err(e) => panic!("invalid json: {:?}", e), + }; + + if self.behaviour.proto_experiments { + settings.ExperimentsEnabled = true; + } + if self.behaviour.proto_experiment_fileshare { + settings + .Experiments + .insert(Experiments::FileSharingExperiment.to_key_string(), true); + } + match settings.save(self.cwtch.as_ref()) { + Ok(_) => (), + Err(e) => println!("ERROR: could not save settings: {}", e), + }; + + 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); + } + } + None => (), + }; + + self.settings = Some(settings); + } + Event::NewPeer => { + println!( + "\n***** {} at {} *****\n", + event.data["name"], event.data["Identity"] + ); + + // process json for profile, conversations 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), + }; + print!("profile: {:?}", profile); + // Share profile image + match self.settings.as_ref() { + Some(_settings) => { + self.cwtch.share_file(&profile.handle, -1, "build_bot.png"); + } + None => (), + }; + + self.cwtch.set_profile_attribute(&profile.handle, "profile.name", &self.behaviour.profile_name); + + self.profile = Some(profile); + } + Event::AppError => { + if initialized && event.data["Error"] == "Loaded 0 profiles" { + self.cwtch.create_profile(&self.behaviour.profile_name, &self.password); + } + } + Event::ContactCreated => { + if event.data["ConnectionState"] == "Authenticated" { + let profile_onion = event.data["RemotePeer"].to_string(); + let convo_id = event.data["ConversationID"].parse::().unwrap(); + + let acl: ACL = serde_json::from_str(&event.data["accessControlList"]) + .expect("Error parsing conversation"); + + let conversation = Conversation { + handle: profile_onion.clone(), + identifier: event.data["ConversationID"].parse::().unwrap(), + name: event.data["nick"].to_string(), + status: ConnectionState::new(&event.data["status"]), + blocked: event.data["blocked"] == "true", + accepted: event.data["accepted"] == "true", + access_control_list: acl, + is_group: false, // by definition + }; + + match self.behaviour.default_contant_policy { + DefaultContactPolicy::Accept => + self.cwtch.accept_conversation(&conversation.handle.clone(), convo_id), + DefaultContactPolicy::Block => + self.cwtch.block_contact(&conversation.handle.clone(), convo_id), + DefaultContactPolicy::Ignore => (), + } + + match self.profile.as_mut() { + Some(profile) => { + profile + .conversations + .insert(event.data["RemotePeer"].to_string(), conversation); + } + None => (), + }; + } + } + Event::ErrUnhandled(err) => eprintln!("unhandled event: {}!", err), + _ => (), + }; + + handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), event); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0dc8d7d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod imp; +pub mod event; \ No newline at end of file