initial import of imp bot framework from update_bot

This commit is contained in:
Dan Ballard 2022-04-11 22:56:12 -07:00
commit ff8f8985d8
9 changed files with 281 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
Cargo.lock
.idea

10
Cargo.toml Normal file
View File

@ -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"

17
README.md Normal file
View File

@ -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));`

BIN
imp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
imp_color.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

12
imp_details.txt Normal file
View File

@ -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 (17801863, 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.

27
src/event.rs Normal file
View File

@ -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()),
}
}
}

210
src/imp.rs Normal file
View File

@ -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<String>,
/// 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<dyn CwtchLib>,
behaviour: Behaviour,
password: String,
home_dir: String,
settings: Option<Settings>,
profile: Option<Profile>,
}
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<dyn EventHandler>) {
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::<i32>().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::<i32>().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);
}
}
}

2
src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod imp;
pub mod event;