mirror of
https://github.com/aljazceru/signal-cli-rest-api.git
synced 2025-12-23 09:34:24 +01:00
Merge pull request #20 from danijelst/master
Add Endpoint for link to existing device
This commit is contained in:
119
README.md
119
README.md
@@ -2,17 +2,15 @@
|
|||||||
|
|
||||||
This project creates a small dockerized REST API around [signal-cli](https://github.com/AsamK/signal-cli).
|
This project creates a small dockerized REST API around [signal-cli](https://github.com/AsamK/signal-cli).
|
||||||
|
|
||||||
|
At the moment, the following functionality is exposed via REST:
|
||||||
|
|
||||||
At the moment, the following functionality is exposed via REST:
|
- Register a number
|
||||||
|
- Verify the number using the code received via SMS
|
||||||
|
- Send message (+ attachment) to multiple recipients
|
||||||
|
|
||||||
* Register a number
|
## Examples
|
||||||
* Verify the number using the code received via SMS
|
|
||||||
* Send message (+ attachment) to multiple recipients
|
|
||||||
|
|
||||||
|
Sample `docker-compose.yml`file:
|
||||||
## Examples
|
|
||||||
|
|
||||||
Sample `docker-compose.yml`file:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
version: "3"
|
version: "3"
|
||||||
@@ -29,95 +27,104 @@ services:
|
|||||||
|
|
||||||
Sample REST API calls:
|
Sample REST API calls:
|
||||||
|
|
||||||
* Register a number (with SMS verification)
|
- Register a number (with SMS verification)
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/<number>'```
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/<number>'`
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291'```
|
|
||||||
|
|
||||||
* Register a number (with voice verification)
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291'`
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" --data '{"use_voice": true}' 'http://127.0.0.1:8080/v1/register/<number>'```
|
- Register a number (with voice verification)
|
||||||
|
|
||||||
e.g:
|
`curl -X POST -H "Content-Type: application/json" --data '{"use_voice": true}' 'http://127.0.0.1:8080/v1/register/<number>'`
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" --data '{"use_voice": true}' 'http://127.0.0.1:8080/v1/register/+431212131491291'```
|
|
||||||
|
|
||||||
* Verify the number using the code received via SMS/voice
|
e.g:
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/<number>/verify/<verification code>'```
|
`curl -X POST -H "Content-Type: application/json" --data '{"use_voice": true}' 'http://127.0.0.1:8080/v1/register/+431212131491291'`
|
||||||
|
|
||||||
e.g:
|
- Verify the number using the code received via SMS/voice
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291/verify/123-456'```
|
|
||||||
|
|
||||||
* Send a message to multiple recipients
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/<number>/verify/<verification code>'`
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "number": "<number>", "recipients": ["<recipient1>", "<recipient2>"]}' 'http://127.0.0.1:8080/v2/send'```
|
e.g:
|
||||||
|
|
||||||
e.g:
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291/verify/123-456'`
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello World!", "number": "+431212131491291", "recipients": ["+4354546464654", "+4912812812121"]}' 'http://127.0.0.1:8080/v2/send'```
|
- Send a message to multiple recipients
|
||||||
|
|
||||||
* Send a message (+ base64 encoded attachment) to multiple recipients
|
`curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "number": "<number>", "recipients": ["<recipient1>", "<recipient2>"]}' 'http://127.0.0.1:8080/v2/send'`
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "base64_attachments": ["<base64 encoded attachment>"], "number": "<number>", "recipients": ["<recipient1>", "<recipient2>"]}' 'http://127.0.0.1:8080/v2/send'```
|
e.g:
|
||||||
|
|
||||||
* Send a message to a group
|
`curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello World!", "number": "+431212131491291", "recipients": ["+4354546464654", "+4912812812121"]}' 'http://127.0.0.1:8080/v2/send'`
|
||||||
|
|
||||||
The group id can be obtained via the "List groups" REST call.
|
- Send a message (+ base64 encoded attachment) to multiple recipients
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "number": "<number>", "recipients": ["<group id>"]}' 'http://127.0.0.1:8080/v2/send'```
|
`curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "base64_attachments": ["<base64 encoded attachment>"], "number": "<number>", "recipients": ["<recipient1>", "<recipient2>"]}' 'http://127.0.0.1:8080/v2/send'`
|
||||||
|
|
||||||
e.g:
|
- Send a message to a group
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello World!", "number": "+431212131491291", "recipients": ["group.ckRzaEd4VmRzNnJaASAEsasa", "+4912812812121"]}' 'http://127.0.0.1:8080/v2/send'```
|
The group id can be obtained via the "List groups" REST call.
|
||||||
|
|
||||||
* Receive messages
|
`curl -X POST -H "Content-Type: application/json" -d '{"message": "<message>", "number": "<number>", "recipients": ["<group id>"]}' 'http://127.0.0.1:8080/v2/send'`
|
||||||
|
|
||||||
|
e.g:
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello World!", "number": "+431212131491291", "recipients": ["group.ckRzaEd4VmRzNnJaASAEsasa", "+4912812812121"]}' 'http://127.0.0.1:8080/v2/send'`
|
||||||
|
|
||||||
|
- Receive messages
|
||||||
|
|
||||||
Fetch all new messages in the inbox of the specified number.
|
Fetch all new messages in the inbox of the specified number.
|
||||||
|
|
||||||
```curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/receive/<number>'```
|
`curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/receive/<number>'`
|
||||||
|
|
||||||
e.g:
|
|
||||||
|
|
||||||
```curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/receive/+431212131491291'```
|
|
||||||
|
|
||||||
* Create a new group
|
|
||||||
|
|
||||||
Create a new group with the specified name and members.
|
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"name": "<group name>", "members": ["<member1>", "<member2>"]}' 'http://127.0.0.1:8080/v1/groups/<number>'```
|
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
```curl -X POST -H "Content-Type: application/json" -d '{"name": "my group", "members": ["+4354546464654", "+4912812812121"]}' 'http://127.0.0.1:8080/v1/groups/+431212131491291'```
|
`curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/receive/+431212131491291'`
|
||||||
|
|
||||||
* List groups
|
- Create a new group
|
||||||
|
|
||||||
```curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/<number>'```
|
Create a new group with the specified name and members.
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" -d '{"name": "<group name>", "members": ["<member1>", "<member2>"]}' 'http://127.0.0.1:8080/v1/groups/<number>'`
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
```curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291'```
|
`curl -X POST -H "Content-Type: application/json" -d '{"name": "my group", "members": ["+4354546464654", "+4912812812121"]}' 'http://127.0.0.1:8080/v1/groups/+431212131491291'`
|
||||||
|
|
||||||
* Delete a group
|
- List groups
|
||||||
|
|
||||||
Delete the group with the given group id. The group id can be obtained via the "List groups" REST call.
|
`curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/<number>'`
|
||||||
|
|
||||||
```curl -X DELETE -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/<number>/<group id>'```
|
e.g:
|
||||||
|
|
||||||
e.g:
|
`curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291'`
|
||||||
|
|
||||||
```curl -X DELETE -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291/ckRzaEd4VmRzNnJaASAEsasa'```
|
- Delete a group
|
||||||
|
|
||||||
|
Delete the group with the given group id. The group id can be obtained via the "List groups" REST call.
|
||||||
|
|
||||||
|
`curl -X DELETE -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/<number>/<group id>'`
|
||||||
|
|
||||||
|
e.g:
|
||||||
|
|
||||||
|
`curl -X DELETE -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291/ckRzaEd4VmRzNnJaASAEsasa'`
|
||||||
|
|
||||||
|
- Link a device
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/qrcodelink?<device name>'`
|
||||||
|
|
||||||
|
e.g:
|
||||||
|
|
||||||
|
`curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/qrcodelink?HomeAssistant'`
|
||||||
|
|
||||||
|
This provides a QR-Code image. In case of an error a JSON object will be returned.
|
||||||
|
|
||||||
|
Due to security reason of Signal, the provided QR-Code will change with each request.
|
||||||
|
|
||||||
The following REST API endpoints are **deprecated and no longer maintained!**
|
The following REST API endpoints are **deprecated and no longer maintained!**
|
||||||
|
|
||||||
```/v1/send```
|
`/v1/send`
|
||||||
|
|
||||||
In case you need more functionality, please **file a ticket** or **create a PR**
|
In case you need more functionality, please **file a ticket** or **create a PR**
|
||||||
|
|||||||
114
src/main.go
114
src/main.go
@@ -1,19 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/h2non/filetype"
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/h2non/filetype"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const groupPrefix = "group."
|
const groupPrefix = "group."
|
||||||
@@ -31,7 +34,6 @@ func convertInternalGroupIdToGroupId(internalId string) string {
|
|||||||
return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId))
|
return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getStringInBetween(str string, start string, end string) (result string) {
|
func getStringInBetween(str string, start string, end string) (result string) {
|
||||||
i := strings.Index(str, start)
|
i := strings.Index(str, start)
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
@@ -126,7 +128,7 @@ func send(c *gin.Context, attachmentTmpDir string, signalCliConfig string, numbe
|
|||||||
cmd = append(cmd, attachmentTmpPaths...)
|
cmd = append(cmd, attachmentTmpPaths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := runSignalCli(cmd)
|
_, err := runSignalCli(true, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupTmpFiles(attachmentTmpPaths)
|
cleanupTmpFiles(attachmentTmpPaths)
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
@@ -138,7 +140,7 @@ func send(c *gin.Context, attachmentTmpDir string, signalCliConfig string, numbe
|
|||||||
func getGroups(number string, signalCliConfig string) ([]GroupEntry, error) {
|
func getGroups(number string, signalCliConfig string) ([]GroupEntry, error) {
|
||||||
groupEntries := []GroupEntry{}
|
groupEntries := []GroupEntry{}
|
||||||
|
|
||||||
out, err := runSignalCli([]string{"--config", signalCliConfig, "-u", number, "listGroups", "-d"})
|
out, err := runSignalCli(true, []string{"--config", signalCliConfig, "-u", number, "listGroups", "-d"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return groupEntries, err
|
return groupEntries, err
|
||||||
}
|
}
|
||||||
@@ -195,35 +197,46 @@ func getGroups(number string, signalCliConfig string) ([]GroupEntry, error) {
|
|||||||
return groupEntries, nil
|
return groupEntries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSignalCli(args []string) (string, error) {
|
func runSignalCli(wait bool, args []string) (string, error) {
|
||||||
cmd := exec.Command("signal-cli", args...)
|
cmd := exec.Command("signal-cli", args...)
|
||||||
var errBuffer bytes.Buffer
|
if wait {
|
||||||
var outBuffer bytes.Buffer
|
var errBuffer bytes.Buffer
|
||||||
cmd.Stderr = &errBuffer
|
var outBuffer bytes.Buffer
|
||||||
cmd.Stdout = &outBuffer
|
cmd.Stderr = &errBuffer
|
||||||
|
cmd.Stdout = &outBuffer
|
||||||
|
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
done <- cmd.Wait()
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-time.After(60 * time.Second):
|
|
||||||
err := cmd.Process.Kill()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "", errors.New("process killed as timeout reached")
|
|
||||||
case err := <-done:
|
done := make(chan error, 1)
|
||||||
if err != nil {
|
go func() {
|
||||||
return "", errors.New(errBuffer.String())
|
done <- cmd.Wait()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-time.After(60 * time.Second):
|
||||||
|
err := cmd.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", errors.New("process killed as timeout reached")
|
||||||
|
case err := <-done:
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New(errBuffer.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return outBuffer.String(), nil
|
||||||
|
} else {
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cmd.Start()
|
||||||
|
buf := bufio.NewReader(stdout) // Notice that this is not in a loop
|
||||||
|
line, _, _ := buf.ReadLine()
|
||||||
|
return string(line), nil
|
||||||
}
|
}
|
||||||
return outBuffer.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -277,7 +290,7 @@ func main() {
|
|||||||
command = append(command, "--voice")
|
command = append(command, "--voice")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := runSignalCli(command)
|
_, err := runSignalCli(true, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -299,7 +312,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := runSignalCli([]string{"--config", *signalCliConfig, "-u", number, "verify", token})
|
_, err := runSignalCli(true, []string{"--config", *signalCliConfig, "-u", number, "verify", token})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -384,7 +397,7 @@ func main() {
|
|||||||
number := c.Param("number")
|
number := c.Param("number")
|
||||||
|
|
||||||
command := []string{"--config", *signalCliConfig, "-u", number, "receive", "-t", "1", "--json"}
|
command := []string{"--config", *signalCliConfig, "-u", number, "receive", "-t", "1", "--json"}
|
||||||
out, err := runSignalCli(command)
|
out, err := runSignalCli(true, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -424,12 +437,12 @@ func main() {
|
|||||||
cmd := []string{"--config", *signalCliConfig, "-u", number, "updateGroup", "-n", req.Name, "-m"}
|
cmd := []string{"--config", *signalCliConfig, "-u", number, "updateGroup", "-n", req.Name, "-m"}
|
||||||
cmd = append(cmd, req.Members...)
|
cmd = append(cmd, req.Members...)
|
||||||
|
|
||||||
out, err := runSignalCli(cmd)
|
out, err := runSignalCli(true, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
internalGroupId := getStringInBetween(out, `"`, `"`)
|
internalGroupId := getStringInBetween(out, `"`, `"`)
|
||||||
c.JSON(201, gin.H{"id": convertInternalGroupIdToGroupId(internalGroupId)})
|
c.JSON(201, gin.H{"id": convertInternalGroupIdToGroupId(internalGroupId)})
|
||||||
})
|
})
|
||||||
@@ -461,12 +474,43 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = runSignalCli([]string{"--config", *signalCliConfig, "-u", number, "quitGroup", "-g", string(groupId)})
|
_, err = runSignalCli(true, []string{"--config", *signalCliConfig, "-u", number, "quitGroup", "-g", string(groupId)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.GET("/v1/qrcodelink", func(c *gin.Context) {
|
||||||
|
deviceName := c.Query("device_name")
|
||||||
|
|
||||||
|
if deviceName == "" {
|
||||||
|
c.JSON(400, gin.H{"error": "Please provide a name for the device"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := []string{"--config", *signalCliConfig, "link", "-n", deviceName}
|
||||||
|
|
||||||
|
tsdeviceLink, err := runSignalCli(false, command)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := qrcode.New(string(tsdeviceLink), qrcode.Medium)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
q.DisableBorder = true
|
||||||
|
var png []byte
|
||||||
|
png, err = q.PNG(256)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(200, "image/png", png)
|
||||||
|
})
|
||||||
|
|
||||||
router.Run()
|
router.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user