signal-cli/client-golang/signal-cli-api.go
mc2contributor 8506e7db53
Create signal-cli-api.go
An interface for `signal-cli daemon --socket ...` to separate command responses from incoming messages.  This also solved a problem I had with using the `--tcp` option inside a LXC container, since signal-cli binds to 127.0.0.1 and I needed access from outside the container.
2023-03-20 16:41:12 -06:00

99 lines
2.1 KiB
Go

/**
Accept commands on TCP socket and send to signal-cli daemon on UNIX socket.
Immediate responses are returned via the TCP socket, and other incoming
messages are logged to stdout.
With this program running as a service, we can follow incoming messages with:
journalctl -fu signal-cli-api --no-tail
**/
package main
import (
"bufio"
"fmt"
"log"
"net"
"time"
)
func main() {
unixSocketPath := "/tmp/signal-cli/socket"
unixConn, err := net.Dial("unix", unixSocketPath)
if err != nil {
log.Fatal(err)
}
defer unixConn.Close()
tcpPort := "5780"
tcpListener, err := net.Listen("tcp", ":"+tcpPort)
if err != nil {
log.Fatal(err)
}
defer tcpListener.Close()
responses := make(chan string)
// Read messages from the UNIX socket
go func() {
scanner := bufio.NewScanner(unixConn)
for scanner.Scan() {
resp := scanner.Text()
if isCommandResponse(resp) {
responses <- resp
} else {
log.Println(resp)
}
}
if err := scanner.Err(); err != nil {
log.Println(err)
}
}()
for {
// Wait for TCP connection
tcpConn, err := tcpListener.Accept()
if err != nil {
log.Println(err)
continue
}
// Read command
cmd, err := bufio.NewReader(tcpConn).ReadString('\n')
if err != nil {
log.Println(err)
tcpConn.Close()
continue
}
// Write command to UNIX socket
fmt.Fprint(unixConn, cmd)
// Read responses
select {
case msg := <-responses:
fmt.Fprintln(tcpConn, msg)
case <-time.After(2 * time.Second):
fmt.Fprint(tcpConn, "Timed out")
}
// Close TCP connection
tcpConn.Close()
}
}
/** Check if signal-cli json-rpc message is a command response.
Command response & error formats:
{"jsonrpc":"2.0","result":{...},"id":1678829060000}
{"jsonrpc":"2.0","result":[...],"id":1678829060000}
{"jsonrpc":"2.0","error":{...},"id":1678829060000}
Incoming message format:
{"jsonrpc":"2.0","method":"receive","params":{...}}
This assumes signal-cli doesn't change jsonrpc format or version.
It would be more robust to parse the JSON, but shouldn't be needed.
**/
func isCommandResponse(resp string) bool {
return len(resp) >= 26 && resp[:26] != "{\"jsonrpc\":\"2.0\",\"method\":"
}