~nicohman/signal-rs

21d519db83d3c672c89259ac11661434f0dbdc34 — nicohman 6 months ago e0c5619
Save to try kirigami
M qml/Avatar.qml => qml/Avatar.qml +6 -8
@@ 14,13 14,9 @@ Item {
		selectFolder: false
		selectMultiple: false
		onAccepted: {
			console.log(fd.fileUrl);
			var regex = new RegExp("file://");
			var path = fd.fileUrl.toString().replace("file://", "");
			console.log(path);
			console.log(avatarItem.tel);
			var newAvatar = signal.set_avatar(avatarItem.tel, path);
			console.log(newAvatar);
			//avatarImg.cache = false;
			avatarImg.source = "";
			avatarImg.source = `file://${newAvatar}`;	


@@ 34,12 30,11 @@ Item {
		id: avatarImg
		Component.onCompleted: {
	        var avatar = signal.avatar(parent.tel);
	        console.log("av"+avatar)
	        if (avatar !== "") {
	        	console.log("set avatar");
	        	avatarImg.source = `file://${avatar}`;
	        } else {
	        	avatarRect.visible = true;
	        	avatarRectLabel.text = new String(signal.resolve_name(parent.tel))[0].toUpperCase();
	        }
		}
		layer.enabled: parent.rounded


@@ 58,7 53,9 @@ Item {
		MouseArea {
			anchors.fill: parent
		onClicked: {
			fd.visible = true;
			if (avatarItem.editable) {
				fd.visible = true;
			}
		}
	}
	}


@@ 69,8 66,9 @@ Item {
	    radius: 50
	    visible:false
	    Label {
	    	id: avatarRectLabel
	        color: theme.text
	        //text: new String(model.name)[0]
	        text: ""
	        anchors.verticalCenter: parent.verticalCenter
	        anchors.horizontalCenter: parent.horizontalCenter
	    }

M qml/ChatHistory.qml => qml/ChatHistory.qml +34 -4
@@ 1,9 1,10 @@
import QtQuick 2.0
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3

Page {
    anchors.fill: stackView
    //anchors.fill: stackView
    id: chatHistory
    objectName: "chatHistory"
    background:Rectangle {


@@ 13,12 14,30 @@ Page {
    function sendMessage() {
        console.log("Sending "+ msgTextInput.text +" to "+signalState.current);
        if (signalState.currentChat.is_group) {
            signal.send_group_message(signalState.current, msgTextInput.text);
            signal.send_group_message(signalState.current, msgTextInput.text, "");
        } else {
            signal.send_message(signalState.current, msgTextInput.text);
            signal.send_message(signalState.current, msgTextInput.text, "");
        }
        msgTextInput.text = "";
    }
    FileDialog {
        id: fd
        title: "Upload an attachment"
        selectExisting: true
        selectFolder: false
        selectMultiple: false
        onAccepted: {
            var path = fd.fileUrl.toString().replace("file://", "");
            if (signalState.currentChat.is_group) {
            signal.send_group_message(signalState.current, msgTextInput.text, path);

                } else {


            signal.send_message(signalState.current, msgTextInput.text, path);
        }
        }
    }
    ColumnLayout {
        anchors.fill: parent
        ListView {


@@ 48,11 67,19 @@ Page {
            Layout.fillWidth: true
            TextField {
                id: msgTextInput
                width: parent.width
                width: parent.width - addAttachmentButton.width
                onAccepted: {
                    chatHistory.sendMessage();
                }
            }
            Button {
                id: addAttachmentButton
                icon.name: "edit-add"
                display:AbstractButton.IconOnly
                onClicked: {
                    fd.visible = true;
                }
            }
        }
        Row {
            Layout.fillWidth: true


@@ 62,6 89,9 @@ Page {
                }
                width: parent.width
                id: sendButton
                contentItem: Text {
                    color: theme.text
                }
                text: "Send Message"
                onClicked: {
                    chatHistory.sendMessage();

M qml/ChatList.qml => qml/ChatList.qml +1 -0
@@ 21,6 21,7 @@ Page {
            width: chatView.width
            height: 40
            onClicked: {
                console.log("ChatList clicked");
                signalState.currentChat = model;
                signalState.setCurrent(tel);
                signal.show_chat(tel);

M qml/Settings.qml => qml/Settings.qml +24 -0
@@ 17,6 17,7 @@ Page {
    		Label {
    			font.pixelSize: Qt.application.font.pixelSize * 1.3
    			text: "Theme Selection"
                color: theme.text
    		}
    	}
    	Row {


@@ 32,5 33,28 @@ Page {
		    	model: ['Signal', 'System']
		    }
    	}
        Row {
            Layout.leftMargin: 10
            Layout.topMargin: 20
            Layout.alignment: Qt.AlignTop
            Label {
                font.pixelSize: Qt.application.font.pixelSize * 1.3
                text: "Desktop View"
                color: theme.text
            }
        }
        Row {
            Layout.leftMargin: 15
            Layout.alignment: Qt.AlignTop
            CheckBox {
                id: desktopCheckBox
                Component.onCompleted: {
                    desktopCheckBox.checked = signalState.desktopView;
                }
                onClicked: {
                    signalState.desktopView = desktopCheckBox.checked;
                }
            }
        }
    }
}

M qml/SignalState.qml => qml/SignalState.qml +4 -2
@@ 9,13 9,13 @@ Item {
    property string currentView: ""
    property var currentChat : {}
    property var headerItems: []
    property bool desktopView: false
    function openView(viewName) {
        signalState.currentView = viewName;
        signal.show_view(viewName);
        switch (viewName) {
            case "chats":
                stackView.push(chatViewStack);
                //signal.show_view("chats");
                break;
            case "createContact":
                stackView.push("CreateContact.qml");


@@ 24,7 24,9 @@ Item {
                stackView.push("Settings.qml");
                break;
            case "chatHistory":
                stackView.push(messagesViewStack);
                if (!signalState.desktopView) {
                    stackView.push(messagesViewStack);
                }
                break;
            case "editContact":
                stackView.push("EditContact.qml");

M qml/Theme.qml => qml/Theme.qml +18 -16
@@ 16,21 16,23 @@ Item {
		setTheme(current);
	}
	function setTheme(themeName) {
	switch(themeName) {
		case 'Signal':
			theme.background = "black";
			theme.message = "#2e2e2e";
			theme.highlightedText = "white";
			theme.outgoing = "#2c6bed";
			theme.text = "white";
			break;
		case 'System':
			theme.background = systemPalette.window;
			theme.message = systemPalette.highlight;
			theme.outgoing = systemPalette.highlight;
			theme.messageText = systemPalette.highlightedText;
			theme.text =  systemPalette.text;
			theme.highlightedText = systemPalette.highlightedText;
	}
		switch(themeName) {
			case 'Signal':
				theme.background = "black";
				theme.message = "#2e2e2e";
				theme.highlightedText = "white";
				theme.outgoing = "#2c6bed";
				theme.text = "white";
				theme.button = "#2e2e2e";
				break;
			case 'System':
				theme.background = systemPalette.window;
				theme.message = systemPalette.highlight;
				theme.outgoing = systemPalette.highlight;
				theme.messageText = systemPalette.highlightedText;
				theme.text =  systemPalette.text;
				theme.highlightedText = systemPalette.highlightedText;
				theme.button = systemPalette.button
		}
	}
}
\ No newline at end of file

M qml/main.qml => qml/main.qml +39 -12
@@ 3,6 3,7 @@ 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
ApplicationWindow {
    id: window
    width: 640


@@ 35,6 36,7 @@ ApplicationWindow {
        }
    }
    header: ToolBar {
        id: headerToolBar
        background: Rectangle {
            color: theme.background
        }


@@ 58,7 60,7 @@ ApplicationWindow {
            text: stackView.currentItem.title
            anchors.centerIn: parent
            font.pixelSize: Qt.application.font.pixelSize * 1.3
            color: theme.highlightedText
            color: theme.text
        }
        Item {
            anchors.right: parent.right


@@ 101,45 103,70 @@ ApplicationWindow {
        }
        Column {
            anchors.fill: parent
            ItemDelegate {
                text: qsTr("Chats")
            /*ItemDelegate {
                width: parent.width
                onClicked: {
                    signalState.openView("chats");
                    drawer.close()
                }
            }
                contentItem: Text {
                    text: "Chats"
                    color: theme.text
                }
            }*/
            ItemDelegate {
                text: qsTr("Create Contact")
                width: parent.width
                onClicked: {
                    signalState.openView("createContact");
                    drawer.close()
                }
                contentItem: Text {
                    text: "Create Contact"
                    color: theme.text
                }
            }
            ItemDelegate {
                text: qsTr("Settings")
                width: parent.width
                onClicked: {
                    signalState.openView("settings");
                    drawer.close()
                }
                contentItem : Text {
                    text: "Settings"
                    color: theme.text
                }
            }
        }
    }
    Row {
        anchors.fill: parent
        StackView {
            id: stackView
            initialItem: "loading.qml"
            anchors.fill: signalState.desktopView ? parent : undefined
            x: 0
            width: signalState.desktopView ? window.width * 0.33 : window.width
            height: window.height - headerToolBar.height
        }
        Loader {
            x: window.width * 0.33
            id: msgDesktopViewLoader
            sourceComponent: signalState.desktopView ? messagesViewStack : undefined
            height:  signalState.desktopView ? window.height - headerToolBar.height : 0
            width:  signalState.desktopView ? window.width * 0.66 : 0
            enabled: signalState.desktopView
            active: signalState.desktopView
        }
    }
    Component {
        id: chatViewStack
        ChatList {}
    }
    Component {
   Component {
        id: messagesViewStack
        ChatHistory {}
    }
    StackView {
        id: stackView
        initialItem: "loading.qml"
        anchors.fill: parent
    }

}



M src/config.rs => src/config.rs +8 -8
@@ 1,21 1,21 @@
use serde::*;
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct Config {
    #[serde(default)]
    pub backend: Backend,
    pub number: Option<String>,
    #[serde(default)]
    pub theme: Theme,
    pub scli: Option<SignalCLIConfig>,
    #[serde(default)]
    pub notifications: NotificationPolicy,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
pub enum Backend {
    Axolotl,
    SignalCLI,
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum NotificationPolicy {
    Enabled,
    Disabled,
}
impl Default for Backend {
impl Default for NotificationPolicy {
    fn default() -> Self {
        Backend::Axolotl
        NotificationPolicy::Disabled
    }
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]

M src/main.rs => src/main.rs +158 -145
@@ 68,18 68,6 @@ use std::process::Command;
use std::rc::*;
use std::result::Result;
mod scli;
pub trait SignalBackend {
    fn messages_id(&self, tel: &str) -> Result<BTreeMap<i64, SignalMessage>, anyhow::Error>;
    fn message(&self, tel: &str, id: i64) -> Result<SignalMessage, anyhow::Error>;
    fn messages(&self) -> Result<BTreeMap<String, BTreeMap<i64, SignalMessage>>, anyhow::Error>;
    fn contact(&self, tel: &str) -> Result<Contact, anyhow::Error>;
    fn contacts(&self) -> HashMap<String, Contact>;
    fn process_response(&mut self, response: SignalResponse);
    fn new(res_sender: glib::Sender<SignalResponse>, config: Config) -> Self;
    fn groups(&self) -> HashMap<String, Group>;
    fn avatar(&self, id: &str) -> Option<String>;
    fn set_avatar(&self, id: String, data: &[u8]) -> String;
}
#[derive(QObject, Default)]
pub struct SignalUI {
    base: qt_base_class!(trait QObject),


@@ 98,7 86,7 @@ pub struct SignalUI {
    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)),
    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)),


@@ 106,20 94,38 @@ pub struct SignalUI {
    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)),
    send_group_message: qt_method!(fn(&self, chat_id: String, message: String, attachment: String)),
}
impl SignalUI {
    fn del_message(&self, chat_id: String, sent_at: i64) {
    fn send_res(&self, resp: SignalResponse) {
        self.state
            .as_ref()
            .unwrap()
            .sbackend
            .res_sender
            .send(SignalResponse::RmMessage { chat_id, sent_at })
            .expect("Couldn't send RmMessage");
            .send(resp)
            .expect("Couldn't send SignalResponse");
    }
    fn send_req(&self, reqp: SignalRequest) {
        self.state
            .as_ref()
            .unwrap()
            .sbackend
            .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 });
        /*self.state
        .as_ref()
        .unwrap()
        .sbackend
        .res_sender
        .send()
        .expect("Couldn't send RmMessage");*/
    }
    fn avatar(&self, id: String) -> QString {
        println!("{}", id);
        QString::from(
            self.state
                .as_ref()


@@ 148,43 154,63 @@ impl SignalUI {
        serde_json::to_string(&fetched).unwrap()
    }
    fn edit_contact(&self, name: String, tel: String, id: String) {
        self.state
            .as_ref()
            .unwrap()
            .req_sender
            .send(SignalRequest::EditContact {
                name,
                phone: tel,
                id,
            })
            .expect("Couldn't send EditContact");
        self.send_req(SignalRequest::EditContact {
            name,
            phone: tel,
            id,
        });
        /*self.state
        .as_ref()
        .unwrap()
        .req_sender
        .send()
        .expect("Couldn't send EditContact");*/
    }
    fn send_message(&self, to: String, message: String) {
        self.state
            .as_ref()
            .unwrap()
            .req_sender
            .send(SignalRequest::SendMessage { to, message })
            .expect("Couldn't send SendMessage");
    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,
        });
        /*        self.state
        .as_ref()
        .unwrap()
        .req_sender
        .send()
        .expect("Couldn't send SendMessage");*/
    }
    fn resolve_name(&mut self, source: String) -> String {
        self.state.as_mut().unwrap().resolve_name(source)
    }
    fn send_group_message(&self, chat_id: String, message: String) {
        self.state
            .as_ref()
            .unwrap()
            .req_sender
            .send(SignalRequest::SendGroupMessage { chat_id, message })
            .expect("Couldn't send SendGroupMessage");
    fn send_group_message(&self, chat_id: String, message: String, attachment: String) {
        let attachment = match attachment.len() {
            0 => None,
            _ => Some(vec![attachment]),
        };
        self.send_req(SignalRequest::SendGroupMessage {
            chat_id,
            message,
            attachment,
        });
        /*self.state
        .as_ref()
        .unwrap()
        .req_sender
        .send()
        .expect("Couldn't send SendGroupMessage");*/
    }
    fn add_contact(&self, name: String, tel: String) {
        self.state
            .as_ref()
            .unwrap()
            .req_sender
            .send(SignalRequest::AddContact { name, phone: tel })
            .expect("Couldn't send AddContact");
        self.send_req(SignalRequest::AddContact { name, phone: tel });
        /*self.state
        .as_ref()
        .unwrap()
        .req_sender
        .send()
        .expect("Couldn't send AddContact");*/
    }
    fn show_view(&mut self, view: String) {
        self.state.as_mut().unwrap().show_view(view);


@@ 202,94 228,102 @@ impl SignalUI {
                .map(|x| QMLSignalMessage::from_msg(x))
                .collect(),
        );
        self.msgs_changed();
        self.current_messages = view.clone();
        self.state.as_ref().unwrap().show_chat(view);
    }
    fn init_state(&mut self) {
        //let config: Config = confy::load("signal-rs").expect("Couldn't handle config file");
        let config = Config {
            backend: Backend::SignalCLI,
            number: Some("+14254920949".to_string()),
            theme: Theme::System,
            scli: Some(SignalCLIConfig {
                username: "+14254920949".to_string(),
                data_dir: "data".to_string(),
            }),
            notifications: NotificationPolicy::Enabled,
        };
        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());
                if let SignalResponse::ChatList(ref chats) = res {
                    slf.chats.borrow_mut().reset_data(chats.clone());
                    slf.chats_changed();
                }
                if let SignalResponse::ContactList(ref contacts) = res {
                    slf.contacts.borrow_mut().reset_data(contacts.clone());
                    slf.contacts_changed();
                }
                if let SignalResponse::AddHist(ref msg, new) = res {
                    if new {
                        if msg.chat_id == slf.current_messages {
                            slf.messages
                                .get_mut()
                                .push(QMLSignalMessage::from_msg(msg.clone()));
                match res {
                    SignalResponse::ChatList(ref chats) => {
                        slf.chats.borrow_mut().reset_data(chats.clone());
                        slf.chats_changed();
                    }
                    SignalResponse::ContactList(ref contacts) => {
                        slf.contacts.borrow_mut().reset_data(contacts.clone());
                        slf.contacts_changed();
                    }
                    SignalResponse::AddHist(ref msg, new) => {
                        if new {
                            if msg.chat_id == slf.current_messages {
                                slf.messages
                                    .get_mut()
                                    .push(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();
                            }
                        }
                        /*Notification::new()
                        .summary(&slf.resolve_name(msg.source.clone()))
                        .body(msg.message.as_str())
                        .show()
                        .unwrap();*/
                    }
                }
                if let SignalResponse::RmMessage {
                    ref chat_id,
                    sent_at,
                } = res
                {
                    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;
                    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;
                                }
                            }
                        }
                    }
                }
                if let SignalResponse::Groups(_) = res {
                    let ids = slf
                        .state
                        .as_mut()
                        .unwrap()
                        .sbackend
                        .msg_store
                        .all()
                        .unwrap()
                        .into_iter()
                        .map(|x| {
                            return x.inner.source;
                        })
                        .collect::<Vec<String>>();
                    let mut done: HashMap<String, bool> = HashMap::new();
                    for id in ids {
                        if !done.contains_key(id.as_str()) {
                            done.insert(id.clone(), true);
                            slf.state
                                .as_mut()
                                .unwrap()
                                .sbackend
                                .resolve_name(&id)
                                .unwrap();
                    SignalResponse::Groups(_) => {
                        let ids = slf
                            .state
                            .as_mut()
                            .unwrap()
                            .sbackend
                            .msg_store
                            .all()
                            .unwrap()
                            .into_iter()
                            .map(|x| {
                                return x.inner.source;
                            })
                            .collect::<Vec<String>>();
                        let mut done: HashMap<String, bool> = HashMap::new();
                        for id in ids {
                            if !done.contains_key(id.as_str()) {
                                done.insert(id.clone(), true);
                                slf.state
                                    .as_mut()
                                    .unwrap()
                                    .sbackend
                                    .resolve_name(&id)
                                    .unwrap();
                            }
                        }
                        println!("Finished initialization");
                        slf.show_view("chats".to_string());
                        slf.ready();
                        slf.signalResponse(serde_json::to_string(&res).unwrap());
                    }
                    println!("Finished initialization");
                    slf.show_view("chats".to_string());
                    slf.ready();
                }
                slf.signalResponse(serde_json::to_string(&res).unwrap());
                    _ => {}
                };
            });
        });
        let (res_sender, res_receiver) =


@@ 302,45 336,26 @@ impl SignalUI {
            let err = resource.await;
            println!("Lost connection to DBus: {}", err);
        });
        match config.backend {
            Backend::SignalCLI => {
                let mut state: SignalState = SignalState::new(
                    req_sender.clone(),
                    l_res_sender.clone(),
                    config.clone(),
                    conn.clone(),
                );
                state.user_tel = config.number.clone();
                self.state = Some(state);
                res_receiver.attach(None, move |value| {
                    process_res(value);
                    glib::Continue(true)
                });
            }
            _ => {}
        };
        let mut state: SignalState = SignalState::new(
            req_sender.clone(),
            l_res_sender.clone(),
            config.clone(),
            conn.clone(),
        );
        state.user_tel = config.number.clone();
        self.state = Some(state);
        res_receiver.attach(None, move |value| {
            process_res(value);
            glib::Continue(true)
        });
        req_sender
            .send(SignalRequest::GetChatList)
            .expect("Couldn't send GetChatList");
        if config.backend == Backend::Axolotl {
            req_sender
                .send(SignalRequest::GetContacts)
                .expect("Couldn't send GetContacts");
        }
        req_sender
            .send(SignalRequest::GetGroups)
            .expect("Couldn't send GetGroups");
        qmetaobject::future::execute_async(async move {
            match clconf.backend {
                Backend::Axolotl => {
                    //AxolotlSession::run(res_sender, req_receiver).await;
                }
                // Inspired in part by https://github.com/boxdot/gurk-rs
                Backend::SignalCLI => {
                    scli::SCLISession::run(res_sender, req_receiver, clconf.scli.unwrap(), conn)
                        .await;
                }
            }
            scli::SCLISession::run(res_sender, req_receiver, clconf.scli.unwrap(), conn).await;
        });
    }
}


@@ 352,7 367,6 @@ pub struct SignalState {
    pub res_sender: glib::Sender<SignalResponse>,
    pub current_chat: Rc<RefCell<Option<String>>>,
    pub user_tel: Option<String>,
    pub backend: Backend,
    pub sbackend: std::boxed::Box<scli::SCLISession>,
    pub config: Rc<RefCell<Config>>,
}


@@ 384,7 398,6 @@ impl SignalState {
            res_sender: res_sender.clone(),
            current_chat: Rc::new(RefCell::new(None)),
            user_tel: None,
            backend: config.backend,
            config: Rc::new(RefCell::new(config.clone())),
            sbackend: std::boxed::Box::new(scli::SCLISession::new(
                res_sender, config, conn, req_sender,

M src/scli.rs => src/scli.rs +63 -28
@@ 2,7 2,6 @@ use crate::*;
use anyhow::{Context, Result};
use dbus::message::MatchRule;
use dbus::nonblock;
use dbus_tokio::connection;
use futures_util::StreamExt;
use pallet::Document;
use pallet::Store;


@@ 39,20 38,22 @@ pub struct SCLISession {
}
impl SCLISession {
    pub fn merge_ids(&self, from: String, to: String) {
        self.msg_store.update_multi(
            self.msg_store
                .search(format!("chat_id:{}", from).as_str())
                .unwrap()
                .hits
                .into_iter()
                .map(|hit| {
                    let mut doc = hit.doc;
                    doc.inner.chat_id = to.clone();
                    doc
                })
                .collect::<Vec<Document<SignalMessage>>>()
                .as_slice(),
        );
        self.msg_store
            .update_multi(
                self.msg_store
                    .search(format!("chat_id:{}", from).as_str())
                    .unwrap()
                    .hits
                    .into_iter()
                    .map(|hit| {
                        let mut doc = hit.doc;
                        doc.inner.chat_id = to.clone();
                        doc
                    })
                    .collect::<Vec<Document<SignalMessage>>>()
                    .as_slice(),
            )
            .expect("Couldn't merge chatids");
    }
    pub fn fetch_contacts_scli(username: &str) -> Vec<Contact> {
        let output = Command::new("signal-cli")


@@ 92,6 93,7 @@ impl SCLISession {
            let mut reader = std::io::BufReader::new(stdout).lines();
            loop {
                let msg_line = reader.next().unwrap().unwrap();
                println!("{}", msg_line);
                if let Some(parsed) = serde_json::from_str::<SignalCLIContainer>(&msg_line).ok() {
                    line_sender
                        .send(parsed)


@@ 172,7 174,7 @@ impl SCLISession {
                            let mut source = parsed.envelope.source.clone();
                            if parsed.envelope.source == config.username {
                                outgoing = true;
                                if let Some(ref group_info) = sm.group_info {
                                if let Some(ref _group_info) = sm.group_info {
                                    //source = group_info.group_id.clone();
                                } else {
                                    source = sm.destination.clone().unwrap();


@@ 224,26 226,56 @@ impl SCLISession {
                                }
                                res_sender.send(SignalResponse::Groups(groups)).expect("Couldn't send Groups");
                            }
                            SignalRequest::SendGroupMessage {chat_id, message} => {
                                let (timestamp, ) : (i64,) = proxy.method_call("org.asamk.Signal", "sendGroupMessage", (&message, Vec::<String>::new(), base64::decode(&chat_id).expect("Couldn't decode group id"))).await.unwrap();
                            SignalRequest::SendGroupMessage {chat_id, message, attachment} => {
                                let (timestamp, ) : (i64,) = proxy.method_call("org.asamk.Signal", "sendGroupMessage", (&message, attachment.clone().unwrap_or_default(), base64::decode(&chat_id).expect("Couldn't decode group id"))).await.unwrap();
                                res_sender.send(SignalResponse::IDLessMessage(SignalMessage {
                                    message: message,
                                    source: config.username.clone(),
                                    outgoing: true,
                                    attachment: None,
                                    attachment: attachment.map(|x| {
                                        x.into_iter().map(|x| {
                                        let path = PathBuf::from(x.clone());
                                        let mut ctype = AttachmentType::File;
                                        if x.contains("png") || x.contains("jpg") || x.contains("jpeg") {
                                            ctype = AttachmentType::Image;
                                        }

                                         Attachment {
                                            file_name: path.file_name().unwrap().to_str().unwrap().to_string(),
                                            file: x,
                                            c_type: ctype
                                        }
                                        }).collect()
                                    }),
                                    sent_at: timestamp,
                                    chat_id: chat_id,
                                    ID: 0,
                                    reactions: vec![]
                                })).expect("Couldnt send IDLessMessage");
                            }
                            SignalRequest::SendMessage { to, message } => {
                                let (timestamp,) : (i64,) = proxy.method_call("org.asamk.Signal", "sendMessage", (&message,Vec::<String>::new(), vec![&to] )).await.unwrap();
                            SignalRequest::SendMessage { to, message, attachment } => {
                                let (timestamp,) : (i64,) = proxy.method_call("org.asamk.Signal", "sendMessage", (&message,attachment.clone().unwrap_or_default(), vec![&to] )).await.unwrap();
                                res_sender.send(SignalResponse::IDLessMessage(SignalMessage {
                                    message: message,
                                    source: to.clone(),
                                    outgoing: true,
                                    attachment: None,
                                    attachment: attachment.map(|x| {
                                        x.into_iter().map(|x| {


                                        let path = PathBuf::from(x.clone());
                                        let mut ctype = AttachmentType::File;
                                        if x.contains("png") || x.contains("jpg") || x.contains("jpeg") {
                                            ctype = AttachmentType::Image;
                                        }

                                         Attachment {
                                            file_name: path.file_name().unwrap().to_str().unwrap().to_string(),
                                            file: x,
                                            c_type: ctype
                                        }
                                        }).collect()
                                    }),
                                    sent_at: timestamp,
                                    chat_id: to,
                                    ID: 0,


@@ 252,7 284,7 @@ impl SCLISession {
                            },
                            SignalRequest::GetContactName { chat_id } => {
                                let (name,): (String,) = proxy.method_call("org.asamk.Signal", "getContactName", (chat_id.clone(), )).await.unwrap();
                    res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: chat_id}]));
                                res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: chat_id}])).expect("Couldn't send ContactList");
                            },
                            SignalRequest::AddContact{ name, phone} => {
                                res_sender.send(SignalResponse::ContactList(vec![Contact {name, tel: phone}])).expect("Couldn't send ContactList");


@@ 365,7 397,6 @@ impl SCLISession {
        let av_dir = PathBuf::from(SCLI_AVATAR_DIRECTORY.to_owned());
        for opt in opts {
            let now_path = av_dir.join(format!("{}-{}", opt, id));
            println!("{:?}", av_dir);
            if now_path.exists() {
                return Some(now_path.to_str().unwrap().to_string());
            }


@@ 455,9 486,11 @@ impl SCLISession {
                return Ok(chat_id.to_string());
            }
        } else {
            self.req_sender.send(SignalRequest::GetContactName {
                chat_id: chat_id.to_string(),
            });
            self.req_sender
                .send(SignalRequest::GetContactName {
                    chat_id: chat_id.to_string(),
                })
                .expect("Couldn't send GetContactName");
            self.resolved_names.insert(chat_id.to_string(), None);
            return Ok(chat_id.to_string());
        }


@@ 522,7 555,9 @@ impl SCLISession {
        match response {
            SignalResponse::RmMessage { chat_id, sent_at } => {
                if let Some(doc) = self.message_doc(&chat_id, sent_at).ok() {
                    self.msg_store.delete(doc.id);
                    self.msg_store
                        .delete(doc.id)
                        .expect("Couldn't delete message");
                }
            }
            SignalResponse::SignalCLIEnvelope(msg) => {

M src/signal.rs => src/signal.rs +2 -52
@@ 1,15 1,9 @@
use enum_variant_type::EnumVariantType;
use futures_util::sink::SinkExt;
use futures_util::stream::*;
use futures_util::{future, StreamExt};
use pallet::DocumentLike;
use qmetaobject::*;
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_repr::*;
use std::convert::TryFrom;
use tokio_tungstenite::tungstenite::protocol::Message as TMessage;
use tokio_tungstenite::*;
lazy_static! {
    static ref CONTACT_REG: Regex =
        Regex::new(r"Number: (\+[0-9]+) Name: (\S+)  Blocked:").unwrap();


@@ 100,6 94,7 @@ pub enum SignalRequest {
    SendMessage {
        to: String,
        message: String,
        attachment: Option<Vec<String>>,
    },
    GetContactName {
        chat_id: String,


@@ 107,6 102,7 @@ pub enum SignalRequest {
    SendGroupMessage {
        chat_id: String,
        message: String,
        attachment: Option<Vec<String>>,
    },
    GetMessageList {
        id: String,


@@ 391,49 387,3 @@ pub struct Contact {
    //pub blocked: bool,
    //pub expire_timer: i32,
}
pub struct SignalSession<'a> {
    pub sink: SplitSink<WebSocketStream<tokio::net::TcpStream>, TMessage>,
    pub stream: BoxStream<'a, SignalResponse>,
}
impl<'a> SignalSession<'a> {
    pub fn from_socket(socket: WebSocketStream<tokio::net::TcpStream>) -> SignalSession<'a> {
        let (write, read) = socket.split();
        let st = read.filter_map(|x| {
            if let Ok(attempt) =
                serde_json::from_str::<SignalResponse>(&x.unwrap().into_text().unwrap())
            {
                return future::ready(Some(attempt));
            }
            future::ready(None)
        });
        SignalSession {
            sink: write,
            stream: st.boxed(),
        }
    }
    pub async fn send(&mut self, req: SignalRequest) -> Result<(), Box<dyn std::error::Error>> {
        Ok(self
            .sink
            .send(TMessage::Text(serde_json::to_string(&req)?))
            .await?)
    }
    pub async fn send_res<T>(&mut self, req: SignalRequest) -> Result<T, Box<dyn std::error::Error>>
    where
        T: TryFrom<SignalResponse>,
    {
        self.send(req).await?;
        Ok(self
            .stream
            .by_ref()
            .filter_map(|x| {
                if let Ok(v) = T::try_from(x) {
                    future::ready(Some(v))
                } else {
                    future::ready(None)
                }
            })
            .next()
            .await
            .unwrap())
    }
}