imp/src/imp.rs

281 lines
10 KiB
Rust

use crate::event::Event;
use libcwtch::structs::*;
use libcwtch::CwtchLib;
use serde_json;
/// How new contacts should be treated
pub enum NewContactPolicy {
/// 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 enable the file sharing experiment
pub proto_experiment_fileshare: bool,
/// The bot will enable the groups experiment
pub proto_experiment_groups: 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 new_contant_policy: NewContactPolicy,
}
pub struct BehaviourBuilder {
behaviour: Behaviour,
}
impl BehaviourBuilder {
pub fn new() -> Self {
return BehaviourBuilder {
behaviour: Behaviour {
proto_experiments: false,
proto_experiment_fileshare: false,
proto_experiment_groups: false,
new_contant_policy: NewContactPolicy::Ignore,
profile_name: "".to_string(),
profile_pic_path: None,
},
};
}
pub fn build(self) -> Behaviour {
self.behaviour
}
pub fn groups(mut self, val: bool) -> Self {
self.behaviour.proto_experiment_groups = val;
self.behaviour.proto_experiments = true;
self
}
pub fn fileshare(mut self, val: bool) -> Self {
self.behaviour.proto_experiment_fileshare = val;
self.behaviour.proto_experiments = true;
self
}
pub fn profile_pic_path(mut self, val: String) -> Self {
self.behaviour.profile_pic_path = Some(val);
self.behaviour.proto_experiment_fileshare = true;
self.behaviour.proto_experiments = true;
self
}
pub fn name(mut self, val: String) -> Self {
self.behaviour.profile_name = val;
self
}
pub fn new_contact_policy(mut self, val: NewContactPolicy) -> Self {
self.behaviour.new_contant_policy = val;
self
}
}
/// 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 CwtchLib, profile: Option<&Profile>, event: Event);
}
/// 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<T>(&mut self, handler: &mut T)
where
T: 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 = Event::from(&event);
match &event {
Event::CwtchStarted { data } => {
println!("event CwtchStarted!");
initialized = true;
match self.profile {
None => {
println!("Creating bot");
self.cwtch.load_profiles(&self.password);
}
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),
};
if self.behaviour.proto_experiments {
settings.ExperimentsEnabled = true;
}
if self.behaviour.proto_experiment_fileshare {
settings
.Experiments
.insert(Experiments::FileSharingExperiment.to_key_string(), true);
}
if self.behaviour.proto_experiment_groups {
settings
.Experiments
.insert(Experiments::GroupExperiment.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 { data } => {
println!("\n***** {} at {} *****\n", data["name"], data["Identity"]);
// 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");
}
None => (),
};
self.cwtch.set_profile_attribute(
&profile.handle,
"profile.name",
&self.behaviour.profile_name,
);
for (_id, conversation) in &profile.conversations {
match self.behaviour.new_contant_policy {
NewContactPolicy::Accept => {
self.cwtch
.accept_conversation(&profile.handle.clone(), conversation.identifier);
}
NewContactPolicy::Block => self
.cwtch
.block_contact(&profile.handle.clone(), conversation.identifier),
NewContactPolicy::Ignore => (),
}
}
self.profile = Some(profile);
}
Event::AppError { data } => {
if initialized && data["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 convo_id = data["ConversationID"].parse::<i32>().unwrap();
let acl: ACL = serde_json::from_str(&data["accessControlList"]).expect("Error parsing conversation");
let conversation = Conversation {
handle: convo_handle.clone(),
identifier: data["ConversationID"].parse::<i32>().unwrap(),
name: data["nick"].to_string(),
status: ConnectionState::new(&data["status"]),
blocked: data["blocked"] == "true",
accepted: data["accepted"] == "true",
access_control_list: acl,
is_group: false, // by definition
};
match self.profile.as_mut() {
Some(profile) => {
profile
.conversations
.insert(data["RemotePeer"].to_string(), conversation);
match self.behaviour.new_contant_policy {
NewContactPolicy::Accept => {
self.cwtch
.accept_conversation(&profile.handle.clone(), convo_id);
}
NewContactPolicy::Block => self.cwtch.block_contact(&profile.handle.clone(), convo_id),
NewContactPolicy::Ignore => (),
}
}
None => (),
};
}
Event::PeerStateChange { data } => {}
Event::ErrUnhandled { name, data } => eprintln!("unhandled event: {}!", name),
_ => (),
};
handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), event);
}
}
}