cwtch_imp/
imp.rs

1use chrono::{DateTime, FixedOffset};
2use libcwtch::structs::*;
3use libcwtch::CwtchLib;
4use libcwtch::event::{ContactIdentity, ConversationID, Event};
5use libcwtch::structs::MessageType::InviteGroup;
6
7use crate::behaviour::{Behaviour, ContactInteractionPolicy, GroupInteractionPolicy, GroupInvitePolicy, NewContactPolicy};
8use crate::behaviour::NewContactPolicy::AllowList;
9
10/// Trait to be used by implementors of imp bots to supply their custom event handling
11/// the handle function is called after the default imp automatic event handling has run on each new event
12pub trait EventHandler {
13    #[allow(unused_variables)]
14    fn handle(&mut self, cwtch: &dyn libcwtch::CwtchLib, profile: Option<&Profile>, event: &Event) {}
15
16    #[allow(unused_variables)]
17    fn on_contact_online(&self, cwtch: &dyn libcwtch::CwtchLib, profile: &Profile, convo_id: ConversationID) {}
18    #[allow(unused_variables)]
19    fn on_new_contact(&self, cwtch: &dyn libcwtch::CwtchLib, profile: &Profile, convo_id: ConversationID) {}
20    #[allow(unused_variables)]
21    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) {}
22
23    #[allow(unused_variables)]
24    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) {}
25}
26
27/// Cwtch bot
28pub struct Imp {
29    cwtch: Box<dyn libcwtch::CwtchLib>,
30    behaviour: Behaviour,
31    password: String,
32    home_dir: String,
33
34    settings: Option<Settings>,
35    profile: Option<Profile>,
36}
37
38impl Imp {
39    /// Create a new imp bot with the specified behaviour
40    /// start_cwtch is called on it
41    pub fn spawn(behaviour: Behaviour, password: String, home_dir: String) -> Self {
42        let cwtch = libcwtch::new_cwtchlib_go();
43        println!("start_cwtch");
44        let ret = cwtch.start_cwtch(&home_dir, "");
45        println!("start_cwtch returned {}", ret);
46
47        return Imp {
48            behaviour,
49            cwtch: Box::new(cwtch),
50            password,
51            home_dir,
52            profile: None,
53            settings: None,
54        };
55    }
56
57    pub fn behaviour_mut(&self) -> &Behaviour {
58        &self.behaviour
59    }
60    pub fn behaviour_allowlist_add(&mut self, contact: ContactIdentity) {
61        self.behaviour.allow_list.peers.push(contact);
62    }
63
64    pub fn behaviour_allowlist_rm(&mut self, contact: ContactIdentity) {
65        if let Some(index) = self.behaviour.allow_list.peers.iter().position(|c| *c == contact) {
66            self.behaviour.allow_list.peers.remove(index);
67        }
68    }
69
70    /// 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
71    #[allow(unused_variables, unused_mut)]
72    pub fn event_loop<T>(&mut self, handler: &mut T)
73    where
74        T: EventHandler,
75    {
76        let mut initialized: bool = false;
77
78        loop {
79            let event = self.cwtch.get_appbus_event();
80
81            match &event {
82                Event::CwtchStarted => {
83                    println!("Cwtch Started");
84                    initialized = true;
85
86                    if self.profile.is_none() {
87                        self.cwtch.load_profiles(&self.password);
88                    }
89                }
90                Event::UpdateGlobalSettings { settings } => {
91                    let mut local_settings = settings.clone();
92                    println!("Loading settings froms {:?}", local_settings);
93
94                    if self.behaviour.proto_experiments {
95                        local_settings.ExperimentsEnabled = true;
96                    }
97                    if self.behaviour.proto_experiment_fileshare {
98                        local_settings
99                            .Experiments
100                            .insert(Experiments::FileSharingExperiment.to_key_string(), true);
101                    }
102                    if self.behaviour.proto_experiment_groups {
103                        local_settings
104                            .Experiments
105                            .insert(Experiments::GroupExperiment.to_key_string(), true);
106                    }
107                    match local_settings.save(self.cwtch.as_ref()) {
108                        Ok(_) => (),
109                        Err(e) => println!("ERROR: could not save settings: {}", e),
110                    };
111
112                    match self.profile.as_ref() {
113                        Some(profile) => {
114                            if let Some(profile_pic_path) = &self.behaviour.profile_pic_path {
115                                self.cwtch.share_file(&profile.profile_id, ConversationID(-1), profile_pic_path);
116                            }
117                        }
118                        None => (),
119                    };
120
121                    self.settings = Some(local_settings);
122                }
123                Event::NewPeer { profile_id, tag, created, name, default_picture, picture, online, profile_data} => {
124                    if let Err(e) = profile_data {
125                        panic!("error parsing profile: {}", e);
126                    }
127
128                    self.cwtch.set_profile_attribute(
129                        &profile_id,
130                        "profile.name",
131                        &self.behaviour.profile_name,
132                    );
133
134                    if let Ok(ok_profile) = profile_data {
135                        for (_id, conversation) in &ok_profile.conversations {
136                            self.process_contact(conversation.identifier);
137                        }
138
139                        // Allow list should add all people in the list
140                        if let AllowList = &self.behaviour.new_contant_policy {
141                            for contact_id in &self.behaviour.allow_list.peers {
142                                if let None = ok_profile.find_conversation_id_by_handle(contact_id.clone()) {
143                                    self.cwtch.import_bundle(&profile_id, contact_id.clone().as_str());
144                                }
145                            }
146                        }
147
148                        self.profile = Some(ok_profile.clone());
149                    }
150                }
151                Event::AppError { error, data } => {
152                    if initialized && error == "Loaded 0 profiles" {
153                        if self.profile.is_none() {
154                            self.cwtch
155                                .create_profile(&self.behaviour.profile_name, &self.password, true);
156                        }
157                    }
158                }
159                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, .. } => {
160                    let conversation = Conversation {
161                        contact_id: contact_id.clone(),
162                        identifier: conversation_id.clone(),
163                        name: nick.clone(),
164                        status: status.clone(),
165                        blocked: blocked.clone(),
166                        accepted: accepted.clone(),
167                        access_control_list: access_control_list.clone(),
168                        is_group: false, // by definition
169                    };
170
171                    self.process_contact(conversation.identifier);
172
173                    match self.profile.as_mut() {
174                        Some(profile) => {
175                            profile
176                                .conversations
177                                .insert(conversation.identifier, conversation);
178                            handler.on_new_contact(self.cwtch.as_ref(), profile, conversation_id.clone());
179                            handler.on_contact_online(self.cwtch.as_ref(), profile, conversation_id.clone());
180                        }
181                        None => (),
182                    };
183                }
184                Event::PeerStateChange { profile_id, contact_id, connection_state } => {
185                    if *connection_state == ConnectionState::Authenticated {
186                        match self.profile.as_ref() {
187                            Some(profile) => {
188                                match profile.find_conversation_id_by_handle(contact_id.clone()) {
189                                    Some(conversation_id) => handler.on_contact_online(self.cwtch.as_ref(), profile,conversation_id),
190                                    None => {}
191                                }
192                            }
193                            None => (),
194                        };
195                    }
196                }
197                Event::NewMessageFromPeer {profile_id, conversation_id,contact_id, nick,  timestamp_received, message, notification, picture } => {
198                    if message.o == InviteGroup {
199                        if let Some(profile) = self.profile.as_ref() {
200                            match &self.behaviour.group_invite_policy {
201                                GroupInvitePolicy::Ignore => (),
202                                GroupInvitePolicy::Accept => self.cwtch.import_bundle(profile_id, message.d.as_str()),
203                                GroupInvitePolicy::AcceptFromContact=> {
204                                    if profile.conversations[conversation_id].accepted {
205                                        self.cwtch.import_bundle(profile_id, message.d.as_str())
206                                    }
207                                }
208                                GroupInvitePolicy::AllowList => {
209                                    if let Some(conversation) = profile.conversations.get(&conversation_id) {
210                                        if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
211                                            self.cwtch.import_bundle(profile_id, message.d.as_str())
212                                        }
213                                    }
214                                }
215                            }
216                        }
217                    } else {
218                        if let Some(profile) = self.profile.as_ref() {
219                            match &self.behaviour.contact_interaction_policy {
220                                ContactInteractionPolicy::Ignore => (),
221                                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()),
222                                ContactInteractionPolicy::AcceptFromContact=> {
223                                    if profile.conversations[conversation_id].accepted {
224                                        handler.on_new_message_from_contact(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone());
225                                    }
226                                },
227                                ContactInteractionPolicy::AllowList => {
228                                    if let Some(conversation) = profile.conversations.get(&conversation_id) {
229                                        if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
230                                            handler.on_new_message_from_contact(self.cwtch.as_ref(), &mut self.behaviour, profile, conversation_id.clone(), nick.clone(), timestamp_received.clone(), message.clone());
231                                        }
232                                    }
233                                }
234                            }
235                        }
236                    }
237                }
238                Event::NewMessageFromGroup { profile_id, conversation_id, timestamp_sent, contact_id, index, message, content_hash, picture, notification, } => {
239                    if let Some(profile) = self.profile.as_ref() {
240                        match &self.behaviour.group_interaction_policy {
241                            GroupInteractionPolicy::Ignore => (),
242                            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()),
243                            GroupInteractionPolicy::AcceptFromContact=> {
244                                if let Some(contact_convo_id) = profile.find_conversation_id_by_handle(contact_id.clone()) {
245                                    if profile.conversations[&contact_convo_id].accepted {
246                                        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())
247                                    }
248                                }
249                            },
250                            GroupInteractionPolicy::AllowList => {
251                                if self.behaviour.allow_list.peers.contains(&contact_id) {
252                                    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())
253                                }
254                            }
255                        }
256                    }
257                }
258                Event::ErrUnhandled { name, data } => eprintln!("unhandled event: {}!", name),
259                _ => (),
260            };
261
262            handler.handle(self.cwtch.as_ref(), self.profile.as_ref(), &event);
263        }
264    }
265
266    fn process_contact(&self,  conversation_id: ConversationID) {
267        match &self.profile {
268            Some(profile) => {
269                let profile_handle = profile.profile_id.clone();
270                match &self.behaviour.new_contant_policy {
271                    NewContactPolicy::Accept => {
272                        self.cwtch
273                            .accept_conversation(&profile.profile_id, conversation_id);
274                    }
275                    NewContactPolicy::Block => self.cwtch.block_conversation(&profile_handle.clone(), conversation_id),
276                    NewContactPolicy::AllowList => {
277                        match profile.conversations.get(&conversation_id) {
278                            Some(conversation) => {
279                                if self.behaviour.allow_list.peers.contains(&conversation.contact_id) {
280                                    self.cwtch
281                                        .accept_conversation(&profile_handle.clone(), conversation_id);
282                                } else {
283                                    self.cwtch.block_conversation(&profile_handle.clone(), conversation_id);
284                                }
285                            },
286                            None => {},
287                        }
288                    }
289                    NewContactPolicy::Ignore => (),
290                }
291            },
292            None => {},
293        }
294    }
295}