~nicohman/signal-rs

9328462c630b7d2d9944e48691e796d9f63575ef — nicohman 7 months ago 8d72ad3
Start on CreateChat
A qml/CreateChat.qml => qml/CreateChat.qml +29 -0
@@ 0,0 1,29 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15
Page {
	background: Rectangle {
		color: theme.background
	}
	anchors.fill: stackView
	ColumnLayout {
		anchors.fill: parent
		ListView {
			anchors.fill: parent
			model: signal.contacts
			delegate: ItemDelegate {
				height: 20
				Component.onCompleted: {
					console.log(signal.contacts);
				}
				Row {
					topPadding: 20
					Label {
						height: 20
						text: model.name
					}
				}
			}
		}
	}
}
\ No newline at end of file

M qml/CreateContact.qml => qml/CreateContact.qml +26 -24
@@ 5,30 5,32 @@ import QtQuick.Layouts 1.15
Page {
    width: 600
    height: 400

    title: qsTr("Create Contact")
    ColumnLayout {
    Row {
    TextField {
    	id: contactNameInput
    	placeholderText: "Contact Name"
    background: Rectangle {
        color: theme.background
    }
}
Row {
    TextField {
    	id:contactPhoneInput
    	placeholderText: "Contact Phone"
    }
}
Row {
    Button {
    	id: addContactButton
    	text: "Add Contact"
    	onClicked: {
    		signal.add_contact(contactNameInput.text, contactPhoneInput.text);
    		stackView.pop();
    	}
    title: "Create New Contact"
    ColumnLayout {
        Row {
            TextField {
            	id: contactNameInput
            	placeholderText: "Contact Name"
            }
        }
        Row {
            TextField {
            	id:contactPhoneInput
            	placeholderText: "Contact Phone"
            }
        }
        Row {
            Button {
            	id: addContactButton
            	text: "Add Contact"
            	onClicked: {
            		signal.add_contact(contactNameInput.text, contactPhoneInput.text);
            		stackView.pop();
            	}
            }
        }
    }
}
}
}
\ No newline at end of file

M qml/EditContact.qml => qml/EditContact.qml +17 -3
@@ 18,10 18,11 @@ Page {
        editContact.contactName = signalState.currentName;
	}
	ColumnLayout {
		anchors.fill: parent
		Layout.fillWidth: true
		Layout.fillHeight: true
		Layout.alignment: Qt.AlignHCenter
		Row {
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			Avatar {
				width: 100
				height: 100


@@ 29,20 30,33 @@ Page {
			}
		}
		Row {
			Layout.alignment: Qt.AlignHCenter
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			Label {
				text: "Contact Name"
			}
		}
		Row {
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			TextField {
				id: editContactName
				text: contactName
			}	
		}
		Row {
			Layout.alignment: Qt.AlignHCenter
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			Label {
				text: "Contact Phone"
			}
		}
		Row {
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			TextField {
				id: editContactTel
				text: contactTel
			}
		}
		Row {
			Layout.alignment: Qt.AlignHCenter | Qt.AlignTop	
			Button {
				text: "Save changes"
				onClicked: {

M qml/MessageUI.qml => qml/MessageUI.qml +18 -2
@@ 16,6 16,8 @@ ItemDelegate {
    width: parent ? parent.width : 0
    property ListModel attachmentModel: ListModel {
    }
    property ListModel reactionModel : ListModel {
    }
    Component.onCompleted : {
        if (message.message.attachment && message.message.attachment !== "[]") {
            var attachments  = JSON.parse(new String(message.message.attachment));


@@ 23,6 25,12 @@ ItemDelegate {
                message.attachmentModel.append(at);
            });
        }
        if (message.message.reactions && message.message.reactions !== "[]") {
            var reactions = JSON.parse(new String(message.message.reactions));
            reactions.forEach(function(r) {
                message.reactionModel.append(r);
            });
        }
    }
    Row {
        id: row


@@ 95,8 103,6 @@ ItemDelegate {
                                switch (CType) {
                                    case 2:
                                        var obj = Qt.createQmlObject(`import QtQuick 2.15; Image { width: 350\nfillMode: Image.PreserveAspectFit\nsource: "file://${File}"}`,atRow,  "imageDel");
                                        //atRow.Layout.minimumHeight += obj.implicitHeight;
                                        //atRow.forceLayout();
                                        if (obj.width > rectangle.width) {
                                            rectangle.width = obj.width + 10;
                                        }


@@ 140,6 146,16 @@ ItemDelegate {
                        }
                    }
                }
                Row {
                    leftPadding: 5
                    ListView {
                        orientation: ListView.Horizontal
                        model: message.reactionModel
                        delegate: Label {
                            text: emoji
                        }
                    }
                }
            }
        }
    }

M qml/SignalState.qml => qml/SignalState.qml +3 -0
@@ 29,6 29,9 @@ Item {
            case "editContact":
                stackView.push("EditContact.qml");
                break;
            case "createChat":
                stackView.push("CreateChat.qml");
                break;
        }
    }
    function contact(tel) {

M qml/loading.qml => qml/loading.qml +1 -1
@@ 4,7 4,7 @@ import QtQuick.Controls 2.5
Page {
    anchors.fill: stackView
    objectName: "loadingpage"
    title: qsTr("Loading")
    title: ""
    background:Rectangle {
    	color: theme.background
    }

M qml/main.qml => qml/main.qml +13 -0
@@ 29,6 29,7 @@ ApplicationWindow {
        }
        name: "signal"
        onReady: {
            signalState.currentView = "chats";
            stackView.replace(null, chatViewStack);
            toolButton.enabled = true;
        }


@@ 75,6 76,18 @@ ApplicationWindow {
                    signalState.openView("editContact");
                }
            }
            ToolButton {
                visible: signalState.currentView === "chats" 
                id: createChatButton
                text: "Create Chat"
                anchors.right: parent.right
                anchors.rightMargin: 5
                anchors.top: parent.top
                anchors.topMargin: 5
                onClicked: {
                    signalState.openView("createChat");
                }
            }
        }
    }
    Drawer {

D src/axolotl.rs => src/axolotl.rs +0 -114
@@ 1,114 0,0 @@
use crate::*;

use anyhow::Context;
use anyhow::Result;
pub struct AxolotlSession {
    pub res_sender: glib::Sender<SignalResponse>,
    pub contacts: HashMap<String, Contact>,
    pub messages: HashMap<String, BTreeMap<i64, SignalMessage>>,
}
impl AxolotlSession {
    pub async fn run(
        res_sender: glib::Sender<SignalResponse>,
        mut req_receiver: UnboundedReceiver<SignalRequest>,
    ) {
        let (socket, _response) = connect_async(Url::parse(CONNECTION).unwrap())
            .await
            .unwrap();
        let mut session = SignalSession::from_socket(socket);
        loop {
            tokio::select! {
                next = session.stream.next() => {
                    res_sender.send(next.unwrap()).expect("Couldn't pass on websocket response");
                },
                inc = req_receiver.recv() => {
                    if let Some(v) = inc {
                        session.send(v).await.expect("Couldn't send websocket request");
                    }
                },
            };
        }
    }
    pub fn add_message(&mut self, message: SignalMessage, new: bool) {
        /*if message.attachment.is_some() {
            println!("{:?}", message);
        }*/
        if !self.messages.contains_key(&message.source) {
            self.messages
                .insert(message.source.clone(), BTreeMap::new());
        }
        let msg_p = self.messages.get_mut(&message.source).unwrap();
        if !msg_p.contains_key(&message.sent_at) {
            msg_p.insert(message.sent_at, message.clone());
            self.res_sender
                .send(SignalResponse::AddHist(
                    message.clone(),
                    new,
                ))
                .expect("Couldn't send AddHist");
        }
    }
    pub fn add_messages_old(&mut self, messages: Vec<SignalMessage>) {
        for msg in messages.into_iter() {
            self.add_message(msg, false);
        }
    }
    pub fn add_messages(&mut self, messages: Vec<SignalMessage>) {
        for msg in messages.into_iter() {
            self.add_message(msg, true)
        }
    }
}
impl SignalBackend for AxolotlSession {
    fn new(res_sender: glib::Sender<SignalResponse>, _config: Config) -> AxolotlSession {
        AxolotlSession {
            res_sender,
            contacts: HashMap::new(),
            messages: HashMap::new(),
        }
    }
        fn messages_tel<'a>(&'a self, tel: &str) -> Result<BTreeMap<i64, SignalMessage>> {
            Ok(self.messages.get(tel).context("none")?.clone())
        }
    fn message<'a>(&'a self, tel: &str, id: i64) -> Result<SignalMessage> {
        Ok(self.messages.get(tel).context("none")?.get(&id).context("none")?.clone())
    }
    fn contact<'a>(&'a self, tel: &str) -> Result<Contact> {
        Ok(self.contacts.get(tel).context("none")?.clone())
    }
    fn contacts<'a>(&'a self) -> HashMap<String, Contact> {
        self.contacts.clone()
    }
    fn process_response(&mut self, response: SignalResponse) {
        match response {
            SignalResponse::ChatList(chats) => {
                for chat in chats {
                    self.add_messages_old(chat.messages);
                }
            }
            SignalResponse::MoreMessageList(mut message_list) => {
                message_list
                    .messages
                    .sort_by(|a, b| a.sent_at.partial_cmp(&b.sent_at).unwrap());
                self.add_messages_old(message_list.messages.into_iter().rev().collect());
            }
            SignalResponse::MessageReceived(msg) => {
                self.add_message(msg, true);
            }
            SignalResponse::ContactList(contacts) => {
                for contact in contacts.into_iter() {
                    if let Some(contact_p) = self.contacts.get_mut(&contact.tel) {
                        *contact_p = contact;
                    } else {
                        self.contacts.insert(contact.tel.clone(), contact.clone());
                    }
                }
            }
            SignalResponse::MessageList(message_list) => {
                println!("{:?}", message_list);
                self.add_messages(message_list.messages.into_iter().rev().collect());
            }
            _ => {}
        };
    }
}

