1
0
Fork 0
This repository has been archived on 2023-07-19. You can view files and clone it, but cannot push or open issues or pull requests.
kekskurse.de-old/content/posts/2021-03-07-signal-dbus-golang.md

227 lines
6.3 KiB
Markdown
Raw Normal View History

2021-03-07 23:51:33 +00:00
---
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)
}
}
```