-
Notifications
You must be signed in to change notification settings - Fork 172
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
Plugins allow to dynamically register custom endpoints without forking this project. | ||
|
||
# Why? | ||
|
||
Imagine that you want to use the Signal REST API in a software component that has some restrictions regarding the payload it supports. To give you a real world example: If you want to use the Signal REST API to send Signal notifications from your Synology NAS to your phone, the HTTP endpoint must have only "flat" parameters - i.e array parameters in the JSON payload aren't allowed. Since the `recipients` parameter in the `/v2/send` endpoint is a array parameter, the send endpoint cannot be used in the Synology NAS. In order to work around that limitation, you can write a small custom plugin in Lua, to create a custom send endpoint, which exposes a single `recipient` string instead of an array. | ||
|
||
# How to write a custom plugin | ||
|
||
In order to use plugins, you first need to enable that feature. This can be done by setting the environment variable `ENABLE_PLUGINS` to `true` in the `docker-compose.yml` file. | ||
|
||
e.g: | ||
|
||
``` | ||
services: | ||
signal-cli-rest-api: | ||
image: bbernhard/signal-cli-rest-api:latest | ||
environment: | ||
- MODE=json-rpc #supported modes: json-rpc, native, normal | ||
- ENABLE_PLUGINS=true #enable plugins | ||
``` | ||
|
||
A valid plugin consists of a definition file (with the file ending `.def`) and a matching lua script file (with the file ending `.lua`). Both of those files must have the same filename and are placed in a folder called `plugins` on the host filesystem. Now, bind mount the `plugins` folder from your host system into the `/plugins` folder inside the docker container. This can be done in the `docker-compose.yml` file: | ||
|
||
``` | ||
services: | ||
signal-cli-rest-api: | ||
image: bbernhard/signal-cli-rest-api:latest | ||
environment: | ||
- MODE=json-rpc #supported modes: json-rpc, native, normal | ||
- ENABLE_PLUGINS=true | ||
volumes: | ||
- "./signal-cli-config:/home/.local/share/signal-cli" | ||
- "./plugins:/plugins" #map "plugins" folder on host system into docker container. | ||
``` | ||
|
||
# The definition file | ||
|
||
The definition file (with the file suffix `.def`) contains some metadata which is necessary to properly register the new endpoint. A proper definition file looks like this: | ||
|
||
``` | ||
endpoint: my-custom-send-endpoint/:number | ||
method: POST | ||
``` | ||
|
||
The `endpoint` specifies the URI of the newly created endpoint. All custom endpoints are registered under the `/v1/plugins` endpoint. So, our `my-custom-send-endpoint` will be available at `/v1/plugins/my-custom-endpoint`. If you want to use variables inside the endpoint, prefix them with a `:`. | ||
|
||
The `method` parameter specifies the HTTP method that is used for the endpoint registration. | ||
|
||
# The script file | ||
|
||
The script file (with the file suffix `.lua`) contains the implementation of the endpoint. | ||
|
||
Example: | ||
|
||
``` | ||
local http = require("http") | ||
local json = require("json") | ||
local url = "http://127.0.0.1:8080/v2/send" | ||
local customEndpointPayload = json.decode(pluginInputData.payload) | ||
local sendEndpointPayload = { | ||
recipients = {customEndpointPayload.recipient}, | ||
message = customEndpointPayload.message, | ||
number = pluginInputData.Params.number | ||
} | ||
local encodedSendEndpointPayload = json.encode(sendEndpointPayload) | ||
response, error_message = http.request("POST", url, { | ||
timeout="30s", | ||
headers={ | ||
Accept="*/*", | ||
["Content-Type"]="application/json" | ||
}, | ||
body=encodedSendEndpointPayload | ||
}) | ||
pluginOutputData:SetPayload(response["body"]) | ||
pluginOutputData:SetHttpStatusCode(response.status_code) | ||
``` | ||
|
||
What the lua script does, is parse the JSON payload from the custom request, extract the `recipient` and the `message` from the payload and the `number` from the URL parameter and call the `/v2/send` endpoint with those parameters. The HTTP status code and the body that is returned by the HTTP request is then returned to the caller (this is done via the `pluginOutputData:SetPayload` and `pluginOutputData:SetHttpStatusCode` functions. | ||
|
||
If you now invoke the following curl command, a message gets sent: | ||
|
||
`curl -X POST -H "Content-Type: application/json" -d '{"message": "test", "recipient": "<recipient>"}' 'http://127.0.0.1:8080/v1/plugins/my-custom-send-endpoint/<registered signal number>'` | ||
|
||
# Pass commands from/to the lua script | ||
|
||
When a new plugin is registered, some parameters are automatically passed as global variables to the lua script: | ||
|
||
* `pluginInputData.payload`: the (JSON) payload that is passed to the custom endpoint | ||
* `pluginInputData.Params`: a map of all parameters that are part of the URL, which were defined in the definition file (i.e those parameters that were defined with `:` prefixed in the URL) | ||
|
||
In order to return values from the lua script, the following functions are available: | ||
* `pluginOutputData:SetPayload()`: Set the (JSON) payload that is returned to the caller | ||
* `pluginOutputData:SetHttpStatusCode()`: Set the HTTP status code that is returned to the caller |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
endpoint: my-custom-send-endpoint/:number | ||
method: POST |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
local http = require("http") | ||
local json = require("json") | ||
|
||
local url = "http://127.0.0.1:8080/v2/send" | ||
|
||
local customEndpointPayload = json.decode(pluginInputData.payload) | ||
|
||
local sendEndpointPayload = { | ||
recipients = {customEndpointPayload.recipient}, | ||
message = customEndpointPayload.message, | ||
number = pluginInputData.Params.number | ||
} | ||
|
||
local encodedSendEndpointPayload = json.encode(sendEndpointPayload) | ||
print(encodedSendEndpointPayload) | ||
|
||
response, error_message = http.request("POST", url, { | ||
timeout="30s", | ||
headers={ | ||
Accept="*/*", | ||
["Content-Type"]="application/json" | ||
}, | ||
body=encodedSendEndpointPayload | ||
}) | ||
|
||
pluginOutputData:SetPayload(response["body"]) | ||
pluginOutputData:SetHttpStatusCode(response.status_code) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package utils | ||
|
||
import ( | ||
"gopkg.in/yaml.v2" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
type PluginConfig struct { | ||
Endpoint string `yaml:"endpoint"` | ||
Method string `yaml:"method"` | ||
ScriptPath string | ||
} | ||
|
||
func NewPluginConfigs() *PluginConfigs { | ||
return &PluginConfigs{} | ||
} | ||
|
||
type PluginConfigs struct { | ||
Configs []PluginConfig | ||
} | ||
|
||
func (c *PluginConfigs) Load(baseDirectory string) error { | ||
|
||
err := filepath.Walk(baseDirectory, func(path string, info os.FileInfo, err error) error { | ||
if info.IsDir() { | ||
return nil | ||
} | ||
|
||
if filepath.Ext(path) != ".def" { | ||
return nil | ||
} | ||
|
||
if _, err := os.Stat(path); err == nil { | ||
data, err := ioutil.ReadFile(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var pluginConfig PluginConfig | ||
err = yaml.Unmarshal(data, &pluginConfig) | ||
if err != nil { | ||
return err | ||
} | ||
pluginConfig.ScriptPath = strings.TrimSuffix(path, filepath.Ext(path)) + ".lua" | ||
c.Configs = append(c.Configs, pluginConfig) | ||
} | ||
return nil | ||
}) | ||
|
||
return err | ||
} |