M src/main.rs => src/main.rs +20 -49
@@ 54,6 54,8 @@ use std::collections::*;

mod config;
use config::*;
use gettextrs::{bindtextdomain, textdomain};
use signal::signal_cli::*;
use std::cell::*;
use std::env;
use std::path::PathBuf;


@@ 61,8 63,6 @@ use std::process::Command;
use std::rc::*;
use std::result::Result;

use gettextrs::{bindtextdomain, textdomain};

mod scli;
pub trait SignalBackend {
    fn messages_id<'a>(&'a self, tel: &str) -> Result<BTreeMap<i64, SignalMessage>, anyhow::Error>;


@@ 89,6 89,8 @@ pub struct SignalUI {
    chats: qt_property!(RefCell<SimpleListModel<Chat>>; NOTIFY chats_changed),
    messages: qt_property!(RefCell<SimpleListModel<QMLSignalMessage>>; NOTIFY msgs_changed),
    current_messages: qt_property!(String),
    contacts: qt_property!(RefCell<SimpleListModel<Contact>>; NOTIFY contacts_changed),
    contacts_changed: qt_signal!(),
    msgs_changed: qt_signal!(),
    chats_changed: qt_signal!(),
    show_chat: qt_method!(fn(&self, tel: String)),


@@ 203,12 205,14 @@ impl SignalUI {
                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.replace_with(|cur| {
                        chats.into_iter().collect()
                    });*/
                    slf.chats.borrow_mut().reset_data(chats.to_vec());
                    slf.chats.borrow_mut().reset_data(chats.clone());
                    slf.chats_changed();
                }
                if let SignalResponse::ContactList(ref contacts) = res {
                    println!("{:?}", contacts);
                    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 {


@@ 338,50 342,12 @@ where
            "chats" => {
                self.update_chats();
            }
            "createChat" => {
                self.update_contacts();
            }
            _ => {}
        }
    }
    /*pub fn show_view(&mut self, view: View) {
        match view.clone() {
            View::Chats => {
                self.req_sender
                    .send(SignalRequest::GetChatList)
                    .expect("Couldn't send GetChatList");
                self.hide_history();
                self.update_chats();
                //self.chat_list.update_chats();
                //self.chat_list.set_enabled(true);
                //self.chat_list.listbox.unselect_all();
                self.stack.set_visible_child_name("chats");
            }
            View::Messages(tel) => {
                //self.chat_list.set_enabled(false);
                self.show_chat(tel);
                self.stack.set_visible_child_name("messages");
            }
            View::Password => {
                self.stack.set_visible_child_name("password");
            }
            View::CreateContact => {
                self.stack.set_visible_child_name("create-contact");
            }
            View::EditContact(id) => {
                self.edit_contact
                    .edit(self.sbackend.contacts().get(&id).unwrap().clone());
                self.stack.set_visible_child_name("edit-contact");
            }
            View::CreateChat => {
                self.cc
                    .set_choices(self.sbackend.contacts().values().cloned().collect());
                self.stack.set_visible_child_name("create-chat");
            }
            View::Settings => {
                self.stack.set_visible_child_name("settings");
            }
        };
        self.headerbar
            .set_view(view, &self.req_sender, &self.res_sender);
    }*/
    pub fn update_chats(&mut self) {
        let mut constructed: Vec<String> = vec![];
        let mut chats: Vec<Chat> = self


@@ 447,11 413,17 @@ where
            }
        }
        chats.sort_by_key(|c| -1 * c.timestamp);
        println!("{:?}", chats);
        self.res_sender
            .send(SignalResponse::ChatList(chats))
            .expect("Couldn't send ChatList");
    }
    pub fn update_contacts(&self) {
        self.res_sender
            .send(SignalResponse::ContactList(
                self.sbackend.contacts().values().cloned().collect(),
            ))
            .expect("Couldn't send ContactList");
    }
    /// Shows a given chat by tel
    pub fn show_chat(&self, chat_id: String) {
        if let Some(map) = self.sbackend.messages_id(&chat_id).ok() {


@@ 520,7 492,6 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
        .unwrap()
        .wait()
        .unwrap();

    Ok(())
}


M src/qrc.rs => src/qrc.rs +2 -1
@@ 10,7 10,8 @@ qrc!(qml_resources,
        "qml/CreateContact.qml",
        "qml/EditContact.qml",
        "qml/Theme.qml",
        "qml/Avatar.qml"
        "qml/Avatar.qml",
        "qml/CreateChat.qml"
    },
);


