~nicohman/signal-rs

ec446ee25535febb1f0f5b0823c4d26d9bf53486 — nicohman 7 months ago f0e05ca
First work on registration
M Cargo.lock => Cargo.lock +54 -48
@@ 2508,7 2508,7 @@ dependencies = [
 "pallet-macros",
 "rayon",
 "serde",
 "sled 0.34.6",
 "sled",
 "tantivy 0.14.0",
 "thiserror",
]


@@ 2537,16 2537,6 @@ dependencies = [

[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
 "lock_api 0.3.4",
 "parking_lot_core 0.7.2",
]

[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"


@@ 2573,20 2563,6 @@ dependencies = [

[[package]]
name = "parking_lot_core"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
 "cfg-if 0.1.10",
 "cloudabi",
 "libc",
 "redox_syscall 0.1.57",
 "smallvec 1.6.1",
 "winapi 0.3.9",
]

[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"


@@ 2606,6 2582,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"

[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
 "ucd-trie",
]

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


@@ 2770,7 2755,7 @@ dependencies = [
 "rand 0.7.3",
 "serde",
 "serde_json",
 "sled 0.31.0",
 "sled",
 "structopt",
 "thiserror",
 "tokio 1.5.0",


@@ 2900,21 2885,24 @@ dependencies = [

[[package]]
name = "qmetaobject"
version = "0.1.4"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eab8585ec73d20ad37b118e613806c15008172152d291939c0959ede82a866"
checksum = "a76ec645d90072a2000911d064f8e130954d1fa2371f33da2742b542737adee8"
dependencies = [
 "cpp",
 "cpp_build",
 "lazy_static",
 "log",
 "qmetaobject_impl",
 "qttypes",
 "semver 0.11.0",
]

[[package]]
name = "qmetaobject_impl"
version = "0.1.4"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1da093791f2d883b53429170c9c69e603611d3a15652c8a2e6d2c203d8503c2c"
checksum = "bcb4d95359a0b8f518f7f93e2aa55511efbc7dc670e08b9546f674ba79e374eb"
dependencies = [
 "proc-macro2",
 "quote 1.0.8",


@@ 2932,6 2920,16 @@ dependencies = [
]

[[package]]
name = "qttypes"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96505a75726c4ac6770edad5b225066f65171fe34f650d6607edea8125722a25"
dependencies = [
 "cpp",
 "cpp_build",
]

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


@@ 3259,7 3257,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
 "semver",
 "semver 0.9.0",
]

[[package]]


@@ 3357,7 3355,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
 "semver-parser",
 "semver-parser 0.7.0",
]

[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
 "semver-parser 0.10.2",
]

[[package]]


@@ 3367,6 3374,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"

[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
 "pest",
]

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


@@ 3515,22 3531,6 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"

[[package]]
name = "sled"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb6824dde66ad33bf20c6e8476f5b82b871bc8bc3c129a10ea2f7dae5060fa3"
dependencies = [
 "crc32fast",
 "crossbeam-epoch 0.8.2",
 "crossbeam-utils 0.7.2",
 "fs2",
 "fxhash",
 "libc",
 "log",
 "parking_lot 0.10.2",
]

[[package]]
name = "sled"
version = "0.34.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"


@@ 4150,6 4150,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"

[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"

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

M Cargo.toml => Cargo.toml +1 -1
@@ 6,7 6,7 @@ edition = "2018"
build = "src/build.rs"

[dependencies]
qmetaobject = "0.1.4"
qmetaobject = { version = "0.2.0", features = ["webengine"]} 
gettext-rs = "0.4"
cstr = "0.1.0"
cpp = "0.5"

A qml/registration/CaptchaWindow.qml => qml/registration/CaptchaWindow.qml +26 -0
@@ 0,0 1,26 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import RegistrationController 0.1
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.12
import QtWebEngine 1.10
import org.kde.kirigami 2.13 as Kirigami
WebEngineView {
	signal gotCode(string code)
	id: captchaView
	url: "https://signalcaptchas.org/registration/generate.html"
	property bool done: false
	onLoadingChanged: {
		console.log(loadRequest.url);
		if (loadRequest.url.toString() == "https://signalcaptchas.org/registration/generate.html")  {
		captchaView.runJavaScript("window.onToken = function(token) { window.location = \"https://signalcaptcharesult.com/\"+token} ");
			} else {
				if (loadRequest.url.toString().indexOf("signalcaptcharesult") && !captchaView.done) {
					captchaView.done = true;
					let code = loadRequest.url.toString().replace("https://signalcaptcharesult.com/", "");
					window.pageStack.layers.pop();
					window.gotCaptcha(code);
				}	
			}
	}
}
\ No newline at end of file

M qml/registration/Registration.qml => qml/registration/Registration.qml +41 -4
@@ 1,21 1,58 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import SignalUI 0.1
import RegistrationController 0.1
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.12
import org.kde.kirigami 2.13 as Kirigami
Kirigami.ApplicationWindow {
	id: window
	title: "Register for Signal"
	pageStack.initalPage: Kirigami.Page {
	signal gotCaptcha(string code)
		function popup(title, text) {
		overlayText.text = text;
		overlayHeading.text = title;
		overlay.open();
	}
		property string captchaCode: ""

	pageStack.initialPage: Kirigami.Page {
	title: "Signal Registration"
	RegistrationController {
		id: registration
		onRegistration_result: {
			console.log(reg_type + "reg");
			console.log(result + "reg");
		}
	}
	Component.onCompleted: {
		registration.init_state();
		//window.pageStack.layers.push(Qt.resolvedUrl("CaptchaWindow.qml"));
	}
	Component {
		id: captchaComponent
		CaptchaWindow {}
	}


	Kirigami.OverlaySheet {
		id: overlay
		parent: window.overlay
		Label {
			id: overlayText
			text: ""
		}
		header: Kirigami.Heading {
			id: overlayHeading
			text: ""
		}
	}
	ColumnLayout {
		Row {
			Button {
				id: regNewBut
				text: "Register as a new device"
				onClicked: {
					window.pageStack.push(Qt.resolveUrl("registration/RegistrationNew.qml"));
					window.pageStack.layers.push(Qt.resolvedUrl("RegistrationNew.qml"));
				}
			}
		}


@@ 24,7 61,7 @@ Kirigami.ApplicationWindow {
				id: regLinkBut
				text: "Link as a secondary device"
				onClicked: {
					window.pageStack.push(Qt.resolveUrl("registration/RegistrationLink.qml"));
					window.pageStack.layers.push(Qt.resolvedUrl("RegistrationLink.qml"));
				}
			}
		}

A qml/registration/RegistrationComplete.qml => qml/registration/RegistrationComplete.qml +18 -0
@@ 0,0 1,18 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import RegistrationController 0.1
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.12
import org.kde.kirigami 2.13 as Kirigami
Kirigami.Page {
	id: registrationComplete
	Label {
		text: "Congrats! You've successfully registered this device with signal. Close this window and restart signal-rs to start using it"
	}
	Button {
		text: "Close"
		onClicked {
			Qt.quit();
		}
	}
}
\ No newline at end of file

M qml/registration/RegistrationLink.qml => qml/registration/RegistrationLink.qml +36 -3
@@ 1,13 1,46 @@
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.Page {
	id: regLinkPage
	title: "Register as a new device"
	title: "Register as a secondary device"
	ColumnLayout {
		
		Label {
			text: "When you press request, a QR code will open in your OS's default image viewer. Scan it to continue with the registration process"
		}
		Label {
			text: "Device Name"
		}
		TextField {
			id: linkDevName
			placeholderText: "Name your device"
			onAccepted: {
				regLinkPage.request();
			}
		}
		Button {
			text: "Request"
			onClicked: {
				regLinkPage.request();
			}
		}
	}
	Connections {
		target: registration 
		function onRegistration_result(reg_type, result, error) {
			if (reg_type == "secondary") {
				if (result == 0) {
					window.pageStack.layers.push(Qt.resolvedUrl("RegistrationComplete.qml"));
				} else {
					window.popup("An error occured registering with Signal", "The following error occured: " + error);
				}
			}
		}
	}
	function request() {
		registration.register_secondary(linkDevName.text);
	}

}
\ No newline at end of file

M qml/registration/RegistrationNew.qml => qml/registration/RegistrationNew.qml +66 -7
@@ 1,22 1,81 @@
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.Page {
	id: regLinkPage
	title: "Register as a secondary device"
	ColumnLayout {
	title: "Register as a new device"
	property bool usingCaptcha: false
	Connections {
		target: registration 
		function onRegistration_result(reg_type, result,error) {
			console.log(reg_type + result + "regnew");
			if (reg_type == "main") {
				if (result == 0) {
					window.pageStack.layers.push(Qt.resolvedUrl("VerificationCode.qml"));
				} else if (result == 1) {
					captchaOverlay.open();
				} else if (result == 2) {
					window.popup("Number can't be parsed", "Your phone number can't be properly parsed. Make sure it includes the country and a + at the beginning.")
				} else {
					window.popup("An error occured registering with Signal", "The following error occured: " + error);
				}
			}
		}
	}
	Connections {
		target: window
		function onGotCaptcha(code ) {
					console.log("CODE"+code);
		window.captchaCode = code;
		regLinkPage.register(number.text, useVoice.checked, code);
		}
	}
	Kirigami.OverlaySheet {
		id: captchaOverlay
		parent: window.overlay
		Label {
			text: "When you press request, a QR code will open in your OS's default image viewer. Scan it to continue with the registration process"
			text: "To register your number with signal, you need to fill out a captcha. Press ok to view the captcha page"
		}
		Button {
			text: "Ok"
			onClicked: {
				window.pageStack.layers.push(Qt.resolvedUrl("CaptchaWindow.qml"));
				captchaOverlay.close();
			}
		}
		header: Kirigami.Heading {
			id: overlayHeading
			text: "Captcha Required"
		}
	}
	ColumnLayout {
		Label {
			text: "Device Name"
			text: "Your phone number, including + and country code"
		}
		TextField {
			id: linkDevName

			id: number
			placeholderText: "+12345678910"
		}
		Label {
			text: "Use a voice call for verification instead of SMS"
		}
		CheckBox {
			id: useVoice
		}
		Button {
			text: "Get Verification code"
			onClicked: {
				regLinkPage.register(number.text, useVoice.checked, false) ;
			}
		}
	}
	function register(number, useVoice, captcha) {
		if (captcha !== false) {
			registration.register_main_captcha(number, useVoice, captcha);
		} else {
			registration.register_main(number, useVoice);
		}
	}
}
\ No newline at end of file

A qml/registration/VerificationCode.qml => qml/registration/VerificationCode.qml +38 -0
@@ 0,0 1,38 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import RegistrationController 0.1
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.12
import org.kde.kirigami 2.13 as Kirigami
Kirigami.Page {
	title: "Input verification code"
	ColumnLayout {
		Label {
			text: "Put in your verification code, without dash"
		}
		TextField {
			id: verifyText
			placeholderText: "123456"
		}
		Button {
			text: "Register"
			onClicked: {
				registration.confirm_code(verifyText.text);
			}
		}
	}
	Connections {
		target: registration
		function onRegistration_result(reg_type, result, error) {
			if (reg_type == "verify") {
				if (result == 0) {
					window.pageStack.layers.push(Qt.resolvedUrl("RegistrationComplete.qml"));
				} else if (result == 1) {
					window.popup("Error parsing code", "Make sure your verification code is six digits long and only numbers - don't include dashes");
				} else {
					window.popup("An error occured registering with Signal", "The following error occured: " + error);
				}
			}
		}
	}
}
\ No newline at end of file

M qml/signal-qt.pro => qml/signal-qt.pro +1 -1
@@ 1,4 1,4 @@
QT += quick
QT += quick webview

CONFIG += c++11


M rustfmt.toml => rustfmt.toml +3 -1
@@ 1,1 1,3 @@
edition = "2018"
\ No newline at end of file
edition = "2018"
reorder_imports = true
reorder_impl_items = true
\ No newline at end of file

M src/build.rs => src/build.rs +1 -1
@@ 153,7 153,7 @@ fn main() {
    let qt_library_path = qmake_query(&qmake_cmd, &args, "QT_INSTALL_LIBS");

    cpp_build::Config::new()
        .include(qt_include_path.trim())
        .include(qt_include_path.clone().trim())
        .build("src/main.rs");

    let macos_lib_search = if cfg!(target_os = "macos") {

M src/main.rs => src/main.rs +44 -11
@@ 32,13 32,12 @@ use signal::*;
use std::collections::*;
mod config;
use config::*;
use gettextrs::{bindtextdomain, textdomain};
use notify_rust::Notification;
use std::cell::*;
use std::env;
use std::path::PathBuf;
use std::result::Result;
mod presage_manager;
mod registration;
use presage_manager::*;
#[derive(QObject, Default)]
pub struct SignalUI {


@@ 77,9 76,11 @@ impl SignalUI {
            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()


@@ 88,6 89,7 @@ impl SignalUI {
            .send(resp)
            .expect("Couldn't send SignalResponse");
    }

    fn send_req(&self, reqp: SignalRequest) {
        self.state
            .as_ref()


@@ 96,12 98,15 @@ impl SignalUI {
            .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<u8> = vec![];
        std::fs::File::open(path)


@@ 110,6 115,7 @@ impl SignalUI {
            .unwrap();
        self.state.as_ref().unwrap().set_avatar(id, &data)
    }

    fn contact(&self, tel: String) -> String {
        let fetched = self
            .state


@@ 119,6 125,7 @@ impl SignalUI {
            .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() {


@@ 140,6 147,7 @@ impl SignalUI {
            }
        }
    }

    fn edit_contact(&self, name: String, tel: String, id: String) {
        self.send_req(SignalRequest::EditContact {
            name,


@@ 147,6 155,7 @@ impl SignalUI {
            id,
        });
    }

    fn send_message(&self, to: String, message: String, attachment: String) {
        let attachment = match attachment.len() {
            0 => None,


@@ 158,6 167,7 @@ impl SignalUI {
            attachment,
        });
    }

    fn send_group_message(&self, chat_id: String, message: String, attachment: String) {
        let attachment = match attachment.len() {
            0 => None,


@@ 172,6 182,7 @@ impl SignalUI {
            master_key: group.master_key,
        });
    }

    fn add_contact(&self, name: String, tel: String) {
        self.send_req(SignalRequest::EditContact {
            name,


@@ 179,6 190,7 @@ impl SignalUI {
            id: "".to_string(),
        });
    }

    fn show_chat(&mut self, view: String) {
        self.messages.replace(
            self.state


@@ 206,6 218,7 @@ impl SignalUI {
                .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);


@@ 334,6 347,7 @@ impl SignalUI {
            .await;
        });
    }

    /// Shows the given view in the app_box
    pub fn show_view(&mut self, view: String) {
        match view.as_ref() {


@@ 346,6 360,7 @@ impl SignalUI {
            _ => {}
        }
    }

    pub fn update_chats(&mut self) {
        let mut constructed: Vec<String> = vec![];
        let mut chats: Vec<Chat> = self


@@ 429,6 444,7 @@ impl SignalUI {
        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


@@ 437,6 453,7 @@ impl SignalUI {
            .resolve_name(source.as_str())
            .unwrap();
    }

    pub fn update_contacts(&self) {
        let mut contacts: Vec<Contact> = self
            .state


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


@@ 496,15 514,30 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
        });
    }
    qrc::load();
    qml_register_type::<SignalUI>(
        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();
    webengine::initialize();
    if registered {
        qml_register_type::<SignalUI>(
            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::<registration::RegistrationController>(
            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(())
}

M src/presage_manager.rs => src/presage_manager.rs +21 -0
@@ 234,11 234,13 @@ 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)
    }

    pub fn new(
        res_sender: glib::Sender<SignalResponse>,
        config: Config,


@@ 304,12 306,15 @@ impl Presage {
        });
        return pre;
    }

    pub fn avatar(&self, id: &str) -> Option<String> {
        None
    }

    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);


@@ 318,9 323,11 @@ impl Presage {
        let dec = base64::encode(gid);
        return dec;
    }

    pub fn get_gid(id: Vec<u8>) -> String {
        return base64::encode(id);
    }

    pub fn add_message(&mut self, message: SignalMessage, new: bool) {
        if self.message(&message.chat_id, message.sent_at).is_err() {
            println!("creating");


@@ 332,6 339,7 @@ impl Presage {
                .expect("Couldn't send AddHist");
        }
    }

    pub fn process_response(&mut self, response: SignalResponse) {
        match response {
            SignalResponse::RmMessage { chat_id, sent_at } => {


@@ 461,6 469,7 @@ impl Presage {
            _ => {}
        }
    }

    pub fn messages_id<'a>(&'a self, chat_id: &str) -> Result<BTreeMap<i64, SignalMessage>> {
        let messages = self
            .msg_store


@@ 474,6 483,7 @@ impl Presage {
            })
            .collect())
    }

    pub fn messages_id_doc<'a>(
        &'a self,
        chat_id: &str,


@@ 490,6 500,7 @@ impl Presage {
            })
            .collect())
    }

    fn message_doc<'a>(
        &'a self,
        chat_id: &str,


@@ 505,6 516,7 @@ impl Presage {
            .doc
            .clone())
    }

    pub fn group_doc(&self, chat_id: &str) -> Result<Document<Group>> {
        Ok(self
            .group_store


@@ 516,6 528,7 @@ impl Presage {
            .doc
            .clone())
    }

    fn contact_doc<'a>(&'a self, tel: &str) -> Result<Document<Contact>, anyhow::Error> {
        Ok(self
            .contact_store


@@ 527,6 540,7 @@ impl Presage {
            .doc
            .clone())
    }

    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);


@@ 536,6 550,7 @@ impl Presage {
            return Ok(chat_id.to_string());
        }
    }

    pub fn messages<'a>(
        &'a self,
    ) -> Result<BTreeMap<String, BTreeMap<i64, SignalMessage>>, anyhow::Error> {


@@ 552,6 567,7 @@ impl Presage {
        }
        Ok(map)
    }

    pub fn message<'a>(&'a self, chat_id: &str, sent_at: i64) -> Result<SignalMessage> {
        Ok(self
            .msg_store


@@ 564,6 580,7 @@ impl Presage {
            .inner
            .clone())
    }

    pub fn contact<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self
            .contact_store


@@ 576,6 593,7 @@ impl Presage {
            .inner
            .clone())
    }

    pub fn contact_tel<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self
            .contact_store


@@ 588,6 606,7 @@ impl Presage {
            .inner
            .clone())
    }

    pub fn groups<'a>(&'a self) -> Result<HashMap<String, Group>> {
        Ok(self
            .group_store


@@ 599,6 618,7 @@ impl Presage {
            })
            .collect())
    }

    pub fn group(&self, chat_id: &str) -> Result<Group> {
        Ok(self
            .group_store


@@ 611,6 631,7 @@ impl Presage {
            .inner
            .clone())
    }

    pub fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
        let map: HashMap<String, Contact> = self
            .contact_store

M src/qrc.rs => src/qrc.rs +4 -1
@@ 18,7 18,10 @@ qrc!(qml_resources,
        "qml/chats/EditGroup.qml" as "qml/EditGroup.qml",
        "qml/registration/Registration.qml" as "qml/Registration.qml",
        "qml/registration/RegistrationLink.qml" as "qml/RegistrationLink.qml",
        "qml/registration/RegistrationNew.qml" as "qml/RegistrationNew.qml"
        "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"
    },
);


A src/registration.rs => src/registration.rs +143 -0
@@ 0,0 1,143 @@
use std::str::FromStr;

use crate::*;
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::sink::SinkExt;
use futures::stream::StreamExt;
pub enum RegReq {
    Register(String, bool, Option<String>),
    Secondary(String),
    Code(String),
}
#[derive(QObject, Default)]
pub struct RegistrationController {
    base: qt_base_class!(trait QObject),
    register_main: qt_method!(fn(&mut self, number: String, voice: bool)),
    register_main_captcha: qt_method!(fn(&mut self, number: String, voice: bool, captcha: String)),
    register_secondary: qt_method!(fn(&mut self, name: String)),
    confirm_code: qt_method!(fn(&mut self, code: String)),
    registration_result: qt_signal!(reg_type: String, result: i32, error: String),
    init_state: qt_method!(fn(&mut self)),
    req_sender: Option<UnboundedSender<RegReq>>,
}

impl RegistrationController {
    pub fn init_state(&mut self) {
        let (req_sender, mut req_receiver) = unbounded::<RegReq>();
        self.req_sender.replace(req_sender);
        let ptr = QPointer::from(&*self);
        let cb = queued_callback(move |args: (String, i32, String)| {
            ptr.as_pinned()
                .unwrap()
                .borrow()
                .registration_result(args.0, args.1, args.2);
        });
        qmetaobject::future::execute_async(async move {
            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();
            loop {
                let next = req_receiver.next().await;
                match next.unwrap() {
                    RegReq::Register(number, voice, captcha) => {
                        if let Ok(phone) =
                            libsignal_service::prelude::phonenumber::PhoneNumber::from_str(
                                number.as_str(),
                            )
                        {
                            let res = manager
                                .register(
                                    libsignal_service::configuration::SignalServers::Production,
                                    phone,
                                    voice,
                                    captcha.as_ref().map(|x| x.as_str()),
                                )
                                .await;
                            if res.is_ok() {
                                cb(("main".into(), 0, "".into()));
                            } else {
                                let err = res.err().unwrap();
                                match err {
                                    presage::Error::CaptchaRequired => {
                                        cb(("main".into(), 1, "".into()));
                                    }
                                    _ => {
                                        cb(("main".into(), 3, err.to_string()));
                                    }
                                }
                            }
                        } else {
                            cb((
                                "main".to_string(),
                                2,
                                "Phone number could not be parsed".to_string(),
                            ));
                        }
                    }

                    RegReq::Secondary(name) => {
                        let res = manager
                            .link_secondary_device(
                                libsignal_service::configuration::SignalServers::Production,
                                name,
                            )
                            .await;
                        if res.is_ok() {
                            cb(("secondary".into(), 0, "".into()));
                        } else {
                            cb(("secondary".into(), 1, res.err().unwrap().to_string()));
                        }
                    }
                    RegReq::Code(code) => {
                        if let Ok(code) = code.as_str().parse::<u32>() {
                            let res = manager.confirm_verification_code(code).await;
                            if res.is_ok() {
                                cb(("verify".into(), 0, "".into()));
                            } else {
                                cb(("verify".into(), 2, res.err().unwrap().to_string()));
                            }
                        } else {
                            cb(("verify".into(), 1, "Code could not be parsed".to_string()))
                        }
                    }
                };
            }
        });
    }

    pub fn register_main(&mut self, number: String, voice: bool) {
        self.register(number, voice, None);
    }

    pub fn register_main_captcha(&mut self, number: String, voice: bool, captcha: String) {
        self.register(number, voice, Some(captcha));
    }

    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;
        });
    }

    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;
        });
    }

    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;
        });
    }
}