227 lines
No EOL
6.3 KiB
Markdown
227 lines
No EOL
6.3 KiB
Markdown
---
|
|
title: "Using the signal-cli dbus interface in golang"
|
|
date: 2021-03-07T10:25:53+02:00
|
|
draft: false
|
|
description: "Using the signal-cli dbus interface in golang"
|
|
tags: ["Blog", "Go"]
|
|
categories: ["Programming"]
|
|
startpage: true
|
|
lang: en
|
|
---
|
|
|
|
The [signal-cli](https://github.com/AsamK/signal-cli) tool profieds a dbus interface to get and send messages from other programms. The Documentation for that is avalible in the [signal-cli wiki](https://github.com/AsamK/signal-cli/wiki/DBus-service) but that not so easy to understand if you never work with dbus before.
|
|
|
|
# Setup signal-cli
|
|
To setup signal-cli and pair it as secound device to an existing account you first need to install signal-cli, that steps based on your os. After you install it you can create a new "Pairing-Code" by runnuning `signal-cli link -n "mybot"`. The given code can used on the webpage [goqr.me](http://goqr.me/) to create an qr code, which you can scann with the signal app on your mobile device.
|
|
|
|
After that you can start signal-cli as deamon, for example with the following command with also print all incomming events as json on your cli:
|
|
|
|
```
|
|
signal-cli -u +49176XXXXXXX -o json daemon
|
|
```
|
|
|
|
# Getting Messages
|
|
First we need to connect to the DBus system, thats possible with the [godbus/dbus](https://github.com/godbus/dbus) package. The connection based on the example from the package is really easy:
|
|
```
|
|
conn, err := dbus.ConnectSessionBus()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
```
|
|
|
|
After that we need to told the dbus package which messages we want to recive and create a channel where we can revice all signales. To got the signal messages the follwoing code works for me:
|
|
|
|
```
|
|
if err = conn.AddMatchSignal(
|
|
dbus.WithMatchInterface("org.asamk.Signal"),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
c := make(chan *dbus.Signal, 10)
|
|
conn.Signal(c)
|
|
```
|
|
than we can "listen" to the channel and got the messages the signal-cli deamon sends:
|
|
|
|
```
|
|
for v := range c {
|
|
fmt.Println(v)
|
|
}
|
|
```
|
|
|
|
That will create mainly two signales im interested in, first getting a message from another conversation partner:
|
|
|
|
```
|
|
&{:1.34 /org/asamk/Signal org.asamk.Signal.MessageReceived [1615064176455 +49176XXXXXXXX [] Durchgefallen []] 11}
|
|
```
|
|
|
|
and secound getting a Sync Message, it will be send if yourself send a message from another device to a conversation partner:
|
|
|
|
```
|
|
&{:1.34 /org/asamk/Signal org.asamk.Signal.SyncMessageReceived [1615064055775 +49176XXXXXXXXX +49176XXXXXXX [] Ich bin ein test []] 8}
|
|
```
|
|
|
|
|
|
# Parsing Messages
|
|
If i just focus on 1:1 chats i could parse both kinds of events. Here two examples without error handling. First on the "Incomming Messages" :
|
|
|
|
```
|
|
type IncommingMessage struct {
|
|
Timestamp int64
|
|
Source string
|
|
Message string
|
|
Attachments []string
|
|
}
|
|
|
|
func parseMessageReceived(v *dbus.Signal) IncommingMessage {
|
|
msg := IncommingMessage{}
|
|
msg.Timestamp, _ = v.Body[0].(int64)
|
|
msg.Source = v.Body[1].(string)
|
|
msg.Message = v.Body[3].(string)
|
|
msg.Attachments = v.Body[4].([]string)
|
|
return msg
|
|
}
|
|
```
|
|
|
|
and for the Snyc Events:
|
|
|
|
```
|
|
type SyncMessage struct {
|
|
Timestamp int64
|
|
Source string
|
|
Destination string
|
|
Message string
|
|
Attachments []string
|
|
}
|
|
|
|
func parseSyncMessageReceived(v *dbus.Signal) SyncMessage {
|
|
msg := SyncMessage{}
|
|
msg.Timestamp, _ = v.Body[0].(int64)
|
|
msg.Source = v.Body[1].(string)
|
|
msg.Destination = v.Body[2].(string)
|
|
msg.Message = v.Body[4].(string)
|
|
msg.Attachments = v.Body[5].([]string)
|
|
return msg
|
|
}
|
|
```
|
|
|
|
|
|
|
|
That functions will return strucs you can easy use for your application.
|
|
|
|
# Sending Messages
|
|
|
|
For the sending we also need a dbus connection, but unlike in the receving message part we don't subscribe to the events we produse one which will be consumed by the signal-cli deamon and send as chat messages to the conversation. All other devices (like your mobile phone) will get a SyncMessage event and show that "new" message too.
|
|
|
|
First you need to create the connection like before:
|
|
|
|
```
|
|
conn, err := dbus.ConnectSessionBus()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
|
|
os.Exit(1)
|
|
}
|
|
defer conn.Close()
|
|
```
|
|
|
|
after that just send a call like this:
|
|
|
|
```
|
|
obj := conn.Object("org.asamk.Signal", "/org/asamk/Signal")
|
|
call := obj.Call("org.asamk.Signal.sendMessage",0, "Your really cool message", []string{}, "+49176XXXXXXX")
|
|
if call.Err != nil {
|
|
panic(call.Err)
|
|
}
|
|
```
|
|
|
|
|
|
# Example
|
|
|
|
Here an "full" example of getting messages (without error handling)
|
|
|
|
```
|
|
func listenToDbus() error {
|
|
conn, err := dbus.ConnectSessionBus()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
|
|
return err
|
|
}
|
|
defer conn.Close()
|
|
|
|
if err = conn.AddMatchSignal(
|
|
dbus.WithMatchInterface("org.asamk.Signal"),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
c := make(chan *dbus.Signal, 10)
|
|
conn.Signal(c)
|
|
for v := range c {
|
|
if v.Name == "org.asamk.Signal.SyncMessageReceived" {
|
|
msg := parseSyncMessageReceived(v)
|
|
// do something with msg like a callback
|
|
}
|
|
if v.Name == "org.asamk.Signal.MessageReceived" {
|
|
msg := parseMessageReceived(v)
|
|
// do something with msg like a callback
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type SyncMessage struct {
|
|
Timestamp int64
|
|
Source string
|
|
Destination string
|
|
Message string
|
|
Attachments []string
|
|
}
|
|
|
|
func parseSyncMessageReceived(v *dbus.Signal) SyncMessage {
|
|
msg := SyncMessage{}
|
|
msg.Timestamp, _ = v.Body[0].(int64)
|
|
msg.Source = v.Body[1].(string)
|
|
msg.Destination = v.Body[2].(string)
|
|
msg.Message = v.Body[4].(string)
|
|
msg.Attachments = v.Body[5].([]string)
|
|
return msg
|
|
}
|
|
|
|
type IncommingMessage struct {
|
|
Timestamp int64
|
|
Source string
|
|
Message string
|
|
Attachments []string
|
|
}
|
|
|
|
func parseMessageReceived(v *dbus.Signal) IncommingMessage {
|
|
msg := IncommingMessage{}
|
|
msg.Timestamp, _ = v.Body[0].(int64)
|
|
msg.Source = v.Body[1].(string)
|
|
msg.Message = v.Body[3].(string)
|
|
msg.Attachments = v.Body[4].([]string)
|
|
return msg
|
|
}
|
|
```
|
|
|
|
and another one to sending messages:
|
|
|
|
```
|
|
func SendMessage(to string, msg string) {
|
|
conn, err := dbus.ConnectSessionBus()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
|
|
os.Exit(1)
|
|
}
|
|
defer conn.Close()
|
|
|
|
obj := conn.Object("org.asamk.Signal", "/org/asamk/Signal")
|
|
call := obj.Call("org.asamk.Signal.sendMessage",0, msg, []string{}, to)
|
|
if call.Err != nil {
|
|
panic(call.Err)
|
|
}
|
|
}
|
|
|
|
``` |