M src/scli.rs => src/scli.rs +101 -47
@@ 120,57 120,79 @@ impl SCLISession {
                parsed = line_receiver.next() => {
                    let parsed = parsed.unwrap();
                    if let Some(ref data_msg) = parsed.envelope.data_message {
                                let mut attachment = vec![];
                                if let Some(ats) = &data_msg.attachments {
                                    attachment = parse_attachments(ats);
                                }
                                let mut chat_id = parsed.envelope.source.clone();
                                if let Some(group_info) = &data_msg.group_info {
                                    chat_id = group_info.group_id.clone();
                                }
                                let outgoing = config.username == parsed.envelope.source;
                        let con_msg = SignalMessage {
                            message: data_msg.message.clone().unwrap_or_default(),
                            ID: 0,
                            outgoing,
                            attachment: Some(attachment),
                            source: parsed.envelope.source.clone(),
                            sent_at: parsed.envelope.timestamp,
                            chat_id,
                        };
                        res_sender.send(SignalResponse::IDLessMessage(con_msg)).expect("Couldn't send IDLessMessage");
                        if let Some(react) = &data_msg.reaction {
                            res_sender.send(SignalResponse::AddReaction {
                                emoji: react.emoji.clone(),
                                target: react.target_author.clone(),
                                from: parsed.envelope.source.clone(),
                                target_timestamp: react.target_sent_timestamp,
                                timestamp: parsed.envelope.timestamp
                            }).expect("Couldn't send AddReaction");
                        } else {
                            let mut attachment = vec![];
                            if let Some(ats) = &data_msg.attachments {
                                attachment = parse_attachments(ats);
                            }
                            let mut chat_id = parsed.envelope.source.clone();
                            if let Some(group_info) = &data_msg.group_info {
                                chat_id = group_info.group_id.clone();
                            }
                            let outgoing = config.username == parsed.envelope.source;
                            let con_msg = SignalMessage {
                                message: data_msg.message.clone().unwrap_or_default(),
                                ID: 0,
                                outgoing,
                                attachment: Some(attachment),
                                source: parsed.envelope.source.clone(),
                                sent_at: parsed.envelope.timestamp,
                                chat_id,
                                reactions: vec![]
                            };
                            res_sender.send(SignalResponse::IDLessMessage(con_msg)).expect("Couldn't send IDLessMessage");
                        }
                    } else if let Some(ref sync_msg) = parsed.envelope.sync_message {
                        if let Some(sm) = &sync_msg.sent_message {
                                let mut outgoing = false;
                                let mut source = parsed.envelope.source.clone();
                                if parsed.envelope.source == config.username {
                                    outgoing = true;
                                    if let Some(ref group_info) = sm.group_info {
                                        //source = group_info.group_id.clone();
                                    } else {
                            if let Some(react) = &sm.reaction {
                                res_sender.send(SignalResponse::AddReaction {
                                    emoji: react.emoji.clone(),
                                    target: react.target_author.clone(),
                                    from: parsed.envelope.source.clone(),
                                    target_timestamp: react.target_sent_timestamp,
                                    timestamp: parsed.envelope.timestamp
                                }).expect("Couldn't send AddReaction");
                            } else {
                            let mut outgoing = false;
                            let mut source = parsed.envelope.source.clone();
                            if parsed.envelope.source == config.username {
                                outgoing = true;
                                if let Some(ref group_info) = sm.group_info {
                                    //source = group_info.group_id.clone();
                                } else {
                                    source = sm.destination.clone().unwrap();
                                }
                                }
                                let mut attachment = vec![];
                                if let Some(ats) = &sm.attachments {
                                    attachment = parse_attachments(ats);
                                }
                                let mut chat_id = source.clone();
                                if let Some(group_info) = &sm.group_info {
                                    chat_id = group_info.group_id.clone();
                                }
                                let con_msg = SignalMessage {
                                    message: sm.message.clone().unwrap_or_default(),
                                    ID: 0,
                                    outgoing,
                                    attachment: Some(attachment),
                                    source,
                                    sent_at: parsed.envelope.timestamp,
                                    chat_id,
                                };
                                res_sender.send(SignalResponse::IDLessMessage(con_msg)).expect("Couldn't send IDLessMessage");
                            }
                            let mut attachment = vec![];
                            if let Some(ats) = &sm.attachments {
                                attachment = parse_attachments(ats);
                            }
                            let mut chat_id = source.clone();
                            if let Some(group_info) = &sm.group_info {
                                chat_id = group_info.group_id.clone();
                            }
                            let con_msg = SignalMessage {
                                message: sm.message.clone().unwrap_or_default(),
                                ID: 0,
                                outgoing,
                                attachment: Some(attachment),
                                source,
                                sent_at: parsed.envelope.timestamp,
                                chat_id,
                                reactions: vec![]
                            };
                            res_sender.send(SignalResponse::IDLessMessage(con_msg)).expect("Couldn't send IDLessMessage");
                        }
                    }
                    }
                }
                inc = req_receiver.recv() => {
                    if let Some(v) = inc {


@@ 205,6 227,7 @@ impl SCLISession {
                                    sent_at: timestamp,
                                    chat_id: chat_id,
                                    ID: 0,
                                    reactions: vec![]
                                })).expect("Couldnt send IDLessMessage");
                            }
                            SignalRequest::SendMessage { to, message } => {


@@ 217,6 240,7 @@ impl SCLISession {
                                    sent_at: timestamp,
                                    chat_id: to,
                                    ID: 0,
                                    reactions: vec![]
                                })).expect("Couldnt send IDLessMessage");
                            }
                            SignalRequest::AddContact{ name, phone} => {


@@ 277,7 301,8 @@ impl SCLISession {
                outgoing,
                sent_at: msg.timestamp,
                attachment: None,
                chat_id: chat_id,
                chat_id,
                reactions: vec![],
            };
            self.add_message(made_msg, true);
        } else {


@@ 288,7 313,8 @@ impl SCLISession {
                outgoing,
                sent_at: msg.timestamp,
                attachment: None,
                chat_id: chat_id,
                chat_id,
                reactions: vec![],
            };
            self.add_message(made_msg, true);
        }


