~nicohman/signal-rs

1ce0055f67b42f2d92a54f266a4f7a01dbc0ac6b — nicohman 7 months ago 6edf1d7
Re-add support for local contact avatars, fix several bugs related to UUID cases, add about page & more info context menu option for messages
M Cargo.lock => Cargo.lock +0 -24
@@ 1019,18 1019,6 @@ dependencies = [
]

[[package]]
name = "enum_variant_type"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1bd85a79872123319ccaa668d1d46912d61053e1a6deb7c2c1e392e034f5824"
dependencies = [
 "proc-macro2",
 "proc_macro_roids",
 "quote 1.0.8",
 "syn 1.0.58",
]

[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2816,17 2804,6 @@ dependencies = [
]

[[package]]
name = "proc_macro_roids"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06675fa2c577f52bcf77fbb511123927547d154faa08097cc012c66ec3c9611a"
dependencies = [
 "proc-macro2",
 "quote 1.0.8",
 "syn 1.0.58",
]

[[package]]
name = "procedural-masquerade"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3497,7 3474,6 @@ dependencies = [
 "cpp_macros 0.5.6",
 "crossbeam-channel 0.4.4",
 "cstr",
 "enum_variant_type",
 "futures",
 "futures-util",
 "gettext-rs",

M Cargo.toml => Cargo.toml +0 -1
@@ 17,7 17,6 @@ serde_json = "1.0.57"
mio = { version = "0.7", features = ["tcp", "os-poll"]}
futures-util = { version = "0.3", default-features = true, features = ["async-await", "sink", "std"] }
tokio = { version = "1",features = ["full"] }
enum_variant_type = "0.2.0"
libsignal-service = { git = "https://github.com/gferon/libsignal-service-rs.git", branch = "presage" }
zkgroup = { git = "https://github.com/signalapp/zkgroup" }
chrono = "0.4.6"

M README.md => README.md +0 -11
@@ 1,14 1,3 @@
# signal-rs

A Rust-based signal app with a QML/Kirigami frontend. Uses presage as a backend. Better name pending. Many features also still pending.

## License

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 version 3, as published
by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, 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 <http://www.gnu.org/licenses/>.

A qml/About.qml => qml/About.qml +26 -0
@@ 0,0 1,26 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import SignalUI 0.1
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.12
import org.kde.kirigami 2.13 as Kirigami
Kirigami.AboutPage {
	id: aboutPage
	aboutData: {
		"displayName": "Signal-Rs",
		"productName":"signal-rs",
		"componentName"	:"signal-rs",
		"version":"0.1",
		"shortDescription":"A client for the Signal messaging service",
		"licenseType":"GPL-3.0",
		"copyrightStatement":"Copyright (C) 2021 Nico Hickman",
		"otherText":"",
		"homePageAddress":"https://git.sr.ht/~nicohman/signal-rs",
		"bugAddress":"nicohickman@protonmail.com",
		"authors": [{
			"name":"Nico Hickman",
			"emailAddress":"nicohickman@protonmail.com",
			"webAddress":"https://nicohman.com"
		}]
	}
}
\ No newline at end of file

M qml/Settings.qml => qml/Settings.qml +13 -3
@@ 5,6 5,13 @@ import org.kde.kirigami 2.13 as Kirigami

Kirigami.Page {
    title: "Settings"
    Component.onCompleted: {
            console.log(signal.get_cfg("notifications"));

        if (signal.get_cfg("notifications").toString().trim() === "\"enabled\"") {
            notifCheckBox.checked = true;
        }
    }
    id: settingsPage
    anchors.fill: parent
    ColumnLayout {


@@ 23,10 30,13 @@ Kirigami.Page {
            Layout.alignment: Qt.AlignTop
            CheckBox {
                id: notifCheckBox
                Component.onCompleted: {
                    // TODO: Make actually work
                }

                onClicked: {
                    if (notifCheckBox.checked) {
                        signal.set_cfg("notifications", "enabled");
                    } else {
                        signal.set_cfg("notifications", "disabled");
                    }
                }
            }
        }

M qml/SignalState.qml => qml/SignalState.qml +6 -0
@@ 17,6 17,9 @@ Item {
        signalState.currentView = viewName;
        signal.show_view(viewName);
        switch (viewName) {
            case "about":
                window.pageStack.layers.push(Qt.resolvedUrl("About.qml"));
                break;
            case "chats":
                window.pageStack.push(chatViewStack);
                break;


@@ 67,6 70,9 @@ Item {
    function group(group_id) {
        return JSON.parse(signal.group(group_id));
    }
    function message(sent_at, chat_id) {
        return signal.message(chat_id, sent_at);
    }
    function setCurrent(cur) {
        signalState.current = cur;
        for (var i=0; i < signal.chats.rowCount(); i++) {

M qml/main.qml => qml/main.qml +13 -2
@@ 44,12 44,12 @@ Kirigami.ApplicationWindow {
    background:Rectangle {
        color: theme.background
    }
    BusyIndicator {
    BusyIndicator { 
        running: true
        anchors.centerIn: parent
    }
}

    
    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [


@@ 68,6 68,13 @@ Kirigami.ApplicationWindow {
                }
            },
            Kirigami.Action {
                text: "About"
                icon.name: "help-about-symbolic"
                onTriggered: {
                    signalState.openView("about")
                }
            },
            Kirigami.Action {
                text: i18n("Quit")
                icon.name: "gtk-quit"
                shortcut: StandardKey.Quit


@@ 87,6 94,10 @@ Kirigami.ApplicationWindow {
        id: msgimgover
        MessageImage {}
    }
    MessageInfo {
        id: messageInfo
    }

    Component {
        id: createChatStack
        CreateChat {}

A qml/messages/MessageInfo.qml => qml/messages/MessageInfo.qml +24 -0
@@ 0,0 1,24 @@
import QtQuick 2.15
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami
Kirigami.OverlaySheet {
	id: messageInfo
	property var message: ""
	parent: window.overlay
	function showMessage(chat_id, sent_at) {
		messageInfo.message = signalState.message(sent_at, chat_id);
		messageInfo.open();
		console.log(messageInfo.message);
	}
	header: Kirigami.Heading {
		text: "Message Source"
	}
	TextArea {
		text: messageInfo.message
		readOnly: true
		color: Kirigami.Theme.textColor
		wrapMode: Text.Wrap
		width: parent.width
	}
}
\ No newline at end of file

M qml/messages/MessageUI.qml => qml/messages/MessageUI.qml +7 -1
@@ 63,6 63,12 @@ ItemDelegate {
                        }
                        text: "Delete"
                    }
                    MenuItem {
                        onClicked: {
                            messageInfo.showMessage(message.message.chat_id, message.message.sent_at);
                        }
                        text: "More Info"
                    }
                }
            }
            id: rectangle


@@ 189,7 195,7 @@ ItemDelegate {
                                    text: emoji
                                }
                            }
                }
                        }
                    }
                }
            }

M src/config.rs => src/config.rs +1 -10
@@ 10,6 10,7 @@ fn default_data() -> String {
    return "~/.config/signal-rs-data".to_string();
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NotificationPolicy {
    Enabled,
    Disabled,


@@ 19,13 20,3 @@ impl Default for NotificationPolicy {
        NotificationPolicy::Disabled
    }
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Theme {
    System,
    Signal,
}
impl Default for Theme {
    fn default() -> Self {
        Theme::System
    }
}

M src/main.rs => src/main.rs +64 -18
@@ 5,7 5,7 @@
 * 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,
 * signal-rs 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.


@@ 13,10 13,7 @@
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#![feature(type_alias_impl_trait)]
#![feature(async_closure)]
#[macro_use]
extern crate qmetaobject;
use cpp::cpp;
use presage::config::*;
use presage::prelude::*;


@@ 31,8 28,10 @@ mod signal;
use signal::*;
use std::collections::*;
mod config;
mod util;
use config::*;
use notify_rust::Notification;
use serde_json::json;
use std::cell::*;
use std::path::PathBuf;
use std::result::Result;


@@ 62,12 61,16 @@ pub struct SignalUI {
    contact: qt_method!(fn(&self, tel: String) -> String),
    del_message: qt_method!(fn(&self, chat_id: String, sent_at: i64)),
    ready: qt_signal!(),
    message: qt_method!(fn(&self, chat_id: String, sent_at: i64) -> String),
    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)),
    get_cfg: qt_method!(fn(&self, key: String) -> String),
    set_cfg: qt_method!(fn(&mut self, key: String, value: String)),
    config: Option<Config>,
}
impl SignalUI {
    fn remove_from_group(&self, group_id: String, phone: String) {


@@ 77,10 80,52 @@ impl SignalUI {
        });
    }

    fn get_cfg(&self, key: String) -> String {
        match key.as_str() {
            "notifications" => {
                return serde_json::to_string(&self.config.as_ref().unwrap().notifications)
                    .unwrap();
            }
            _ => {
                println!("Unrecognized config key: {}", key);
                return "".into();
            }
        }
    }

    fn set_cfg(&mut self, key: String, value: String) {
        self.config = self.config.clone().map(|mut cfg| {
            match key.as_str() {
                "notifications" => {
                    cfg.notifications = serde_json::from_value(json!(value))
                        .expect("Couldn't parse notifications policy value");
                }
                _ => {
                    println!("Unrecognized config key: {}", key);
                }
            }
            confy::store("signal-rs", cfg.clone()).expect("Couldn't store config");
            return cfg;
        });
    }

    fn group(&self, chat_id: String) -> String {
        serde_json::to_string(&self.state.as_ref().unwrap().group(&chat_id).unwrap()).unwrap()
    }

    fn message(&self, chat_id: String, sent_at: i64) -> String {
        if let Ok(msg) = self
            .state
            .as_ref()
            .unwrap()
            .message(chat_id.as_str(), sent_at)
        {
            serde_json::to_string_pretty(&msg).unwrap()
        } else {
            "".into()
        }
    }

    fn send_res(&self, resp: SignalResponse) {
        self.state
            .as_ref()


@@ 222,9 267,8 @@ impl SignalUI {
    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();
        self.config.replace(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());


@@ 236,9 280,6 @@ impl SignalUI {
                    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();


@@ 250,8 291,9 @@ impl SignalUI {
                                    .get_mut()
                                    .insert(0, QMLSignalMessage::from_msg(msg.clone()));
                            }
                            let cfg = cfg;
                            if cfg.notifications == NotificationPolicy::Enabled {
                            if slf.config.as_ref().unwrap().notifications
                                == NotificationPolicy::Enabled
                            {
                                Notification::new()
                                    .summary(&slf.resolve_name(msg.source.clone()))
                                    .body(msg.message.as_str())


@@ 312,11 354,10 @@ impl SignalUI {
        let l_res_sender = res_sender.clone();
        let (req_sender, req_receiver) = unbounded_channel::<SignalRequest>();

        self.state = Some(Presage::new(
            l_res_sender,
            config.clone(),
            req_sender.clone(),
        ));
        self.state = Some(
            Presage::new(l_res_sender, config.clone(), req_sender.clone())
                .expect("Couldn't create Presage"),
        );
        res_receiver.attach(None, move |value| {
            process_res(value);
            glib::Continue(true)


@@ 398,8 439,12 @@ impl SignalUI {
            .into_iter()
            .enumerate()
        {
            println!("{}", gid);
            println!("{:?}", group);
            if let Some(msgs) = self.state.as_ref().unwrap().messages_id(&gid).ok() {
                println!("GROUP2");
                if msgs.len() > 0 {
                    println!("GROUP");
                    let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                    constructed.push(gid.clone());



@@ 426,6 471,7 @@ impl SignalUI {
            .enumerate()
        {
            if msgs.len() > 0 {
                println!("{}", id);
                let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                if latest.message.as_str() == "🧝\u{200d}♀\u{fe0f}" {
                    println!("{:?} - {}", latest, id);


@@ 501,7 547,7 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
        )
        .await
        .unwrap();*/
    let mut registered = false;
    let mut registered = true;
    unsafe {
        cpp! { {
            #include <QtCore/QCoreApplication>


@@ 514,7 560,6 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
        });
    }
    qrc::load();
    webengine::initialize();
    if registered {
        qml_register_type::<SignalUI>(
            CStr::from_bytes_with_nul(b"SignalUI\0").unwrap(),


@@ 527,6 572,7 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
        engine.load_file("qrc:/qml/main.qml".into());
        engine.exec();
    } else {
        webengine::initialize();
        qml_register_type::<registration::RegistrationController>(
            CStr::from_bytes_with_nul(b"RegistrationController\0").unwrap(),
            0,

M src/presage_manager.rs => src/presage_manager.rs +213 -172
@@ 1,11 1,12 @@
use crate::util::*;
use crate::*;
use anyhow::{Context, Result};
use futures_util::StreamExt;
use pallet::Document;
use pallet::Store;
use std::fs;
use std::io::Write;
use std::path::Path;
use zkgroup::api::groups::group_params::*;
pub struct Presage {
    pub msg_store: Store<SignalMessage>,
    pub contact_store: Store<Contact>,


@@ 13,6 14,7 @@ pub struct Presage {
    pub req_sender: UnboundedSender<SignalRequest>,
    pub whoami: Option<Uuid>,
    pub res_sender: glib::Sender<SignalResponse>,
    pub data_dir: String,
}
impl Presage {
    pub async fn run(


@@ 23,7 25,13 @@ impl Presage {
        let (tx, mut rx) = futures::channel::mpsc::channel::<(Metadata, ContentBody)>(100);
        let mut manager = Manager::with_config_store(config_store, ProtocolContext::default())
            .expect("Couldn't create Manager");
        let whoami = manager.whoami().await.unwrap().uuid.to_string();
        let whoami = manager
            .whoami()
            .await
            .unwrap()
            .uuid
            .to_string()
            .to_uppercase();
        let cloned_man = manager.clone();
        let x = cloned_man.receive_messages(tx);
        let mut fut = Box::pin(x);


@@ 44,11 52,11 @@ impl Presage {
                                    continue;
                                }
                            res_sender.send(SignalResponse::AddReaction{
                                target: react.target_author_uuid.unwrap().clone(),
                                target: react.target_author_uuid.unwrap().clone().to_uppercase(),
                                emoji: react.emoji.unwrap(),
                                target_timestamp: react.target_sent_timestamp.unwrap() as i64,
                                timestamp: response.0.timestamp as i64,
                                from: response.0.sender.identifier()
                                from: response.0.sender.identifier().to_uppercase()
                            }).expect("Couldn't send AddReaction");
                            }
                        }


@@ 57,9 65,9 @@ impl Presage {
                        let mut chat_id = response
                            .0
                            .sender
                            .identifier();
                            .identifier().to_uppercase();
                        if let Some(gc) = dm.group {
                            chat_id = Presage::get_gid(gc.id.unwrap());
                            chat_id = get_gid(gc.id.unwrap());
                            let g = Group {
                                name: gc.name.unwrap_or_default(),
                                members: gc.members_e164,


@@ 69,7 77,7 @@ impl Presage {
                            res_sender.send(SignalResponse::SGroup(g)).expect("Couldn't send SGroup");
                        }
                        if let Some(gc2) = dm.group_v2 {
                            let gid = Presage::get_gid(gc2.master_key.clone().unwrap());
                            let gid = get_gid_v2(gc2.master_key.clone().unwrap());
                            chat_id = gid;
                            res_sender.send(SignalResponse::GroupV2Context(gc2)).expect("Couldn't send GroupV2Context");
                        }


@@ 84,7 92,7 @@ impl Presage {
                            message: body,
                            reactions: vec![],
                            attachment: Some(vec![]),
                            outgoing: response.0.sender.identifier() == whoami,
                            outgoing: response.0.sender.identifier().to_uppercase() == whoami,
                        };
                        res_sender.send(SignalResponse::IDLessMessage(msg)).expect("Couldn't send IDLessMessage");
                    } else {


@@ 101,11 109,11 @@ impl Presage {
                                    continue;
                                }
                            res_sender.send(SignalResponse::AddReaction{
                                target: react.target_author_uuid.unwrap().clone(),
                                target: react.target_author_uuid.unwrap().clone().to_uppercase(),
                                emoji: react.emoji.unwrap(),
                                target_timestamp: react.target_sent_timestamp.unwrap() as i64,
                                timestamp: response.0.timestamp as i64,
                                from: response.0.sender.identifier()
                                from: response.0.sender.identifier().to_uppercase()
                            }).expect("Couldn't send AddReaction");
                            }



@@ 115,9 123,9 @@ impl Presage {
                            if let Some(body) = dm.body {
                                let mut chat_id = sent
                                    .destination_uuid.
                                    unwrap_or(sent.destination_e164.unwrap_or_default());
                                    unwrap_or(sent.destination_e164.unwrap_or_default()).to_uppercase();
                                if let Some(gc) = dm.group {
                                    chat_id = Presage::get_gid(gc.id.unwrap());
                                    chat_id = get_gid(gc.id.unwrap());
                                                                let g = Group {
                                name: gc.name.unwrap_or_default(),
                                members: gc.members_e164,


@@ 127,21 135,21 @@ impl Presage {
                            res_sender.send(SignalResponse::SGroup(g)).expect("Couldn't send SGroup");
                                }
                                if let Some(gc2) = dm.group_v2 {
                                    let gid = Presage::get_gid_v2(gc2.master_key.clone().unwrap());
                                    let gid = get_gid_v2(gc2.master_key.clone().unwrap());
                                    chat_id = gid;
                                    res_sender.send(SignalResponse::GroupV2Context(gc2)).expect("Couldn't send GroupV2Context");
                                }
                                let msg = SignalMessage {
                                    source: response
                                        .0
                                        .sender.identifier(),
                                        .sender.identifier().to_uppercase(),
                                    chat_id: chat_id,
                                    sent_at: dm.timestamp.unwrap() as i64,
                                    ID: 0,
                                    message: body,
                                    reactions: vec![],
                                    attachment: Some(vec![]),
                                    outgoing: response.0.sender.identifier() == whoami,
                                    outgoing: response.0.sender.identifier().to_uppercase() == whoami,
                                };
                                res_sender.send(SignalResponse::IDLessMessage(msg)).expect("Couldn't send IDLessMessage");
                            }


@@ 174,7 182,7 @@ impl Presage {
                                }).collect::<Vec<ServiceAddress>>(),  msg, now).await.unwrap();
                                let sm = SignalResponse::IDLessMessage(SignalMessage {
                                    message: message,
                                    source: whoami.clone(),
                                    source: whoami.clone().to_uppercase(),
                                    chat_id: id,
                                    outgoing: true,
                                    attachment: None,


@@ 195,8 203,8 @@ impl Presage {
                                manager.send_message(tos.clone(), msg, now).await.unwrap();
                                let sm = SignalResponse::IDLessMessage(SignalMessage {
                                    message: message,
                                    source: to.clone(),
                                    chat_id: to.clone(),
                                    source: to.clone().to_uppercase(),
                                    chat_id: to.clone().to_uppercase(),
                                    outgoing: true,
                                    attachment: None,
                                    sent_at: now as i64,


@@ 210,15 218,15 @@ impl Presage {
                                res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: phone, uuid: id}])).expect("Couldn't send ContactList");
                            },
                            SignalRequest::GetGroupCtxV2(ctx) => {
                                let group = manager.get_group_v2(Presage::make_gkey(ctx.master_key.clone().unwrap())).await.unwrap();
                                let group = manager.get_group_v2(make_gkey(ctx.master_key.clone().unwrap())).await.unwrap();
                                println!("{:?}", group);
                                let gid = Presage::get_gid_v2(ctx.master_key.clone().unwrap());
                                let gid = get_gid_v2(ctx.master_key.clone().unwrap());
                                res_sender.send(SignalResponse::SGroup(Group {
                                    name: group.title,
                                    id: gid,
                                    master_key: ctx.master_key.clone(),
                                    members: group.members.clone().into_iter().map(|x| {
                                        return Uuid::from_slice(&x.uuid).unwrap().to_string();
                                        return Uuid::from_slice(&x.uuid).unwrap().to_string().to_uppercase();
                                    }).collect()
                                })).expect("Couldn't send SGroup");
                            },


@@ 235,52 243,40 @@ impl Presage {
        }
    }

    pub fn make_gkey(bytes: Vec<u8>) -> GroupMasterKey {
        let mut slice: [u8; 32] = Default::default();
        slice.copy_from_slice(bytes.as_slice());
        GroupMasterKey::new(slice)
    }

    // Makes a new Presage struct.
    pub fn new(
        res_sender: glib::Sender<SignalResponse>,
        config: Config,
        req_sender: UnboundedSender<SignalRequest>,
    ) -> Presage {
    ) -> Result<Presage> {
        let data_dir = config
            .data_dir
            .replace("~", home::home_dir().unwrap().to_str().unwrap());
        let m_db = pallet::ext::sled::open(Path::new(&data_dir).join("messages").to_str().unwrap())
            .unwrap();
        let c_db = pallet::ext::sled::open(Path::new(&data_dir).join("contacts").to_str().unwrap())
            .unwrap();
        let g_db =
            pallet::ext::sled::open(Path::new(&data_dir).join("groups").to_str().unwrap()).unwrap();
        fs::create_dir("/tmp/mdb");
        fs::create_dir("/tmp/cdb");
        fs::create_dir("/tmp/gdb");
        // Open, index and setup stores for messages/contacts/groups/avatars
        fs::create_dir_all(Path::new(&data_dir).join("avatars"))?;
        let m_db =
            pallet::ext::sled::open(Path::new(&data_dir).join("messages").to_str().unwrap())?;
        let c_db =
            pallet::ext::sled::open(Path::new(&data_dir).join("contacts").to_str().unwrap())?;
        let g_db = pallet::ext::sled::open(Path::new(&data_dir).join("groups").to_str().unwrap())?;
        fs::create_dir_all("/tmp/mdb")?;
        fs::create_dir_all("/tmp/cdb")?;
        fs::create_dir_all("/tmp/gdb")?;
        let msg_store = pallet::Store::builder()
            .with_db(m_db)
            .with_index_dir("/tmp/mdb")
            .finish()
            .unwrap();
        msg_store.index_all().expect("Couldn't index message store");
            .finish()?;
        msg_store.index_all()?;
        let contact_store = pallet::Store::builder()
            .with_db(c_db)
            .with_index_dir("/tmp/cdb")
            .finish()
            .unwrap();
        contact_store
            .index_all()
            .expect("Couldn't index contact store");

            .finish()?;
        contact_store.index_all()?;
        let group_store = pallet::Store::builder()
            .with_db(g_db)
            .with_index_dir("/tmp/gdb")
            .finish()
            .unwrap();
        group_store.index_all().expect("Couldn't index group store");

        //let uuid = manager.whoami().await.unwrap().uuid;
            .finish()?;
        group_store.index_all()?;
        let pre = Presage {
            msg_store,
            contact_store,


@@ 288,15 284,17 @@ impl Presage {
            whoami: None,
            res_sender,
            req_sender,
            data_dir,
        };
        pre.contact_store.all().unwrap().into_iter().for_each(|c| {
        // Converts all old messages stored by phone # into uuid. Probably not necessary anymore
        pre.contact_store.all()?.into_iter().for_each(|c| {
            if c.uuid == String::new() || c.tel == String::new() {
                return;
            }
            if let Ok(msgs) = pre.messages_id_doc(c.tel.as_str()) {
                let msgs = msgs
                    .into_iter()
                    .map(|(i, mut d)| {
                    .map(|(_i, mut d)| {
                        d.chat_id = c.uuid.clone();
                        return d;
                    })


@@ 304,30 302,66 @@ impl Presage {
                pre.msg_store.update_multi(&msgs).unwrap();
            }
        });
        return pre;
        pre.contact_store
            .all()
            .unwrap()
            .into_iter()
            .for_each(|mut c| {
                c.inner.uuid = c.inner.uuid.to_uppercase();
                pre.contact_store.update(&c).unwrap();
            });
        pre.msg_store
            .all()
            .unwrap()
            .into_iter()
            .for_each(|mut msg| {
                msg.inner.chat_id = msg.inner.chat_id.to_uppercase();
                msg.inner.source = msg.inner.source.to_uppercase();
                pre.msg_store.update(&msg).unwrap();
            });
        pre.group_store
            .all()
            .unwrap()
            .into_iter()
            .for_each(|mut g| {
                g.inner.id = g.inner.id.to_uppercase();
                pre.group_store.update(&g).unwrap();
            });
        return Ok(pre);
    }

    // TODO
    pub fn avatar(&self, id: &str) -> Option<String> {
        let opts = vec![
            "profile".to_string(),
            "contact".to_string(),
            "group".to_string(),
        ];
        let av_dir = Path::new(self.data_dir.as_str()).join("avatars");
        for opt in opts {
            let now_path = av_dir.join(format!("{}-{}", opt, id));
            if now_path.exists() {
                return Some(now_path.to_str().unwrap().to_string());
            }
        }
        None
    }

    // TODO
    pub fn set_avatar(&self, id: String, data: &[u8]) -> String {
        return String::new();
    }

    pub fn get_gid_v2(master_key: Vec<u8>) -> String {
        let mut ar: [u8; 32] = Default::default();
        ar.copy_from_slice(&master_key);
        let secret_params = GroupSecretParams::derive_from_master_key(GroupMasterKey::new(ar));
        let gid = secret_params.get_group_identifier();
        let dec = base64::encode(gid);
        return dec;
    }

    pub fn get_gid(id: Vec<u8>) -> String {
        return base64::encode(id);
        let path = Path::new(self.data_dir.as_str())
            .join("avatars")
            .join(format!("contact-{}", id));
        let mut fd = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(&path)
            .expect("Couldn't create avatar file");
        fd.write_all(data).expect("Couldn't write to avatar file");
        return path.into_os_string().into_string().unwrap();
    }

    // Adds a message to store and updates GUI
    pub fn add_message(&mut self, message: SignalMessage, new: bool) {
        if self.message(&message.chat_id, message.sent_at).is_err() {
            println!("creating");


@@ 340,6 374,7 @@ impl Presage {
        }
    }

    // Processes a SignalResponse enum and makes appropriate changes to manager state/UI state
    pub fn process_response(&mut self, response: SignalResponse) {
        match response {
            SignalResponse::RmMessage { chat_id, sent_at } => {


@@ 352,8 387,8 @@ impl Presage {
            SignalResponse::Metadata(meta) => {
                if let Ok(mut cont) = self.contact_doc(meta.sender.identifier().as_str()) {
                    if let Some(uuid) = meta.sender.uuid {
                        if uuid.to_string() != cont.inner.uuid {
                            cont.inner.uuid = uuid.to_string();
                        if uuid.to_string().to_uppercase() != cont.inner.uuid {
                            cont.inner.uuid = uuid.to_string().to_uppercase();
                            self.contact_store
                                .update(&cont)
                                .expect("Couldn't update contact");


@@ 372,11 407,17 @@ impl Presage {
                        .sender
                        .e164()
                        .map(|x| x.to_string())
                        .unwrap_or(meta.sender.uuid.unwrap().to_string());
                        .unwrap_or(meta.sender.uuid.unwrap().to_string())
                        .to_uppercase();
                    let contact = Contact {
                        name,
                        tel: meta.sender.e164().unwrap_or_default().to_string(),
                        uuid: meta.sender.uuid.unwrap_or_default().to_string(),
                        uuid: meta
                            .sender
                            .uuid
                            .unwrap_or_default()
                            .to_string()
                            .to_uppercase(),
                    };
                    self.contact_store.create(&contact).unwrap();
                }


@@ 398,17 439,8 @@ impl Presage {
                        .expect("Couldn't delete contact");
                }
            }
            /*SignalResponse::Groups(groups) => {
                for group in groups.into_iter() {
                    if let Some(group_p) = self.group(group.id.as_str()) {
                        *group_p = group;
                    } else {
                        self.groups.insert(group.id.clone(), group);
                    }
                }
            }*/
            SignalResponse::GroupV2Context(ctx) => {
                let id = Presage::get_gid_v2(ctx.master_key.clone().unwrap());
                let id = get_gid_v2(ctx.master_key.clone().unwrap());
                if ctx.group_change.is_some() || self.group_doc(id.as_str()).is_err() || true {
                    self.req_sender
                        .send(SignalRequest::GetGroupCtxV2(ctx))


@@ 416,6 448,7 @@ impl Presage {
                }
            }
            SignalResponse::SGroup(group) => {
                println!("{:?}", group);
                if let Ok(mut g) = self.group_doc(group.id.as_str()) {
                    g.inner = group;
                    self.group_store.update(&g).expect("Couldn't update group");


@@ 470,77 503,7 @@ impl Presage {
        }
    }

    pub fn messages_id<'a>(&'a self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
        let messages = self
            .msg_store
            .search(format!("+chat_id:\"{}\"", chat_id).as_str())?
            .hits;
        Ok(messages
            .into_iter()
            .map(|m| {
                let m = m.doc.inner;
                (m.sent_at, m)
            })
            .collect())
    }

    pub fn messages_id_doc<'a>(
        &'a self,
        chat_id: &str,
    ) -> Result<BTreeMap<i64, Document<SignalMessage>>> {
        let messages = self
            .msg_store
            .search(format!("+chat_id:\"{}\"", chat_id).as_str())?
            .hits;
        Ok(messages
            .into_iter()
            .map(|m| {
                let m = m.doc;
                (m.sent_at, m)
            })
            .collect())
    }

    fn message_doc<'a>(
        &'a self,
        chat_id: &str,
        sent_at: i64,
    ) -> Result<Document<SignalMessage>, anyhow::Error> {
        Ok(self
            .msg_store
            .search(format!("chat_id:{} AND sent_at:{}", chat_id, sent_at).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .clone())
    }

    pub fn group_doc(&self, chat_id: &str) -> Result<Document<Group>> {
        Ok(self
            .group_store
            .search(format!("id:{}", chat_id).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .clone())
    }

    fn contact_doc<'a>(&'a self, tel: &str) -> Result<Document<Contact>, anyhow::Error> {
        Ok(self
            .contact_store
            .search(format!("tel:{} OR uuid:{}", tel, tel).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .clone())
    }

    // Given a chat id of any kind, resolve a relevant name. E.g. returns a contact's name/group name/etc. Returns the id if no name can be found
    pub fn resolve_name(&mut self, chat_id: &str) -> Result<String> {
        if let Some(contact) = self.contact(chat_id.clone()).ok() {
            return Ok(contact.name);


@@ 551,8 514,9 @@ impl Presage {
        }
    }

    pub fn messages<'a>(
        &'a self,
    // Returns all messages, sorted by chat id and then timestamp
    pub fn messages(
        &self,
    ) -> Result<BTreeMap<String, BTreeMap<i64, SignalMessage>>, anyhow::Error> {
        let mut map: BTreeMap<String, BTreeMap<i64, SignalMessage>> = BTreeMap::new();
        for msg in self.msg_store.all()? {


@@ 568,7 532,8 @@ impl Presage {
        Ok(map)
    }

    pub fn message<'a>(&'a self, chat_id: &str, sent_at: i64) -> Result<SignalMessage> {
    // Returns any message with the given chat_id and timestamp
    pub fn message(&self, chat_id: &str, sent_at: i64) -> Result<SignalMessage> {
        Ok(self
            .msg_store
            .search(format!("chat_id:{} AND sent_at:{}", chat_id, sent_at).as_str())?


@@ 581,32 546,54 @@ impl Presage {
            .clone())
    }

    pub fn contact<'a>(&'a self, tel: &str) -> Result<Contact> {
    // Returns a message with a chat_id and timestamp, as a Document
    fn message_doc(
        &self,
        chat_id: &str,
        sent_at: i64,
    ) -> Result<Document<SignalMessage>, anyhow::Error> {
        Ok(self
            .contact_store
            .search(format!("uuid:{} OR tel:{}", tel, tel).as_str())?
            .msg_store
            .search(format!("chat_id:{} AND sent_at:{}", chat_id, sent_at).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .inner
            .clone())
    }

    pub fn contact_tel<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self
            .contact_store
            .search(format!("tel:{}", tel).as_str())?
            .hits
    // Returns all messages with a given chat_id in a BTreeMap, keyed by timestamp
    pub fn messages_id(&self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
        let messages = self
            .msg_store
            .search(format!("+chat_id:\"{}\"", chat_id).as_str())?
            .hits;
        Ok(messages
            .into_iter()
            .next()
            .context("none")?
            .doc
            .inner
            .clone())
            .map(|m| {
                let m = m.doc.inner;
                (m.sent_at, m)
            })
            .collect())
    }

    // Returns all messages with a given chat_id in a BTreeMap, keyed by timestamp. Includes the pallet Document for further operations
    pub fn messages_id_doc(&self, chat_id: &str) -> Result<BTreeMap<i64, Document<SignalMessage>>> {
        let messages = self
            .msg_store
            .search(format!("+chat_id:\"{}\"", chat_id).as_str())?
            .hits;
        Ok(messages
            .into_iter()
            .map(|m| {
                let m = m.doc;
                (m.sent_at, m)
            })
            .collect())
    }

    // Returns all groups in a BTreeMap, keyed by id
    pub fn groups<'a>(&'a self) -> Result<HashMap<String, Group>> {
        Ok(self
            .group_store


@@ 619,6 606,7 @@ impl Presage {
            .collect())
    }

    // Returns a group with the given id
    pub fn group(&self, chat_id: &str) -> Result<Group> {
        Ok(self
            .group_store


@@ 632,6 620,20 @@ impl Presage {
            .clone())
    }

    // Returns a group with the given chat_id, including Document
    pub fn group_doc(&self, chat_id: &str) -> Result<Document<Group>> {
        Ok(self
            .group_store
            .search(format!("id:{}", chat_id).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .clone())
    }

    // Returns all contacts, keyed by uuid
    pub fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
        let map: HashMap<String, Contact> = self
            .contact_store


@@ 640,10 642,49 @@ impl Presage {
            .into_iter()
            .map(|x| {
                let x = x.inner;
                (x.tel.clone(), x)
                (x.uuid.clone(), x)
            })
            .collect();
        println!("{}", map.len());
        map
    }

    // Returns any contact with the given string as either tel or uuid
    pub fn contact(&self, search: &str) -> Result<Contact> {
        Ok(self
            .contact_store
            .search(format!("uuid:{} OR tel:{}", search, search).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .inner
            .clone())
    }

    fn contact_tel<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self
            .contact_store
            .search(format!("tel:{}", tel).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .inner
            .clone())
    }

    // Returns a contact with the given string as either phone # or uuid. Includes Document
    fn contact_doc(&self, search: &str) -> Result<Document<Contact>, anyhow::Error> {
        Ok(self
            .contact_store
            .search(format!("tel:{} OR uuid:{}", search, search).as_str())?
            .hits
            .into_iter()
            .next()
            .context("none")?
            .doc
            .clone())
    }
}

M src/qrc.rs => src/qrc.rs +3 -1
@@ 21,7 21,9 @@ qrc!(qml_resources,
        "qml/registration/RegistrationNew.qml" as "qml/RegistrationNew.qml",
        "qml/registration/CaptchaWindow.qml" as "qml/CaptchaWindow.qml",
        "qml/registration/RegistrationComplete.qml" as "qml/RegistrationComplete.qml",
        "qml/registration/VerificationCode.qml" as "qml/VerificationCode.qml"
        "qml/registration/VerificationCode.qml" as "qml/VerificationCode.qml",
        "qml/About.qml",
        "qml/messages/MessageInfo.qml" as "qml/MessageInfo.qml"
    },
);


M src/registration.rs => src/registration.rs +13 -4
@@ 1,7 1,7 @@
use std::str::FromStr;

use crate::*;
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::channel::mpsc::{unbounded, UnboundedSender};
use futures::sink::SinkExt;
use futures::stream::StreamExt;
pub enum RegReq {


@@ 123,21 123,30 @@ impl RegistrationController {
    pub fn register(&mut self, number: String, voice: bool, captcha: Option<String>) {
        let mut sender = self.req_sender.as_ref().unwrap().clone();
        qmetaobject::execute_async(async move {
            sender.send(RegReq::Register(number, voice, captcha)).await;
            sender
                .send(RegReq::Register(number, voice, captcha))
                .await
                .expect("Couldn't send RegReq");
        });
    }

    pub fn register_secondary(&mut self, name: String) {
        let mut sender = self.req_sender.as_ref().unwrap().clone();
        qmetaobject::execute_async(async move {
            sender.send(RegReq::Secondary(name)).await;
            sender
                .send(RegReq::Secondary(name))
                .await
                .expect("Couldn't send RegReq");
        });
    }

    pub fn confirm_code(&mut self, code: String) {
        let mut sender = self.req_sender.as_ref().unwrap().clone();
        qmetaobject::execute_async(async move {
            sender.send(RegReq::Code(code)).await;
            sender
                .send(RegReq::Code(code))
                .await
                .expect("Couldn't send RegReq");
        });
    }
}

M src/signal.rs => src/signal.rs +2 -125
@@ 1,115 1,11 @@
use enum_variant_type::EnumVariantType;
use pallet::DocumentLike;

use presage::prelude::*;

use qmetaobject::*;
use regex::Regex;

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_repr::*;
lazy_static! {
    static ref CONTACT_REG: Regex =
        Regex::new(r"Number: (\+[0-9]+) Name: (\S+)  Blocked:").unwrap();
}
use signal_cli::*;
pub mod signal_cli {
    use crate::signal::*;
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct SignalCLIContainer {
        pub envelope: SignalCLIMessage,
    }
    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct SignalCLIMessage {
        pub source: String,
        pub source_device: u8,
        pub timestamp: i64,
        pub message: Option<String>,
        pub is_read: Option<bool>,
        pub is_delivery: Option<bool>,
        pub data_message: Option<DataMessage>,
        pub sync_message: Option<SyncMessage>,
        pub typing_message: Option<TypingMessage>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "UPPERCASE")]
    pub enum TypingType {
        Started,
        Stopped,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct TypingMessage {
        pub action: TypingType,
        pub timestamp: i64,
    }
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct SignalCLIContactInfo {
        pub number: String,
        pub name: String,
    }
    impl SignalCLIContactInfo {
        pub fn from_str(sti: impl Into<String>) -> Option<SignalCLIContactInfo> {
            if let Some(cap) = CONTACT_REG.captures(&sti.into()) {
                return Some(SignalCLIContactInfo {
                    number: cap[1].to_string(),
                    name: cap[2].to_string(),
                });
            }
            None
        }
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct SyncMessage {
        pub sent_message: Option<SentMessage>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct SentMessage {
        pub timestamp: u64,
        pub message: Option<String>,
        pub reaction: Option<SCLIReaction>,
        pub expires_in_seconds: u64,
        pub attachments: Option<Vec<SignalCLIAttachment>>,
        pub group_info: Option<GroupInfo>,
        pub destination: Option<String>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct SCLIReaction {
        pub emoji: String,
        pub target_author: String,
        pub is_remove: bool,
        pub target_sent_timestamp: i64,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct DataMessage {
        pub timestamp: u64,
        pub message: Option<String>,
        pub expires_in_seconds: u64,
        pub attachments: Option<Vec<SignalCLIAttachment>>,
        pub group_info: Option<GroupInfo>,
        pub destination: Option<String>,
        pub reaction: Option<SCLIReaction>,
    }

    #[derive(Serialize, Deserialize, Clone, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct SignalCLIAttachment {
        pub content_type: String,
        pub filename: Option<String>,
        pub id: String,
        pub size: u64,
    }
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "request")]


@@ 191,34 87,18 @@ pub struct Reaction {
    pub timestamp: i64,
    pub source: String,
}
#[derive(Serialize, Deserialize, Clone, EnumVariantType, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum SignalResponse {
    #[serde(skip)]
    Whoami(String),
    SGroup(Group),
    #[serde(skip)]
    Metadata(presage::prelude::Metadata),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    ContactList(Vec<Contact>),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    ModelContactList(Vec<Contact>),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    ChatList(Vec<Chat>),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    MessageList(SignalMessageList),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    SignalCLIEnvelope(SignalCLIMessage),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    MoreMessageList(SignalMessageList),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    #[serde(rename = "MessageRecieved")]
    MessageReceived(SignalMessage),
    #[evt(derive(Clone, Debug))]
    ShowView(String),
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]
    Type(String),
    AddHist(SignalMessage, bool),
    ShowTheme(crate::config::Theme),
    IDLessMessage(SignalMessage),
    RmContact(String),
    Quit,


@@ 413,9 293,6 @@ impl PartialEq for SignalMessage {
        self.ID == other.ID && self.message == other.message
    }
}
trait SignalResponseTrait {}
impl SignalResponseTrait for SignalResponse {}

#[derive(Serialize, Deserialize, Clone, Debug, DocumentLike, Default, SimpleListItem)]
#[serde(rename_all = "PascalCase")]
#[pallet(tree_name = "contacts")]

A src/util.rs => src/util.rs +20 -0
@@ 0,0 1,20 @@
use libsignal_service::prelude::*;
// When given a group master key, derive secret params and return the identifier
pub fn get_gid_v2(master_key: Vec<u8>) -> String {
    let secret_params = GroupSecretParams::derive_from_master_key(make_gkey(master_key));
    let gid = secret_params.get_group_identifier();
    let dec = base64::encode(gid);
    return dec.to_uppercase();
}

// Returns the string form of a group id v1 in bytes
pub fn get_gid(id: Vec<u8>) -> String {
    return base64::encode(id).to_uppercase();
}

// Takes a vec of bytes and returns a constructed GroupMasterKey struct
pub fn make_gkey(bytes: Vec<u8>) -> GroupMasterKey {
    let mut slice: [u8; 32] = Default::default();
    slice.copy_from_slice(bytes.as_slice());
    GroupMasterKey::new(slice)
}