/* * Copyright (C) 2021 Nico Hickman * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * signal-rs-ut is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #![feature(type_alias_impl_trait)] #![feature(async_closure)] #[macro_use] extern crate qmetaobject; use cpp::cpp; use presage::config::*; use presage::prelude::*; use presage::*; use qmetaobject::QAbstractListModel; use qmetaobject::*; use std::ffi::CStr; use std::io::Read; use tokio::sync::mpsc::*; #[allow(non_snake_case)] mod signal; use signal::*; use std::collections::*; mod config; use config::*; use notify_rust::Notification; use std::cell::*; use std::path::PathBuf; use std::result::Result; mod presage_manager; mod registration; use presage_manager::*; #[derive(QObject, Default)] pub struct SignalUI { base: qt_base_class!(trait QObject), state: Option, name: qt_property!(QString; NOTIFY name_changed), name_changed: qt_signal!(), init_state: qt_method!(fn(&mut self)), show_view: qt_method!(fn(&mut self, view: String)), signalResponse: qt_signal!(response: String), chats: qt_property!(RefCell>; NOTIFY chats_changed), messages: qt_property!(RefCell>; NOTIFY msgs_changed), current_messages: qt_property!(String), contacts: qt_property!(RefCell>; NOTIFY contacts_changed), contacts_changed: qt_signal!(), msgs_changed: qt_signal!(), chats_changed: qt_signal!(), show_chat: qt_method!(fn(&self, tel: String)), add_contact: qt_method!(fn(&self, name: String, tel: String)), send_message: qt_method!(fn(&self, to: String, message: String, attachment: String)), edit_contact: qt_method!(fn(&self, name: String, tel: String, id: String)), contact: qt_method!(fn(&self, tel: String) -> String), del_message: qt_method!(fn(&self, chat_id: String, sent_at: i64)), ready: qt_signal!(), group: qt_method!(fn(&self, chat_id: String) -> String), resolve_name: qt_method!(fn(&self, source: String) -> String), avatar: qt_method!(fn(&self, id: String) -> QString), set_avatar: qt_method!(fn(&self, id: String, path: String) -> String), send_group_message: qt_method!(fn(&self, chat_id: String, message: String, attachment: String)), remove_from_group: qt_method!(fn(&self, group_id: String, phone: String)), } impl SignalUI { fn remove_from_group(&self, group_id: String, phone: String) { self.send_req(SignalRequest::RemoveFromGroup { group_id, contact: phone, }); } fn group(&self, chat_id: String) -> String { serde_json::to_string(&self.state.as_ref().unwrap().group(&chat_id).unwrap()).unwrap() } fn send_res(&self, resp: SignalResponse) { self.state .as_ref() .unwrap() .res_sender .send(resp) .expect("Couldn't send SignalResponse"); } fn send_req(&self, reqp: SignalRequest) { self.state .as_ref() .unwrap() .req_sender .send(reqp) .expect("Couldn't send SignalRequest"); } fn del_message(&self, chat_id: String, sent_at: i64) { self.send_res(SignalResponse::RmMessage { chat_id, sent_at }); } fn avatar(&self, id: String) -> QString { QString::from(self.state.as_ref().unwrap().avatar(&id).unwrap_or_default()) } fn set_avatar(&self, id: String, path: String) -> String { let mut data: Vec = vec![]; std::fs::File::open(path) .unwrap() .read_to_end(&mut data) .unwrap(); self.state.as_ref().unwrap().set_avatar(id, &data) } fn contact(&self, tel: String) -> String { let fetched = self .state .as_ref() .unwrap() .contact(&tel) .unwrap_or_default(); serde_json::to_string(&fetched).unwrap() } fn update_chat(&mut self, chat_id: String) { let name = self.resolve_name(chat_id.clone()); if let Some(msgs) = self.state.as_ref().unwrap().messages_id(&chat_id).ok() { if msgs.len() > 0 { let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap(); let mut chats = self.chats.borrow_mut(); for i in 0..chats.row_count() as usize { let mut chat = chats[i].clone(); if chat.tel == chat_id { chat.name = name.clone(); chat.last = latest.message.clone(); chat.timestamp = latest.sent_at; chat.messages = vec![latest.clone()]; chats.remove(i); chats.insert(0, chat); break; } } } } } fn edit_contact(&self, name: String, tel: String, id: String) { self.send_req(SignalRequest::EditContact { name, phone: tel, id, }); } fn send_message(&self, to: String, message: String, attachment: String) { let attachment = match attachment.len() { 0 => None, _ => Some(vec![attachment]), }; self.send_req(SignalRequest::SendMessage { to, message, attachment, }); } fn send_group_message(&self, chat_id: String, message: String, attachment: String) { let attachment = match attachment.len() { 0 => None, _ => Some(vec![attachment]), }; let group = self.state.as_ref().unwrap().group(&chat_id).unwrap(); self.send_req(SignalRequest::SendMessageToGroup { members: group.members, message, attachment, id: chat_id, master_key: group.master_key, }); } fn add_contact(&self, name: String, tel: String) { self.send_req(SignalRequest::EditContact { name, phone: tel, id: "".to_string(), }); } fn show_chat(&mut self, view: String) { self.messages.replace( self.state .as_ref() .unwrap() .messages_id(view.as_str()) .unwrap() .values() .cloned() .rev() .map(|x| QMLSignalMessage::from_msg(x)) .collect(), ); self.msgs_changed(); self.current_messages = view.clone(); if let Some(map) = self.state.as_ref().unwrap().messages_id(&view).ok() { self.state .as_ref() .unwrap() .res_sender .send(SignalResponse::HistoryMessage { phone: view, messages: map.values().cloned().collect(), }) .expect("Couldn't send HistoryMessage"); } } fn init_state(&mut self) { let config: Config = confy::load("signal-rs").expect("Couldn't handle config file"); let qptr = QPointer::from(&*self); let cfg = config.clone(); let process_res = queued_callback(move |res: SignalResponse| { let cfg = cfg.clone(); qptr.as_pinned().map(move |self_| { let ref mut slf = self_.borrow_mut(); slf.state.as_mut().unwrap().process_response(res.clone()); match res { SignalResponse::ChatList(ref chats) => { slf.chats.borrow_mut().reset_data(chats.clone()); slf.chats_changed(); } SignalResponse::ContactList(_) => { slf.update_chats(); } SignalResponse::ShowView(to_show) => { slf.show_view(to_show); } SignalResponse::ModelContactList(contacts) => { slf.contacts.borrow_mut().reset_data(contacts); slf.contacts_changed(); } SignalResponse::AddHist(ref msg, new) => { if new { if msg.chat_id == slf.current_messages { slf.messages .get_mut() .insert(0, QMLSignalMessage::from_msg(msg.clone())); } let cfg = cfg; if cfg.notifications == NotificationPolicy::Enabled { Notification::new() .summary(&slf.resolve_name(msg.source.clone())) .body(msg.message.as_str()) .show() .unwrap(); } slf.update_chat(msg.chat_id.clone()); } } SignalResponse::RmMessage { ref chat_id, sent_at, } => { if &slf.current_messages == chat_id { let ref mut msg_model = slf.messages.get_mut(); let count = msg_model.row_count() as usize; for i in 0..count { if msg_model[i].sent_at == sent_at { msg_model.remove(i); break; } } } println!("Ge"); } SignalResponse::Groups(_) => { let ids = slf .state .as_mut() .unwrap() .msg_store .all() .unwrap() .into_iter() .map(|x| { return x.inner.source; }) .collect::>(); let mut done: HashMap = HashMap::new(); for id in ids { if !done.contains_key(id.as_str()) { done.insert(id.clone(), true); slf.state.as_mut().unwrap().resolve_name(&id).unwrap(); } } println!("Finished initialization"); slf.update_contacts(); slf.show_view("chats".to_string()); slf.ready(); slf.signalResponse(serde_json::to_string(&res).unwrap()); } _ => {} }; }); }); let (res_sender, res_receiver) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); let l_res_sender = res_sender.clone(); let (req_sender, req_receiver) = unbounded_channel::(); self.state = Some(Presage::new( l_res_sender, config.clone(), req_sender.clone(), )); res_receiver.attach(None, move |value| { process_res(value); glib::Continue(true) }); req_sender .send(SignalRequest::GetChatList) .expect("Couldn't send GetChatList"); req_sender .send(SignalRequest::GetGroups) .expect("Couldn't send GetGroups"); res_sender .send(SignalResponse::Groups(vec![])) .expect("Couldn't send Groups"); let cstore_path = PathBuf::from( config .clone() .data_dir .clone() .replace("~", home::home_dir().unwrap().to_str().unwrap()), ); qmetaobject::future::execute_async(async move { Presage::run( presage::config::SledConfigStore::new(cstore_path.join(PathBuf::from("cstore"))) .unwrap(), res_sender, req_receiver, ) .await; }); } /// Shows the given view in the app_box pub fn show_view(&mut self, view: String) { match view.as_ref() { "chats" => { self.update_chats(); } "createChat" => { self.update_contacts(); } _ => {} } } pub fn update_chats(&mut self) { let mut constructed: Vec = vec![]; let mut chats: Vec = self .state .as_ref() .unwrap() .contacts() .values() .enumerate() .filter_map(|(i, contact)| { if let Some(msgs) = self.state.as_ref().unwrap().messages_id(&contact.tel).ok() { if msgs.len() > 0 { let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap(); constructed.push(contact.tel.clone()); return Some(Chat { ID: i as i32, name: contact.name.clone(), tel: contact.tel.clone(), is_group: false, last: latest.message.clone(), timestamp: latest.sent_at, messages: vec![latest.clone()], }); } } None }) .collect(); for (i, (gid, group)) in self .state .as_ref() .unwrap() .groups() .unwrap() .into_iter() .enumerate() { if let Some(msgs) = self.state.as_ref().unwrap().messages_id(&gid).ok() { if msgs.len() > 0 { let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap(); constructed.push(gid.clone()); chats.push(Chat { ID: (chats.len() + i) as i32, name: group.name, tel: gid, is_group: true, last: latest.message.clone(), timestamp: latest.sent_at, messages: vec![latest.clone()], }); } } } for (i, (id, msgs)) in self .state .as_ref() .unwrap() .messages() .unwrap() .into_iter() .filter(|(id, _msgs)| !constructed.contains(&id)) .enumerate() { if msgs.len() > 0 { let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap(); if latest.message.as_str() == "🧝\u{200d}♀\u{fe0f}" { println!("{:?} - {}", latest, id); } chats.push(Chat { ID: (chats.len() + i) as i32, name: self.resolve_name(id.clone()), tel: id, is_group: false, last: latest.message.clone(), timestamp: latest.sent_at, messages: msgs.values().cloned().collect(), }) } } chats.sort_by_key(|c| -1 * c.timestamp); self.send_res(SignalResponse::ChatList(chats)); } pub fn resolve_name(&mut self, source: String) -> String { return self .state .as_mut() .unwrap() .resolve_name(source.as_str()) .unwrap(); } pub fn update_contacts(&self) { let mut contacts: Vec = self .state .as_ref() .unwrap() .contacts() .into_iter() .map(|(_k, v)| v) .collect(); contacts.sort_by_key(|x| x.name.clone()); self.send_res(SignalResponse::ModelContactList(contacts)); } } /// An enum that represents a view that can be displayed. #[derive(Clone, Debug)] pub enum View { Chats, /// Contains the telephone number of the chat to show Messages(String), Password, CreateChat, CreateContact, /// Contains the ID of the contact to edit EditContact(String), Settings, } mod qrc; #[tokio::main] async fn main() -> Result<(), std::boxed::Box> { /*let conf: Config = confy::load("signal-rs").unwrap(); let data_dir = conf .data_dir .replace("~", home::home_dir().unwrap().to_str().unwrap()); let config_store = presage::config::SledConfigStore::new( PathBuf::from(data_dir).join(PathBuf::from("cstore")), ) .unwrap();*/ /*let mut manager = Manager::with_config_store(config_store, ProtocolContext::default()).unwrap(); manager .link_secondary_device( libsignal_service::configuration::SignalServers::Production, "pre".to_string(), ) .await .unwrap();*/ let mut registered = false; unsafe { cpp! { { #include #include }} cpp! ([] { QCoreApplication::setApplicationName(QStringLiteral("signal-rs.nicohman")); QCoreApplication::setOrganizationDomain(QStringLiteral("nicohman.com")); QCoreApplication::setOrganizationName(QStringLiteral("signal-rs.nicohman")); }); } qrc::load(); webengine::initialize(); if registered { qml_register_type::( CStr::from_bytes_with_nul(b"SignalUI\0").unwrap(), 0, 1, CStr::from_bytes_with_nul(b"SignalUI\0").unwrap(), ); let mut engine = QmlEngine::new(); engine.load_file("qrc:/qml/main.qml".into()); engine.exec(); } else { qml_register_type::( CStr::from_bytes_with_nul(b"RegistrationController\0").unwrap(), 0, 1, CStr::from_bytes_with_nul(b"RegistrationController\0").unwrap(), ); let mut engine = QmlEngine::new(); engine.load_file("qrc:/qml/Registration.qml".into()); engine.exec(); } println!("Shutting down"); Ok(()) }