cwtch-imp 0.3.1

small demon, a familiar of a witch. imp is a set of bot creating utilities built on top of libcwtch-rs
Documentation
use chrono::{DateTime, FixedOffset};
use libcwtch::structs::*;
use libcwtch::CwtchLib;
use libcwtch::event::{ContactIdentity, ConversationID, Event};
use libcwtch::structs::MessageType::InviteGroup;

use crate::behaviour::{Behaviour, ContactInteractionPolicy, GroupInteractionPolicy, GroupInvitePolicy, 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 {
    #[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(&mut self, cwtch: &dyn libcwtch::CwtchLib, behaviour: &mut Behaviour, profile: &Profile, conversation_id: ConversationID, handle: String, timestamp_received: DateTime<FixedOffset>, message: MessageWrapper) {}

    #[allow(unused_variables)]
    fn on_new_message_from_group(&mut self, cwtch: &dyn libcwtch::CwtchLib, behaviour: &mut Behaviour, profile: &Profile, conversation_id: ConversationID, contact: ContactIdentity, timestamp_sent: DateTime<FixedOffset>, message: MessageWrapper) {}
}

/// Cwtch bot
pub struct Imp {
    cwtch: Box<dyn libcwtch::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,
        };
    }

    pub fn behaviour_mut(&self) -> &Behaviour {
        &self.behaviour
    }
    pub fn behaviour_allowlist_add(&mut self, contact: ContactIdentity) {
        self.behaviour.allow_list.peers.push(contact);
    }

    pub fn behaviour_allowlist_rm(&mut self, contact: ContactIdentity) {
        if let Some(index) = self.behaviour.allow_list.peers.iter().position(|c| *c == contact) {
            self.behaviour.allow_list.peers.remove(index);
        }
    }

    /// 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<T>(&mut self, handler: &mut T)
    where
        T: EventHandler,
    {
        let mut initialized: bool = false;

        loop {
            let event = self.cwtch.get_appbus_event();

            match &event {
                Event::CwtchStarted => {
                    println!("Cwtch Started");
                    initialized = true;

                    if self.profile.is_none() {
                        self.cwtch.load_profiles(&self.password);
                    }
                }
                Event::UpdateGlobalSettings { settings } => {
                    let mut local_settings = settings.clone();
                    println!("Loading settings froms {:?}", local_settings);

                    if self.behaviour.proto_experiments {
                        local_settings.ExperimentsEnabled = true;
                    }
                    if self.behaviour.proto_experiment_fileshare {
                        local_settings
                            .Experiments
                            .insert(Experiments::FileSharingExperiment.to_key_string(), true);
                    }
                    if self.behaviour.proto_experiment_groups {
                        local_settings
                            .Experiments
                            .insert(Experiments::GroupExperiment.to_key_string(), true);
                    }
                    match local_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.profile_id, ConversationID(-1), profile_pic_path);
                            }
                        }
                        None => (),
                    };

                    self.settings = Some(local_settings);
                }
                Event::NewPeer { profile_id, tag, created, name, default_picture, picture, online, profile_data} => {
                    if let Err(e) = profile_data {
                        panic!("error parsing profile: {}", e);
                    }

                    self.cwtch.set_profile_attribute(
                        &profile_id,
                        "profile.name",
                        &self.behaviour.profile_name,
                    );

                    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 = &self.behaviour.new_contant_policy {
                            for contact_id in &self.behaviour.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(ok_profile.clone());
                    }
                }
                Event::AppError { error, data } => {
                    if initialized && error == "Loaded 0 profiles" {
                        if self.profile.is_none() {
                            self.cwtch
                                .create_profile(&self.behaviour.profile_name, &self.password, true);
                        }
                    }
                }
                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 {
                        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
                    };

                    self.process_contact(conversation.identifier);

                    match self.profile.as_mut() {
                        Some(profile) => {
                            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 { 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 } => {
                    if message.o == InviteGroup {
                        if let Some(profile) = self.profile.as_ref() {
                            match &self.behaviour.group_invite_policy {
                                GroupInvitePolicy::Ignore => (),
                                GroupInvitePolicy::Accept => self.cwtch.import_bundle(profile_id, message.d.as_str()),
                                GroupInvitePolicy::AcceptFromContact=> {
                                    if profile.conversations[conversation_id].accepted {
                                        self.cwtch.import_bundle(profile_id, message.d.as_str())
                                    }
                                }
                                GroupInvitePolicy::AllowList => {
                                    if let Some(conversation) = profile.conversations.get(&conversation_id) {
                                        if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
                                            self.cwtch.import_bundle(profile_id, message.d.as_str())
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        if let Some(profile) = self.profile.as_ref() {
                            match &self.behaviour.contact_interaction_policy {
                                ContactInteractionPolicy::Ignore => (),
                                ContactInteractionPolicy::Accept => handler.on_new_message_from_contact(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone()),
                                ContactInteractionPolicy::AcceptFromContact=> {
                                    if profile.conversations[conversation_id].accepted {
                                        handler.on_new_message_from_contact(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone());
                                    }
                                },
                                ContactInteractionPolicy::AllowList => {
                                    if let Some(conversation) = profile.conversations.get(&conversation_id) {
                                        if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
                                            handler.on_new_message_from_contact(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone());
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                Event::NewMessageFromGroup { profile_id, conversation_id, timestamp_sent, contact_id, index, message, content_hash, picture, notification, } => {
                    if let Some(profile) = self.profile.as_ref() {
                        match &self.behaviour.group_interaction_policy {
                            GroupInteractionPolicy::Ignore => (),
                            GroupInteractionPolicy::Accept => handler.on_new_message_from_group(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), contact_id.clone(), timestamp_sent.clone(), message.clone()),
                            GroupInteractionPolicy::AcceptFromContact=> {
                                if let Some(contact_convo_id) = profile.find_conversation_id_by_handle(contact_id.clone()) {
                                    if profile.conversations[&contact_convo_id].accepted {
                                        handler.on_new_message_from_group(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), contact_id.clone(), timestamp_sent.clone(), message.clone())
                                    }
                                }
                            },
                            GroupInteractionPolicy::AllowList => {
                                if self.behaviour.allow_list.peers.contains(&contact_id) {
                                    handler.on_new_message_from_group(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), contact_id.clone(), timestamp_sent.clone(), message.clone())
                                }
                            }
                        }
                    }
                }
                Event::ErrUnhandled { name, data } => eprintln!("unhandled event: {}!", name),
                _ => (),
            };

            handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), &event);
        }
    }

    fn process_contact(&self,  conversation_id: ConversationID) {
        match &self.profile {
            Some(profile) => {
                let profile_handle = profile.profile_id.clone();
                match &self.behaviour.new_contant_policy {
                    NewContactPolicy::Accept => {
                        self.cwtch
                            .accept_conversation(&profile.profile_id, conversation_id);
                    }
                    NewContactPolicy::Block => self.cwtch.block_conversation(&profile_handle.clone(), conversation_id),
                    NewContactPolicy::AllowList => {
                        match profile.conversations.get(&conversation_id) {
                            Some(conversation) => {
                                if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
                                    self.cwtch
                                        .accept_conversation(&profile_handle.clone(), conversation_id);
                                } else {
                                    self.cwtch.block_conversation(&profile_handle.clone(), conversation_id);
                                }
                            },
                            None => {},
                        }
                    }
                    NewContactPolicy::Ignore => (),
                }
            },
            None => {},
        }
    }
}