--- 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) } } ```