~nicohman/signal-rs

0dda6ee3b1035fc46c4f26a945876f1f3062cb3d — nicohman 8 months ago e9f3f6e
Add sending group messages
13 files changed, 52 insertions(+), 246 deletions(-)

D qml/Chat.qml
D qml/ChatForm.ui.qml
M qml/ChatHistory.qml
M qml/ChatList.qml
D qml/Home.ui.qml
M qml/MessageUI.qml
M qml/SignalState.qml
M src/main.rs
M src/qrc.rs
M src/scli.rs
D src/signal.glade
M src/signal.rs
D src/util.rs
D qml/Chat.qml => qml/Chat.qml +0 -4
@@ 1,4 0,0 @@
import QtQuick 2.4

ChatForm {
}

D qml/ChatForm.ui.qml => qml/ChatForm.ui.qml +0 -12
@@ 1,12 0,0 @@
import QtQuick 2.4
import QtQuick.Controls 2.5

Row {
    width: 400
    height: 250
    property string name: "Nico with an H"
    property string last: ""
    Label {
        text: name
    }
}

M qml/ChatHistory.qml => qml/ChatHistory.qml +6 -1
@@ 9,7 9,12 @@ Page {
    title: signalState.currentName
    function sendMessage() {
        console.log("Sending "+ msgTextInput.text +" to "+signalState.current);
        signal.send_message(signalState.current, msgTextInput.text);
        if (signalState.currentChat.is_group) {
            console.log("is group");
            signal.send_group_message(signalState.current, msgTextInput.text);
        } else {
            signal.send_message(signalState.current, msgTextInput.text);
        }
        msgTextInput.text = "";
    }
    ColumnLayout {

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

D qml/Home.ui.qml => qml/Home.ui.qml +0 -13
@@ 1,13 0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5

Page {
    width: 600
    height: 400
    objectName: "homeobb"
    title: qsTr("Page 1")
    Label {
        text: textHome
        anchors.centerIn: parent
    }
}

M qml/MessageUI.qml => qml/MessageUI.qml +0 -1
@@ 17,7 17,6 @@ ItemDelegate {
    property ListModel attachmentModel: ListModel {
    }
    Component.onCompleted : {
        console.log(message.message);
        if (message.message.attachment && message.message.attachment !== "[]") {
            var attachments  = JSON.parse(new String(message.message.attachment));
            attachments.forEach(function(at) {

M qml/SignalState.qml => qml/SignalState.qml +4 -3
@@ 41,9 41,10 @@ Item {
    }
    function setCurrent(cur) {
        signalState.current = cur;
        for (var i=0; i < signal.chats.count; i++) {
            if (chatModel.get(i).Tel === cur) {
                signal.chats.currentName = signal.chats.get(i).Name;
        console.log(signal.chats.rowCount());
        for (var i=0; i < signal.chats.rowCount(); i++) {
            if (chatModel[i] && chatModel[i].tel === cur) {
                signalState.currentName = signal.chats[i].name;
                break;
            }
        }

M src/main.rs => src/main.rs +14 -45
@@ 43,10 43,8 @@ extern crate url;

#[macro_use]
extern crate lazy_static;
use futures_util::StreamExt;
use qmetaobject::QAbstractListModel;
use qmetaobject::*;
use tokio::io::AsyncBufReadExt;
use tokio::sync::mpsc::*;

#[allow(non_snake_case)]


@@ 55,7 53,6 @@ use signal::*;
use std::collections::*;

mod config;
mod util;
/// Contains the custom widgets
//mod widgets;
use config::*;


@@ 105,6 102,7 @@ pub struct SignalUI {
    del_message: qt_method!(fn(&self, chat_id: String, sent_at: i64)),
    ready: qt_signal!(),
    avatar: qt_method!(fn(&self, id: String) -> QString),
    send_group_message: qt_method!(fn(&self, chat_id: String, message: String)),
}
impl SignalUI {
    fn del_message(&self, chat_id: String, sent_at: i64) {


@@ 134,11 132,9 @@ impl SignalUI {
            .sbackend
            .contact(&tel)
            .unwrap_or_default();
        println!("{:?}", fetched);
        serde_json::to_string(&fetched).unwrap()
    }
    fn edit_contact(&self, name: String, tel: String, id: String) {
        println!("edit:{}{}{}", name, tel, id);
        self.state
            .as_ref()
            .unwrap()


@@ 158,6 154,14 @@ impl SignalUI {
            .send(SignalRequest::SendMessage { to, message })
            .expect("Couldn't send SendMessage");
    }
    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 add_contact(&self, name: String, tel: String) {
        self.state
            .as_ref()


@@ 186,7 190,6 @@ impl SignalUI {
        self.state.as_ref().unwrap().show_chat(view);
    }
    fn init_state(&mut self) {
        println!("init");
        //let config: Config = confy::load("signal-rs").expect("Couldn't handle config file");
        let config = Config {
            backend: Backend::SignalCLI,


@@ 232,6 235,7 @@ impl SignalUI {
                    }
                }
                if let SignalResponse::Groups(_) = res {
                    println!("Finished initialization");
                    slf.show_view("chats".to_string());
                    slf.ready();
                }


@@ 284,22 288,14 @@ impl SignalUI {
/// The main state manager/UI controller
pub struct SignalState<T: SignalBackend + ?Sized> {
    pub current: Option<String>,
    //pub history: Option<ChatHistory>,
    pub req_sender: UnboundedSender<SignalRequest>,
    pub last_id: Rc<RefCell<Option<String>>>,
    //pub chat_list: ChatList,
    pub res_sender: glib::Sender<SignalResponse>,
    pub current_chat: Rc<RefCell<Option<String>>>,
    //pub pw: PasswordDialog,
    //pub cc: CreateChat,
    //pub ccontact: CreateContact,
    //pub edit_contact: EditContact,
    pub user_tel: Option<String>,
    pub backend: Backend,
    pub sbackend: std::boxed::Box<T>,
    //pub headerbar: Header,
    pub config: Rc<RefCell<Config>>,
    //pub settings: Settings,
}
/// An enum that represents a view that can be displayed.
#[derive(Clone, Debug)]


@@ 336,30 332,6 @@ where
            sbackend: std::boxed::Box::new(T::new(res_sender, config)),
        }
    }
    /// Connects the gtk event signals to the relevant callbacks
    /*pub fn connect_signals(&mut self) {
        //self.chat_list.connect(&self);
        self.connect_scroll();
        self.connect_send_button();
        self.cc.connect(&self);
        self.pw.connect(&self);
        self.edit_contact.connect(&self);
        self.ccontact.connect(&self);
        self.settings.connect(&self);
    }
    pub fn connect_send_button(&mut self) {
        self.button.connect_clicked(clone!( @weak self.input as input, @strong self.req_sender as req_sender, @weak self.scroll as scroll, @weak self.current_chat as chat => move |_| {
            let msg = input.get_buffer().get_text();
            input.set_buffer(&EntryBuffer::new(None));
            let to = chat.borrow().clone().unwrap();
            req_sender.send(SignalRequest::SendMessage {
                to: to.clone(),
                message: msg
            }).expect("Couldn't send SendMessage");
            req_sender.send(SignalRequest::GetMessageList{id: to}).expect("Couldn't send GetMessageList");
            scroll.emit_scroll_child(ScrollType::End, false);
        }));
    }*/
    /// Shows the given view in the app_box
    pub fn show_view(&mut self, view: String) {
        match view.as_ref() {


@@ 411,7 383,6 @@ where
            .set_view(view, &self.req_sender, &self.res_sender);
    }*/
    pub fn update_chats(&mut self) {
        println!("{:?}", self.sbackend.contacts());
        let mut constructed: Vec<String> = vec![];
        let mut chats: Vec<Chat> = self
            .sbackend


@@ 419,9 390,7 @@ where
            .values()
            .enumerate()
            .filter_map(|(i, contact)| {
                println!("{:?}", contact);
                if let Some(msgs) = self.sbackend.messages_id(&contact.tel).ok() {
                    println!("{:?}", msgs);
                    if msgs.len() > 0 {
                        let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                        constructed.push(contact.tel.clone());


@@ 465,7 434,6 @@ where
            .enumerate()
        {
            if msgs.len() > 0 {
                println!("MSGS{:?}", msgs);
                let latest = msgs.values().max_by_key(|x| x.sent_at).unwrap();
                chats.push(Chat {
                    ID: (chats.len() + i) as i32,


@@ 479,12 447,13 @@ where
            }
        }
        chats.sort_by_key(|c| -1 * c.timestamp);
        self.res_sender.send(SignalResponse::ChatList(chats));
        self.res_sender
            .send(SignalResponse::ChatList(chats))
            .expect("Couldn't send ChatList");
    }
    /// 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() {
            println!("{:?}", map);
            self.res_sender
                .send(SignalResponse::HistoryMessage {
                    phone: chat_id,


@@ 541,7 510,7 @@ async fn main() -> Result<(), std::boxed::Box<dyn std::error::Error>> {
    let mut engine = QmlEngine::new();
    engine.load_file("qrc:/qml/main.qml".into());
    engine.exec();
    println!("Close");
    println!("Shutting down");
    std::process::Command::new("pkill")
        .arg("-f")
        .arg("-SIGKILL")

M src/qrc.rs => src/qrc.rs +0 -3
@@ 2,11 2,8 @@ qrc!(qml_resources,
    "/" {
        "qml/main.qml",
        "qml/MessageUI.qml",
        "qml/Chat.qml",
        "qml/ChatForm.ui.qml",
        "qml/ChatHistory.qml",
        "qml/ChatList.qml",
        "qml/Home.ui.qml",
        "qml/loading.qml",
        "qml/Settings.ui.qml",
        "qml/SignalState.qml",

M src/scli.rs => src/scli.rs +23 -17
@@ 1,22 1,16 @@
use crate::*;
use anyhow::{Context, Result};
use config::*;
use dbus::message::MatchRule;
use dbus::nonblock;
use dbus_tokio::connection;
use futures_util::StreamExt;
use pallet::Document;
use pallet::Store;
use serde::{Deserialize, Deserializer, Serialize};
use signal::*;
use signal::*;
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufWriter, Write};
use std::io::BufRead;
use std::path::*;
use std::process::Stdio;
use std::time::Duration;
use tempfile::TempDir;
lazy_static! {
    static ref SCLI_ATTACHMENT_DIRECTORY: String = home::home_dir()
        .unwrap()


@@ 76,7 70,7 @@ impl SCLISession {
            let err = resource.await;
            println!("Lost connection to DBus: {}", err);
        });
        let (mut line_sender, mut line_receiver) =
        let (line_sender, mut line_receiver) =
            tokio::sync::mpsc::unbounded_channel::<SignalCLIContainer>();
        let clconf = config.clone();
        std::thread::spawn(move || {


@@ 93,8 87,9 @@ impl SCLISession {
                let msg_line = reader.next();
                let parsed: SignalCLIContainer =
                    serde_json::from_str(&msg_line.unwrap().unwrap()).unwrap();
                println!("gone");
                line_sender.send(parsed);
                line_sender
                    .send(parsed)
                    .expect("Couldn't send parsed line from scli daemon");
            }
        });
        let proxy = nonblock::Proxy::new(


@@ 115,14 110,13 @@ impl SCLISession {
            if opts.is_ok() {
                break;
            }
            std::thread::sleep_ms(500);
            std::thread::sleep(Duration::from_millis(500));
        }
        // NOTE: HOLY SHIT GO DBUS
        loop {
            tokio::select! {
                parsed = line_receiver.next() => {
                    let parsed = parsed.unwrap();
                    println!("Incoming!");
                    if let Some(ref data_msg) = parsed.envelope.data_message {
                                let mut attachment = vec![];
                                if let Some(ats) = &data_msg.attachments {


@@ 193,7 187,19 @@ impl SCLISession {
                                        id: parsed_id
                                    });
                                }
                                res_sender.send(SignalResponse::Groups(groups));
                                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();
                                res_sender.send(SignalResponse::IDLessMessage(SignalMessage {
                                    message: message,
                                    source: config.username.clone(),
                                    outgoing: true,
                                    attachment: None,
                                    sent_at: timestamp,
                                    chat_id: chat_id,
                                    ID: 0,
                                })).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();


@@ 222,8 228,8 @@ impl SCLISession {
                }
            }
        }
        unreachable!();
        conn.remove_match(_inc.0.token()).await.unwrap();
        //unreachable!();
        //conn.remove_match(_inc.0.token()).await.unwrap();
    }
    pub fn add_message(&mut self, message: SignalMessage, new: bool) {
        if self.message(&message.chat_id, message.sent_at).is_err() {


@@ 449,7 455,7 @@ impl SignalBackend for SCLISession {
            SignalResponse::IDLessMessage(mut msg) => {
                let mut id = 0;
                if let Some(v) = self.messages_id(&msg.chat_id).ok() {
                    if (v.len() > 0) {
                    if v.len() > 0 {
                        id = v.values().map(|msg| msg.ID).max().unwrap() + 1;
                    }
                }


@@ 465,7 471,7 @@ impl SignalBackend for SCLISession {
            }
            SignalResponse::Groups(groups) => {
                for group in groups.into_iter() {
                    if let Some(mut group_p) = self.groups.get_mut(&group.id) {
                    if let Some(group_p) = self.groups.get_mut(&group.id) {
                        *group_p = group;
                    } else {
                        self.groups.insert(group.id.clone(), group);

D src/signal.glade => src/signal.glade +0 -134
@@ 1,134 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
  <requires lib="gtk+" version="3.22"/>
  <requires lib="libhandy" version="0.0"/>
  <!-- interface-css-provider-path style.css -->
  <object class="HdyApplicationWindow">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkHeaderBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkButton">
                <property name="label">gtk-go-back</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkStack">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkScrolledWindow">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkViewport">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkBox">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="orientation">vertical</property>
                        <child>
                          <object class="GtkListBox">
                            <property name="visible">True</property>
                            <property name="can_focus">False</property>
                            <child>
                              <object class="GtkListBoxRow">
                                <property name="width_request">100</property>
                                <property name="height_request">80</property>
                                <property name="visible">True</property>
                                <property name="can_focus">True</property>
                                <child>
                                  <placeholder/>
                                </child>
                              </object>
                            </child>
                          </object>
                          <packing>
                            <property name="expand">False</property>
                            <property name="fill">True</property>
                            <property name="position">0</property>
                          </packing>
                        </child>
                        <child>
                          <object class="GtkEntry">
                            <property name="visible">True</property>
                            <property name="can_focus">True</property>
                          </object>
                          <packing>
                            <property name="expand">False</property>
                            <property name="fill">True</property>
                            <property name="position">2</property>
                          </packing>
                        </child>
                        <child>
                          <object class="GtkButton">
                            <property name="label" translatable="yes">Send Message</property>
                            <property name="visible">True</property>
                            <property name="can_focus">True</property>
                            <property name="receives_default">True</property>
                          </object>
                          <packing>
                            <property name="expand">False</property>
                            <property name="fill">True</property>
                            <property name="position">3</property>
                          </packing>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="name">chats</property>
                <property name="title" translatable="yes">page0</property>
              </packing>
            </child>
            <child>
              <object class="GtkScrolledWindow" id="msg-scroll">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="name">messages</property>
                <property name="title" translatable="yes">page1</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

M src/signal.rs => src/signal.rs +4 -0
@@ 23,6 23,10 @@ pub enum SignalRequest {
        to: String,
        message: String,
    },
    SendGroupMessage {
        chat_id: String,
        message: String,
    },
    GetMessageList {
        id: String,
    },

D src/util.rs => src/util.rs +0 -13
@@ 1,13 0,0 @@
/// Takes in an elapsed amount of milliseconds and represents it as a string, i.e. 45000 -> 45 sec
pub fn parse_time(elapsed: u128) -> String {
    let elapsed = elapsed / 1000;
    if elapsed < 60 {
        format!("{} sec", elapsed)
    } else if elapsed < 3600 {
        format!("{} min", elapsed / 60)
    } else if elapsed < (3600 * 24) {
        format!("{} hours", elapsed / 3600)
    } else {
        format!("{} days", elapsed / (3600 * 24))
    }
}