diff --git a/Dockerfile b/Dockerfile index 10bdf53..e9e71f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -ARG SIGNAL_CLI_VERSION=0.14.3 -ARG LIBSIGNAL_CLIENT_VERSION=0.92.1 +ARG SIGNAL_CLI_VERSION=0.14.4.1 +ARG LIBSIGNAL_CLIENT_VERSION=0.94.1 ARG SWAG_VERSION=1.16.4 @@ -16,7 +16,7 @@ RUN dpkg-reconfigure debconf --frontend=noninteractive \ && apt-get update \ && apt-get -y install --no-install-recommends \ wget git locales zip unzip \ - file build-essential libz-dev zlib1g-dev binutils \ + file build-essential libz-dev zlib1g-dev binutils openjdk-25-jdk \ && rm -rf /var/lib/apt/lists/* #COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries @@ -40,6 +40,15 @@ ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork" ENV LANG en_US.UTF-8 +RUN cd /tmp \ + && wget https://services.gradle.org/distributions/gradle-9.5.1-bin.zip \ + && unzip -d /opt/gradle gradle-9.5.1-bin.zip + +ENV PATH=$PATH:/opt/gradle/gradle-9.5.1/bin +RUN git clone https://github.com/AsamK/signal-cli.git --branch v${SIGNAL_CLI_VERSION} --single-branch signal-cli-source \ + && cd signal-cli-source \ + && /opt/gradle/gradle-9.5.1/bin/gradle jsonSchemas + RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION} RUN cd /tmp/ \ @@ -101,9 +110,7 @@ RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefau # manually add the json schemas for the receive V1 endpoint to the docs RUN cd /tmp/signal-cli-rest-api-src/docs \ - && wget https://github.com/Gara-Dorta/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz \ - && mkdir signal-cli-schemas \ - && tar xf signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz -C signal-cli-schemas \ + && cp -r /tmp/signal-cli-source/build/generated/META-INF/schemas signal-cli-schemas \ && go run add_v1_receive_schemas.go signal-cli-schemas # build signal-cli-rest-api diff --git a/plugins/README.md b/plugins/README.md index 6d7d7a2..17e76f3 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -40,45 +40,56 @@ The definition file (with the file suffix `.def`) contains some metadata which i ``` endpoint: my-custom-send-endpoint/:number method: POST +version: 2 ``` -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 `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 `:`. + +If you write a new plugin, it is recommended to use the `version: 2` of the plugin mechanism. (`version: 1` is deprecated!) 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. +The script file (with the file suffix `.lua`) contains the implementation of the endpoint. Each plugin must implement a `exec` function and can optionally implement a `init` function. The `exec` function gets called whenever the plugin is called. The `init` function only gets called once during the startup and can be used to perform initialization tasks. Example: ``` -local http = require("http") -local json = require("json") +-- +function exec() + local http = require("http") + local json = require("json") -local url = "http://127.0.0.1:8080/v2/send" + local url = "http://127.0.0.1:8080/v2/send" -local customEndpointPayload = json.decode(pluginInputData.payload) + local customEndpointPayload = json.decode(pluginInputData.payload) -local sendEndpointPayload = { - recipients = {customEndpointPayload.recipient}, - message = customEndpointPayload.message, - number = pluginInputData.Params.number -} + local sendEndpointPayload = { + recipients = {customEndpointPayload.recipient}, + message = customEndpointPayload.message, + number = pluginInputData.Params.number + } -local encodedSendEndpointPayload = json.encode(sendEndpointPayload) + local encodedSendEndpointPayload = json.encode(sendEndpointPayload) -response, error_message = http.request("POST", url, { - timeout="30s", - headers={ - Accept="*/*", - ["Content-Type"]="application/json" - }, - body=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) + pluginOutputData:SetPayload(response["body"]) + pluginOutputData:SetHttpStatusCode(response.status_code) +end + +-- optional init function +function init() + +end ``` 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. diff --git a/plugins/example.def b/plugins/example.def index f4b6dfe..0ce1784 100644 --- a/plugins/example.def +++ b/plugins/example.def @@ -1,2 +1,3 @@ endpoint: my-custom-send-endpoint/:number method: POST +version: 2 diff --git a/plugins/example.lua b/plugins/example.lua index 8a66f11..019a15f 100644 --- a/plugins/example.lua +++ b/plugins/example.lua @@ -1,27 +1,27 @@ local http = require("http") local json = require("json") -local url = "http://127.0.0.1:8080/v2/send" +function exec() + 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 customEndpointPayload = json.decode(pluginInputData.payload) + local encodedSendEndpointPayload = json.encode(sendEndpointPayload) + print(encodedSendEndpointPayload) -local sendEndpointPayload = { - recipients = {customEndpointPayload.recipient}, - message = customEndpointPayload.message, - number = pluginInputData.Params.number -} + response, error_message = http.request("POST", url, { + timeout="30s", + headers={ + Accept="*/*", + ["Content-Type"]="application/json" + }, + body=encodedSendEndpointPayload + }) -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) + pluginOutputData:SetPayload(response["body"]) + pluginOutputData:SetHttpStatusCode(response.status_code) +end diff --git a/plugins/migrate-v1-plugin-to-v2.md b/plugins/migrate-v1-plugin-to-v2.md new file mode 100644 index 0000000..4512608 --- /dev/null +++ b/plugins/migrate-v1-plugin-to-v2.md @@ -0,0 +1,29 @@ +Migrating a plugin from version `1` to version `2` is really easy. + +* Change your plugin definition (`*.def`) file + and set the version to `2` +e.g: + +``` +endpoint: my-custom-send-endpoint/:number +method: POST +version: 2 +``` + +* Change your plugin script + and implement the `exec` (and optionally the `init`) functions. + +e.g: + +``` +function exec() + -- your plugin code goes here +end + +function init() + -- if your script needs some additional setup (e.g a sqlite database, a config file, etc) + -- the initialization can be done here. +end +``` + + diff --git a/plugins/persistence/README.md b/plugins/persistence/README.md new file mode 100644 index 0000000..5f9e3ff --- /dev/null +++ b/plugins/persistence/README.md @@ -0,0 +1,35 @@ +# Persistence Plugin + +Plugin which writes every received message to a sqlite3 database. + +## Howto enable this plugin + +* Download the `persist-message.def`, `persist-message.lua`, `query-message.def` and `query-message.lua` files and put them in a `plugins` folder on your filesystem +* Create a `persistence` folder on your host system. In this folder the docker container then creates the sqlite3 database. +* Adapt your `docker-compose.yml` to enable the plugin and map the required resources into the docker container + +``` +services: + signal-cli-rest-api: + image: bbernhard/signal-cli-rest-api:latest + environment: + - MODE=json-rpc #supported modes: json-rpc, native, normal (choose the mode you want; the plugin works with all modes) + - ENABLE_PLUGINS=true # enable plugins + - "./plugins:/plugins" #map "plugins" folder from the host system into the docker container + - "./persistence;/persistence" #map "persistence" folder from the host system into the docker container + - RECEIVE_WEBHOOK_URL=http://127.0.0.1:8080/v1/plugins/persistence/persist-message #register an internal webhook endpoint +``` +* Restart your docker container + +Every message that is received is then written to the `messages.db` inside the `persistence` folder. + +The stored messages can then be received via the REST API with: + +`curl -X GET 'http://127.0.0.1:8080/v1/plugins/persistence/query-message'` + +## Debugging and Troubleshooting + +* Make sure that the docker container has write permissions to the `persistence` folder +* On the host system, check if the `messages.db` gets created in the `persistence` folder +* Check the logs. Do you see any error? + diff --git a/plugins/persistence/persist-message.def b/plugins/persistence/persist-message.def new file mode 100644 index 0000000..047e374 --- /dev/null +++ b/plugins/persistence/persist-message.def @@ -0,0 +1,3 @@ +endpoint: persistence/persist-message +method: POST +version: 2 diff --git a/plugins/persistence/persist-message.lua b/plugins/persistence/persist-message.lua new file mode 100644 index 0000000..34f1c3d --- /dev/null +++ b/plugins/persistence/persist-message.lua @@ -0,0 +1,37 @@ +local http = require("http") +local json = require("json") +local sqlite = require("sqlite3").new(); + +function exec() + ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rw" }); + if ok then + local data = json.decode(pluginInputData.payload); + if data.params and data.params.envelope and data.params.envelope.dataMessage then + local strippedPayload = json.encode(data.params.envelope) + res, err = sqlite:exec("insert into messages(data) values(?)", strippedPayload) + if err == nil then + pluginOutputData:SetHttpStatusCode(200) + else + pluginOutputData:SetHttpStatusCode(400) + pluginOutputData:SetPayload("Couldn't persist data to sqlite db") + end + else + pluginOutputData:SetHttpStatusCode(200) + end + else + pluginOutputData:SetHttpStatusCode(400) + pluginOutputData:SetPayload("Couldn't persist data to sqlite db") + end +end + +function init() + ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rwc" }); + if ok then + res, err = sqlite:exec("create table if not exists messages (id INTEGER PRIMARY KEY, data json, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"); + if err ~= nil then + print(err) + return nil, err + end + end + return nil, nil +end diff --git a/plugins/persistence/query-message.def b/plugins/persistence/query-message.def new file mode 100644 index 0000000..00bbec5 --- /dev/null +++ b/plugins/persistence/query-message.def @@ -0,0 +1,3 @@ +endpoint: persistence/query-message +method: GET +version: 2 diff --git a/plugins/persistence/query-message.lua b/plugins/persistence/query-message.lua new file mode 100644 index 0000000..40e2542 --- /dev/null +++ b/plugins/persistence/query-message.lua @@ -0,0 +1,35 @@ +local http = require("http") +local json = require("json") +local sqlite = require("sqlite3").new(); + +function exec() + ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rw" }); + if ok then + res, err = sqlite:query("select data, timestamp from messages") + if err == nil then + for _, row in ipairs(res) do + row.data = json.decode(row.data) + end + pluginOutputData:SetPayload(json.encode(res)) + pluginOutputData:SetHttpStatusCode(200) + else + pluginOutputData:SetHttpStatusCode(400) + pluginOutputData:SetPayload("Couldn't query data from sqlite db") + end + else + pluginOutputData:SetHttpStatusCode(400) + pluginOutputData:SetPayload("Couldn't query data from sqlite db") + end +end + +function init() + ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rwc" }); + if ok then + res, err = sqlite:exec("create table if not exists messages (id INTEGER PRIMARY KEY, data json, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)"); + if err ~= nil then + print(err) + return nil, err + end + end + return nil, nil +end diff --git a/src/go.mod b/src/go.mod index a348eed..9f5a509 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,6 +3,7 @@ module github.com/bbernhard/signal-cli-rest-api go 1.24.0 require ( + github.com/bbernhard/gluasql v0.2.0 github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/cyphar/filepath-securejoin v0.2.4 github.com/gabriel-vasile/mimetype v1.4.8 @@ -17,13 +18,14 @@ require ( github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.4 github.com/tidwall/sjson v1.2.5 - github.com/yuin/gopher-lua v1.1.1 + github.com/yuin/gopher-lua v1.1.2 gopkg.in/yaml.v2 v2.4.0 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf layeh.com/gopher-luar v1.0.11 ) require ( + filippo.io/edwards25519 v1.2.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.12.8 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect @@ -36,13 +38,16 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.24.0 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.44 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect diff --git a/src/go.sum b/src/go.sum index 5bb1df4..4e60dac 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,10 +1,16 @@ +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/bbernhard/gluasql v0.2.0 h1:2UanAHDSbNQeWTr+utubePjItTOSVRE157zuC5rIl3g= +github.com/bbernhard/gluasql v0.2.0/go.mod h1:swUFFVYyknwnRX8bfeHaKtNLc1/oeWftlzvlBx8pW5Y= github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs= github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -18,6 +24,10 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59 h1:puVbTWGX6h0pdi6J8n6dilweYk7jU8LElfw8yBxLHEk= +github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59/go.mod h1:aVtgxAf6Bfs0hCj+KzIH7Y1aAxg7/7FlslouCh94VVQ= +github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8 h1:h1DBe5+9JIArCVsBV14fA+RHDXWY8ynUheDL5ZVPOTg= +github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= @@ -26,6 +36,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -42,21 +54,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk= +github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw= +github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b h1:9HQYGbaDnuRLMuM//SZVkZJ43ANmMStSAQAx4aQX3II= +github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= @@ -67,23 +93,35 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -122,8 +160,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= -github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= -github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA= +github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8= +go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -154,6 +196,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -169,11 +213,17 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk= +google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/main.go b/src/main.go index a18a49f..9d6870c 100644 --- a/src/main.go +++ b/src/main.go @@ -352,6 +352,18 @@ func main() { } for _, pluginConfig := range pluginConfigs.Configs { + if pluginConfig.Version > 1 { + err = pluginHandler.InitPlugin(pluginConfig) + if err != nil { + log.Error("Couldn't initialize plugin ", pluginConfig.Endpoint) + continue + } + } else { + log.Info("Plugin ", pluginConfig.Endpoint, " still uses plugin version 1. Consider migrating to version 2! (see https://github.com/bbernhard/signal-cli-rest-api/plugins/migrate-v1-plugin-to-v2.md)") + } + + log.Info("Registering plugin ", pluginConfig.Endpoint) + if pluginConfig.Method == "GET" { plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig)) } else if pluginConfig.Method == "POST" { diff --git a/src/plugin_loader.go b/src/plugin_loader.go index 0487fa9..43a37d3 100644 --- a/src/plugin_loader.go +++ b/src/plugin_loader.go @@ -1,17 +1,20 @@ package main import ( + "errors" + "io" + "net/http" + "strings" + + gluasql "github.com/bbernhard/gluasql" "github.com/bbernhard/signal-cli-rest-api/api" "github.com/bbernhard/signal-cli-rest-api/utils" "github.com/cjoudrey/gluahttp" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" - "github.com/yuin/gopher-lua" - "io" + lua "github.com/yuin/gopher-lua" luajson "layeh.com/gopher-json" - "layeh.com/gopher-luar" - "net/http" - "strings" + luar "layeh.com/gopher-luar" ) type PluginInputData struct { @@ -41,7 +44,7 @@ func (p *PluginOutputData) HttpStatusCode() int { return p.httpStatusCode } -func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) { +func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) { jsonData, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"}) @@ -78,8 +81,10 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) { l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData)) l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) luajson.Preload(l) + gluasql.Preload(l) defer l.Close() if err := l.DoFile(pluginConfig.ScriptPath); err != nil { + log.Error("Error executing lua script: ", err) c.JSON(400, api.Error{Msg: err.Error()}) return } @@ -87,16 +92,138 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) { c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload()) } +func execPluginV2(c *gin.Context, pluginConfig utils.PluginConfig) { + jsonData, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"}) + log.Error(err.Error()) + return + } + + pluginInputData := &PluginInputData{ + Params: make(map[string]string), + QueryParams: make(map[string]string), + Payload: string(jsonData), + } + + pluginOutputData := &PluginOutputData{ + payload: "", + httpStatusCode: 200, + } + + parts := strings.Split(pluginConfig.Endpoint, "/") + for _, part := range parts { + if strings.HasPrefix(part, ":") { + paramName := strings.TrimPrefix(part, ":") + pluginInputData.Params[paramName] = c.Param(paramName) + } + } + + queryParams := c.Request.URL.Query() + for key, values := range queryParams { + pluginInputData.QueryParams[key] = values[0] + } + + l := lua.NewState() + l.SetGlobal("pluginInputData", luar.New(l, pluginInputData)) + l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData)) + l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) + luajson.Preload(l) + gluasql.Preload(l) + defer l.Close() + if err := l.DoFile(pluginConfig.ScriptPath); err != nil { + log.Error("Error executing lua script: ", err) + c.JSON(400, api.Error{Msg: err.Error()}) + return + } + + // Get global "exec" + lv := l.GetGlobal("exec") + + // Check if it exists and is a function + if fn, ok := lv.(*lua.LFunction); ok { + err := l.CallByParam(lua.P{ + Fn: fn, + NRet: 1, // exec function returns one value + Protect: true, + }) + + if err != nil { + log.Error("Couldn't execute plugin: ", err.Error()) + c.JSON(400, "Couldn't execute plugin: "+err.Error()) + return + } + + ret := l.Get(-1) + l.Pop(1) + + if ret != lua.LNil { + log.Error("Couldn't execute plugin") + c.JSON(400, "Couldn't execute plugin") + } + c.Data( + pluginOutputData.HttpStatusCode(), + "application/json", + []byte(pluginOutputData.Payload()), + ) + } else { + log.Error("Couldn't execute plugin. No exec function implemented!") + c.JSON(400, "Couldn't execute plugin. No exec function implemented!") + } +} + type plugHandler struct { } func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc { fn := func(c *gin.Context) { - execPlugin(c, pluginConfig) + if pluginConfig.Version == 1 { + execPluginV1(c, pluginConfig) + } else { + execPluginV2(c, pluginConfig) + } } return gin.HandlerFunc(fn) } +func (p plugHandler) InitPlugin(pluginConfig utils.PluginConfig) error { + l := lua.NewState() + l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) + luajson.Preload(l) + gluasql.Preload(l) + defer l.Close() + err := l.DoFile(pluginConfig.ScriptPath) + if err != nil { + log.Error("Error executing lua script: ", err) + } + + // Get global "init" + lv := l.GetGlobal("init") + + // Check if it exists and is a function + if fn, ok := lv.(*lua.LFunction); ok { + err := l.CallByParam(lua.P{ + Fn: fn, + NRet: 2, // init function returns two values + Protect: true, + }) + + if err != nil { + return err + } + + _ = l.Get(-2) + errVal := l.Get(-1) + l.Pop(2) + + if errVal != lua.LNil { + return errors.New("Couldn't initialize lua script: " + errVal.String()) + } + } + + return nil +} + // exported -var PluginHandler plugHandler +var PluginHandler plugHandler \ No newline at end of file diff --git a/src/utils/plugin_config.go b/src/utils/plugin_config.go index 4063375..fb97558 100644 --- a/src/utils/plugin_config.go +++ b/src/utils/plugin_config.go @@ -13,6 +13,7 @@ import ( type PluginConfig struct { Endpoint string `yaml:"endpoint"` Method string `yaml:"method"` + Version int `yaml:"version,omitempty"` ScriptPath string } @@ -26,7 +27,6 @@ type PluginConfigs struct { func (c *PluginConfigs) Load(baseDirectory string) error { baseDirectory = filepath.Clean(baseDirectory) - root, err := os.OpenRoot(baseDirectory) if err != nil { return err @@ -37,11 +37,9 @@ func (c *PluginConfigs) Load(baseDirectory string) error { if err != nil { return err } - if d.IsDir() { return nil } - if filepath.Ext(path) != ".def" { return nil } @@ -63,14 +61,13 @@ func (c *PluginConfigs) Load(baseDirectory string) error { } var pluginConfig PluginConfig + pluginConfig.Version = 1 // default; overridden by yaml if present if err = yaml.Unmarshal(data, &pluginConfig); err != nil { return err } pluginConfig.ScriptPath = strings.TrimSuffix(path, filepath.Ext(path)) + ".lua" c.Configs = append(c.Configs, pluginConfig) - return nil }) - return err -} +} \ No newline at end of file diff --git a/src/utils/plugin_handler.go b/src/utils/plugin_handler.go index d2931b6..b432efd 100644 --- a/src/utils/plugin_handler.go +++ b/src/utils/plugin_handler.go @@ -6,4 +6,5 @@ import ( type PluginHandler interface { ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc + InitPlugin(pluginConfig PluginConfig) error }