@@ 498,6 524,34 @@ impl SignalBackend for SCLISession {
                    }
                }
            }
            SignalResponse::AddReaction {
                target,
                target_timestamp,
                timestamp,
                from,
                emoji,
            } => {
                println!("{}\n{}\n{}", target, target_timestamp, emoji.clone());

                let reaction = Reaction {
                    emoji,
                    source: from,
                    timestamp,
                };
                let mut fetched = self
                    .msg_store
                    .search(format!("source:{} AND sent_at:{}", target, target_timestamp).as_str())
                    .expect("Couldn't find reaction message")
                    .hits
                    .into_iter()
                    .next()
                    .unwrap()
                    .doc;
                fetched.inner.reactions.push(reaction);
                self.msg_store
                    .update(&fetched)
                    .expect("Couldn't update reaction message");
            }
            _ => {}
        }
    }

M src/signal.rs => src/signal.rs +101 -87
@@ 15,6 15,85 @@ 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,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    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>,
    }
    #[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
        }
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct SyncMessage {
        pub sent_message: Option<SentMessage>,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    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>,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    pub struct SCLIReaction {
        pub emoji: String,
        pub target_author: String,
        pub is_remove: bool,
        pub target_sent_timestamp: i64,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    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>,
    }
    #[serde(rename_all = "camelCase")]
    #[derive(Serialize, Deserialize, Clone, Debug)]
    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")]


@@ 71,7 150,12 @@ pub enum SignalRequest {
    },
    GetGroups,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Reaction {
    pub emoji: String,
    pub timestamp: i64,
    pub source: String,
}
#[derive(Serialize, Deserialize, Clone, EnumVariantType, Debug)]
pub enum SignalResponse {
    #[evt(derive(Clone, Serialize, Deserialize, Debug))]


@@ 106,6 190,13 @@ pub enum SignalResponse {
        chat_id: String,
        sent_at: i64,
    },
    AddReaction {
        target: String,
        emoji: String,
        timestamp: i64,
        target_timestamp: i64,
        from: String,
    },
}
impl Default for SignalResponse {
    fn default() -> Self {


@@ 130,17 221,14 @@ pub struct Chat {
    pub is_group: bool,
    pub last: String,
    pub timestamp: i64,
    // pub when: String,
    // pub c_type: i32,
    // pub(crate) to prevent this field from being used by SimpleListItem
    #[serde(deserialize_with = "parse_messages")]
    pub(crate) messages: MessageVec,
    pub(crate) messages: Vec<SignalMessage>,
    /*#[serde(deserialize_with = "parse_messages")]
    pub messages: usize,*/
    /*#[serde(default)]
    pub force_enable: bool,*/
}
type MessageVec = Vec<SignalMessage>;
impl QMetaType for Chat {}
fn parse_messages<'de, D>(d: D) -> Result<Vec<SignalMessage>, D::Error>
where


@@ 148,99 236,20 @@ where
{
    Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or(vec![]))
}
/*fn parse_messages<'de, D>(d: D) -> Result<usize, D::Error>
where
    D: Deserializer<'de>,
{
    let msgs : Result<Vec<SignalMessage>, D::Error> = Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or(vec![]));
    Ok(msgs?.len())
}*/

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SignalCLIContainer {
    pub envelope: SignalCLIMessage,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
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>,
}
#[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
    }
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SyncMessage {
    pub sent_message: Option<SentMessage>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Group {
    pub name: String,
    pub members: Vec<String>,
    pub id: String,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
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>,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SCLIReaction {
    pub emoji: String,
    pub targetAuthor: String,
    pub isRemove: bool,
    pub targetSentTimestamp: i64,
}

#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GroupInfo {
    pub group_id: String,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
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>,
}
#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SignalCLIAttachment {
    pub content_type: String,
    pub filename: Option<String>,
    pub id: String,
    pub size: u64,
}

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


@@ 263,6 272,9 @@ pub struct SignalMessage {
        serialize_with = "ser_attachments"
    )]
    pub attachment: Option<Vec<Attachment>>,
    #[serde(default)]
    #[pallet(skip_indexing)]
    pub reactions: Vec<Reaction>,
    //pub received_at: i64,
    /*pub h_time: String,
    pub c_type: i32,


@@ 288,6 300,7 @@ pub struct QMLSignalMessage {
    pub outgoing: bool,
    pub sent_at: i64,
    pub attachment: String,
    pub reactions: String,
    //pub received_at: i64,
    /*pub h_time: String,
    pub c_type: i32,


@@ 309,6 322,7 @@ impl QMLSignalMessage {
            outgoing: msg.outgoing,
            sent_at: msg.sent_at,
            attachment: serde_json::to_string(&msg.attachment).unwrap(),
            reactions: serde_json::to_string(&msg.reactions).unwrap(),
        }
    }
}


@@ 365,7 379,7 @@ trait SignalResponseTrait {}
impl SignalResponseTrait for SignalResponse {}
#[serde(rename_all = "PascalCase")]
#[pallet(tree_name = "contacts")]
#[derive(Serialize, Deserialize, Clone, Debug, DocumentLike, QGadget, Default)]
#[derive(Serialize, Deserialize, Clone, Debug, DocumentLike, QGadget, Default, SimpleListItem)]
pub struct Contact {
    pub tel: String,
    //pub uuid: String,