Compare commits

..

No commits in common. "master" and "0.99-pre" have entirely different histories.

43 changed files with 9131 additions and 10041 deletions

View File

@ -1,43 +0,0 @@
name: Check Generated Docs
on:
workflow_call:
permissions:
contents: read
jobs:
check-docs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.0.0
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum
- name: Regenerate docs
working-directory: src
run: go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault
- name: Build json schemas
run: |
export JAVA_HOME="$JAVA_HOME_25_X64"
export PATH="$JAVA_HOME/bin:$PATH"
SIGNAL_CLI_VERSION="$(grep -m1 '^ARG SIGNAL_CLI_VERSION=' Dockerfile | cut -d= -f2)"
git clone --depth 1 --branch v${SIGNAL_CLI_VERSION} https://github.com/AsamK/signal-cli/ signal-cli
cd signal-cli
./gradlew jsonSchemas
cp -r build/generated/META-INF/schemas ${GITHUB_WORKSPACE}/src/docs/signal-cli-schemas
- name: Inject receive schemas
working-directory: src/docs
run: |
go run add_v1_receive_schemas.go signal-cli-schemas
- name: Fail if docs are out of date
run: |
git diff --exit-code -- src/docs/docs.go src/docs/swagger.json src/docs/swagger.yaml

View File

@ -8,16 +8,14 @@ on:
branches: branches:
- '**' #every branch - '**' #every branch
permissions: {}
jobs: jobs:
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@v3
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Install Podman - name: Install Podman
@ -27,11 +25,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@master
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@ -1,38 +0,0 @@
name: Deploy API Docs to GitHub Pages
on:
release:
types:
- published
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build-and-deploy:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Pages
uses: actions/configure-pages@v6
- name: Build static documentation bundle
run: |
src/docs/build-static-bundle.sh site "${{ github.event.release.tag_name }}"
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

View File

@ -4,24 +4,18 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
description: 'Version' description: 'Version'
required: true required: true
permissions:
contents: read
jobs: jobs:
check-docs:
uses: ./.github/workflows/check-docs.yml
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: check-docs
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@v3
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Install Podman - name: Install Podman
@ -30,11 +24,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@master
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@ -4,24 +4,18 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
description: 'Version' description: 'Version'
required: true required: true
permissions:
contents: read
jobs: jobs:
check-docs:
uses: ./.github/workflows/check-docs.yml
setup: setup:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: check-docs
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@v3
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Install Podman - name: Install Podman
@ -30,11 +24,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@master
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

1
.gitignore vendored
View File

@ -2,4 +2,3 @@ signal-cli-config
src/main src/main
src/signal-cli-rest-api src/signal-cli-rest-api
.idea/ .idea/
./persistence

View File

@ -1,7 +1,9 @@
ARG SIGNAL_CLI_VERSION=0.14.7 ARG SIGNAL_CLI_VERSION=0.14.2
ARG LIBSIGNAL_CLIENT_VERSION=0.99.1 ARG LIBSIGNAL_CLIENT_VERSION=0.90.0
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.14.2+morph027+2
ARG SWAG_VERSION=1.16.4 ARG SWAG_VERSION=1.16.4
ARG GRAALVM_VERSION=25.0.2
ARG BUILD_VERSION_ARG=unset ARG BUILD_VERSION_ARG=unset
@ -10,18 +12,12 @@ FROM golang:1.26-trixie AS buildcontainer
ARG SIGNAL_CLI_VERSION ARG SIGNAL_CLI_VERSION
ARG LIBSIGNAL_CLIENT_VERSION ARG LIBSIGNAL_CLIENT_VERSION
ARG SWAG_VERSION ARG SWAG_VERSION
ARG GRAALVM_VERSION
ARG BUILD_VERSION_ARG ARG BUILD_VERSION_ARG
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION
RUN dpkg-reconfigure debconf --frontend=noninteractive \ COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
&& apt-get update \ COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch
&& apt-get -y install --no-install-recommends \
wget git locales zip unzip \
file build-essential libz-dev zlib1g-dev binutils \
&& rm -rf /var/lib/apt/lists/*
#COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
RUN wget https://github.com/bbernhard/libsignal-client-builds/releases/download/v${LIBSIGNAL_CLIENT_VERSION}/libsignal-client-build-v${LIBSIGNAL_CLIENT_VERSION}.tar.gz -O /tmp/libsignal-client.tar.gz
RUN cd /tmp && mkdir -p /tmp/libsignal-client-libraries && tar xf libsignal-client.tar.gz && mv x86-64 armv7 arm64 -t libsignal-client-libraries
# use architecture specific libsignal_jni.so # use architecture specific libsignal_jni.so
RUN arch="$(uname -m)"; \ RUN arch="$(uname -m)"; \
@ -32,6 +28,13 @@ RUN arch="$(uname -m)"; \
*) echo "Unknown architecture" && exit 1 ;; \ *) echo "Unknown architecture" && exit 1 ;; \
esac; esac;
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 \
&& rm -rf /var/lib/apt/lists/*
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
dpkg-reconfigure --frontend=noninteractive locales && \ dpkg-reconfigure --frontend=noninteractive locales && \
update-locale LANG=en_US.UTF-8 update-locale LANG=en_US.UTF-8
@ -40,29 +43,71 @@ ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"
ENV LANG en_US.UTF-8 ENV LANG en_US.UTF-8
#RUN cd /tmp/ \
# && git clone https://github.com/swaggo/swag.git swag-${SWAG_VERSION} \
# && cd swag-${SWAG_VERSION} \
# && git checkout -q v${SWAG_VERSION} \
# && make -s < /dev/null > /dev/null \
# && cp /tmp/swag-${SWAG_VERSION}/swag /usr/bin/swag \
# && rm -r /tmp/swag-${SWAG_VERSION}
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION} RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
RUN cd /tmp/ \ RUN cd /tmp/ \
&& wget -nv https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz -O /tmp/signal-cli.tar.gz \ && wget -nv https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz -O /tmp/signal-cli.tar.gz \
&& tar xf signal-cli.tar.gz && tar xf signal-cli.tar.gz
# build native image with graalvm
RUN arch="$(uname -m)"; \
case "$arch" in \
aarch64) wget -nv https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAALVM_VERSION}/graalvm-community-jdk-${GRAALVM_VERSION}_linux-aarch64_bin.tar.gz -O /tmp/gvm.tar.gz ;; \
armv7l) echo "GRAALVM doesn't support 32bit" ;; \
x86_64) wget -nv https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAALVM_VERSION}/graalvm-community-jdk-${GRAALVM_VERSION}_linux-x64_bin.tar.gz -O /tmp/gvm.tar.gz ;; \
*) echo "Invalid architecture" ;; \
esac;
RUN if [ "$(uname -m)" = "x86_64" ]; then \ RUN if [ "$(uname -m)" = "x86_64" ]; then \
cd /tmp \ cd /tmp \
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \ && git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \ && cd signal-cli-${SIGNAL_CLI_VERSION}-source \
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/x86-64/signal-cli-native /tmp/signal-cli-native \ && git checkout -q v${SIGNAL_CLI_VERSION} \
&& chmod +x /tmp/signal-cli-native; \ && cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \
&& export GRAALVM_HOME=/tmp/graalvm \
&& export PATH=/tmp/graalvm/bin:$PATH \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
&& sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' src/main/java/org/asamk/signal/BaseConfig.java \
&& ./gradlew build \
&& ./gradlew installDist \
&& ls build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls build/install/signal-cli/lib/libsignal-client-* && echo "\n\n" && exit 1) \
&& cd /tmp \
&& cp signal-cli-${SIGNAL_CLI_VERSION}-source/build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal-client.jar \
&& zip -qu libsignal-client.jar libsignal_jni.so \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
&& git apply /tmp/signal-cli-native.patch \
&& ./gradlew -q nativeCompile; \
elif [ "$(uname -m)" = "aarch64" ] ; then \ elif [ "$(uname -m)" = "aarch64" ] ; then \
cd /tmp \ echo "Use native image from @morph027 (https://packaging.gitlab.io/signal-cli/) for arm64 - many thanks to @morph027" \
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \ && curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \ && echo "deb [signed-by=/usr/share/keyrings/signal-cli-native.pgp] https://packaging.gitlab.io/signal-cli signalcli main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/arm64/signal-cli-native /tmp/signal-cli-native \ && mkdir -p /tmp/signal-cli-native \
&& chmod +x /tmp/signal-cli-native; \ && cd /tmp/signal-cli-native \
#&& wget https://gitlab.com/packaging/signal-cli/-/jobs/3716873649/artifacts/download?file_type=archive -O /tmp/signal-cli-native/archive.zip \
#&& unzip archive.zip \
#&& mv signal-cli-native-arm64/*deb . \
&& apt-get update \
&& apt-get download signal-cli-native=${SIGNAL_CLI_NATIVE_PACKAGE_VERSION} \
&& ar x *.deb \
&& tar xf data.tar.gz \
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
&& cp /tmp/signal-cli-native/usr/bin/signal-cli-native /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
elif [ "$(uname -m)" = "armv7l" ] ; then \ elif [ "$(uname -m)" = "armv7l" ] ; then \
echo "GRAALVM doesn't support 32bit" \ echo "GRAALVM doesn't support 32bit" \
&& echo "Creating temporary file, otherwise the below copy doesn't work for armv7" \ && echo "Creating temporary file, otherwise the below copy doesn't work for armv7" \
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \ && mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
&& touch /tmp/signal-cli-native; \ && touch /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
else \ else \
echo "Unknown architecture"; \ echo "Unknown architecture"; \
fi; fi;
@ -94,22 +139,10 @@ COPY src/main.go /tmp/signal-cli-rest-api-src/
COPY src/go.mod /tmp/signal-cli-rest-api-src/ COPY src/go.mod /tmp/signal-cli-rest-api-src/
COPY src/go.sum /tmp/signal-cli-rest-api-src/ COPY src/go.sum /tmp/signal-cli-rest-api-src/
COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/ COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/
COPY src/docs/add_v1_receive_schemas.go /tmp/signal-cli-rest-api-src/docs/add_v1_receive_schemas.go
RUN ls -la /tmp/signal-cli-rest-api-src
# build the docs
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault --outputTypes "go,json"
# 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/AsamK/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 \
&& go run add_v1_receive_schemas.go signal-cli-schemas
# build signal-cli-rest-api # build signal-cli-rest-api
RUN ls -la /tmp/signal-cli-rest-api-src
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v
@ -144,7 +177,7 @@ RUN dpkg-reconfigure debconf --frontend=noninteractive \
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION} COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native COPY --from=buildcontainer /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@ -1,16 +1,16 @@
diff --git a/build.gradle.kts b/build.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts
index 64e29294..5d0e53cc 100644 index 5d127e4c..7ca155f9 100644
--- a/build.gradle.kts --- a/build.gradle.kts
+++ b/build.gradle.kts +++ b/build.gradle.kts
@@ -104,6 +104,7 @@ dependencies { @@ -91,6 +91,7 @@ dependencies {
implementation(libs.micronaut.json.schema.generator) implementation(libs.logback)
} implementation(libs.zxing)
implementation(project(":libsignal-cli")) implementation(project(":libsignal-cli"))
+ implementation(files("/tmp/libsignal-client.jar")) + implementation(files("/tmp/libsignal-client.jar"))
testImplementation(libs.junit.jupiter) testImplementation(libs.junit.jupiter)
testImplementation(platform(libs.junit.jupiter.bom)) testImplementation(platform(libs.junit.jupiter.bom))
@@ -120,6 +121,10 @@ configurations { @@ -107,6 +108,10 @@ configurations {
} }
} }

View File

@ -11,7 +11,7 @@
window.onload = function () { window.onload = function () {
// Begin Swagger UI call region // Begin Swagger UI call region
const ui = SwaggerUIBundle({ const ui = SwaggerUIBundle({
url: "swagger.json", url: "src/docs/swagger.json", //Location of Open API spec in the repo
dom_id: '#swagger-ui', dom_id: '#swagger-ui',
deepLinking: true, deepLinking: true,
presets: [ presets: [

View File

@ -40,56 +40,45 @@ The definition file (with the file suffix `.def`) contains some metadata which i
``` ```
endpoint: my-custom-send-endpoint/:number endpoint: my-custom-send-endpoint/:number
method: POST 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 `method` parameter specifies the HTTP method that is used for the endpoint registration.
# The script file # The script file
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. The script file (with the file suffix `.lua`) contains the implementation of the endpoint.
Example: Example:
``` ```
-- local http = require("http")
function exec() local json = require("json")
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 = { local sendEndpointPayload = {
recipients = {customEndpointPayload.recipient}, recipients = {customEndpointPayload.recipient},
message = customEndpointPayload.message, message = customEndpointPayload.message,
number = pluginInputData.Params.number number = pluginInputData.Params.number
} }
local encodedSendEndpointPayload = json.encode(sendEndpointPayload) local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
response, error_message = http.request("POST", url, { response, error_message = http.request("POST", url, {
timeout="30s", timeout="30s",
headers={ headers={
Accept="*/*", Accept="*/*",
["Content-Type"]="application/json" ["Content-Type"]="application/json"
}, },
body=encodedSendEndpointPayload body=encodedSendEndpointPayload
}) })
pluginOutputData:SetPayload(response["body"]) pluginOutputData:SetPayload(response["body"])
pluginOutputData:SetHttpStatusCode(response.status_code) 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. 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.

View File

@ -1,3 +1,2 @@
endpoint: my-custom-send-endpoint/:number endpoint: my-custom-send-endpoint/:number
method: POST method: POST
version: 2

View File

@ -1,27 +1,27 @@
local http = require("http") local http = require("http")
local json = require("json") local json = require("json")
function exec() 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 sendEndpointPayload = {
recipients = {customEndpointPayload.recipient},
message = customEndpointPayload.message,
number = pluginInputData.Params.number
}
local encodedSendEndpointPayload = json.encode(sendEndpointPayload) local customEndpointPayload = json.decode(pluginInputData.payload)
print(encodedSendEndpointPayload)
response, error_message = http.request("POST", url, { local sendEndpointPayload = {
timeout="30s", recipients = {customEndpointPayload.recipient},
headers={ message = customEndpointPayload.message,
Accept="*/*", number = pluginInputData.Params.number
["Content-Type"]="application/json" }
},
body=encodedSendEndpointPayload
})
pluginOutputData:SetPayload(response["body"]) local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
pluginOutputData:SetHttpStatusCode(response.status_code) print(encodedSendEndpointPayload)
end
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)

View File

@ -1,29 +0,0 @@
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
```

View File

@ -1,35 +0,0 @@
# 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?

View File

@ -1,3 +0,0 @@
endpoint: persistence/persist-message
method: POST
version: 2

View File

@ -1,37 +0,0 @@
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

View File

@ -1,3 +0,0 @@
endpoint: persistence/query-message
method: GET
version: 2

View File

@ -1,35 +0,0 @@
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

View File

@ -173,6 +173,10 @@ type TrustIdentityRequest struct {
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"` TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
} }
type SendMessageResponse struct {
Timestamp string `json:"timestamp"`
}
type RemoteDeleteResponse struct { type RemoteDeleteResponse struct {
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
} }
@ -320,14 +324,8 @@ func (a *Api) RegisterNumber(c *gin.Context) {
err = a.signalClient.RegisterNumber(number, req.UseVoice, req.Captcha) err = a.signalClient.RegisterNumber(number, req.UseVoice, req.Captcha)
if err != nil { if err != nil {
switch err.(type) { c.JSON(400, gin.H{"error": err.Error()})
case *client.InvalidTransportError: return
c.JSON(400, Error{Msg: "Couldn't use SMS verification to register the specified number. Wait 60 seconds and try again with {\"use_voice\": true}"})
return
default:
c.JSON(400, Error{Msg: err.Error()})
return
}
} }
c.Writer.WriteHeader(201) c.Writer.WriteHeader(201)
} }
@ -472,6 +470,7 @@ func (a *Api) VerifyRegisteredNumber(c *gin.Context) {
// @Router /v1/send [post] // @Router /v1/send [post]
// @Deprecated // @Deprecated
func (a *Api) Send(c *gin.Context) { func (a *Api) Send(c *gin.Context) {
var req SendMessageV1 var req SendMessageV1
err := c.BindJSON(&req) err := c.BindJSON(&req)
if err != nil { if err != nil {
@ -484,12 +483,12 @@ func (a *Api) Send(c *gin.Context) {
base64Attachments = append(base64Attachments, req.Base64Attachment) base64Attachments = append(base64Attachments, req.Base64Attachment)
} }
resp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup) timestamp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup)
if err != nil { if err != nil {
c.JSON(400, Error{Msg: err.Error()}) c.JSON(400, Error{Msg: err.Error()})
return return
} }
c.JSON(201, resp) c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt(timestamp.Timestamp, 10)})
} }
// @Summary Send a signal message. // @Summary Send a signal message.
@ -497,7 +496,7 @@ func (a *Api) Send(c *gin.Context) {
// @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes. // @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes.
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Success 201 {object} ds.SendMessageResponse // @Success 201 {object} SendMessageResponse
// @Failure 400 {object} SendMessageError // @Failure 400 {object} SendMessageError
// @Param data body SendMessageV2 true "Input Data" // @Param data body SendMessageV2 true "Input Data"
// @Router /v2/send [post] // @Router /v2/send [post]
@ -569,7 +568,7 @@ func (a *Api) SendV2(c *gin.Context) {
return return
} }
c.JSON(201, data) c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt((*data)[0].Timestamp, 10)})
} }
func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) { func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) {

View File

@ -44,9 +44,6 @@ func (attachmentEntry *AttachmentEntry) extractMetaData(attachmentData string) {
} }
attachmentEntry.Base64 = attachmentData[base64FlagIndex+len("base64,"):] attachmentEntry.Base64 = attachmentData[base64FlagIndex+len("base64,"):]
if base64FlagIndex == 0 {
return
}
metaDataKeys := map[string]string{ metaDataKeys := map[string]string{
"data:": "MimeInfo", "data:": "MimeInfo",
"filename=": "FileName", "filename=": "FileName",

View File

@ -45,9 +45,6 @@ func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *tes
{ {
"-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=", "-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=",
}, },
{
"base64 prefix at start +data", "base64,data:someData", false, "data:someData", false, "", "", "data:someData",
},
} }
attachmentTmp := flag.String("attachment-tmp-dir", string(os.PathSeparator)+"tmp"+string(os.PathSeparator), "Attachment tmp directory") attachmentTmp := flag.String("attachment-tmp-dir", string(os.PathSeparator)+"tmp"+string(os.PathSeparator), "Attachment tmp directory")

View File

@ -119,7 +119,6 @@ type GroupEntry struct {
InternalId string `json:"internal_id"` InternalId string `json:"internal_id"`
Members []string `json:"members"` Members []string `json:"members"`
Blocked bool `json:"blocked"` Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []string `json:"pending_invites"` PendingInvites []string `json:"pending_invites"`
PendingRequests []string `json:"pending_requests"` PendingRequests []string `json:"pending_requests"`
InviteLink string `json:"invite_link"` InviteLink string `json:"invite_link"`
@ -144,7 +143,6 @@ type ExpandedGroupEntry struct {
InternalId string `json:"internal_id"` InternalId string `json:"internal_id"`
Members []GroupMember `json:"members"` Members []GroupMember `json:"members"`
Blocked bool `json:"blocked"` Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []GroupMember `json:"pending_invites"` PendingInvites []GroupMember `json:"pending_invites"`
PendingRequests []GroupMember `json:"pending_requests"` PendingRequests []GroupMember `json:"pending_requests"`
InviteLink string `json:"invite_link"` InviteLink string `json:"invite_link"`
@ -479,8 +477,8 @@ func validateLinkPreview(message string, linkPreview *ds.LinkPreviewType) error
return nil return nil
} }
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.SendMessageResponse, error) { func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*SendResponse, error) {
var rawData string var resp SendResponse
var linkPreviewAttachmentEntry *AttachmentEntry = nil var linkPreviewAttachmentEntry *AttachmentEntry = nil
if len(signalCliSendRequest.Recipients) == 0 { if len(signalCliSendRequest.Recipients) == 0 {
@ -615,13 +613,23 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.S
} }
} }
rawData, err = jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request) rawData, err := jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request)
if err != nil { if err != nil {
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry) cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
return nil, err return nil, err
} }
err = json.Unmarshal([]byte(rawData), &resp)
if err != nil {
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
if strings.Contains(err.Error(), signalCliV2GroupError) {
return nil, errors.New("Cannot send message to group - please first update your profile.")
}
return nil, err
}
} else { } else {
cmd := []string{"--output", "json", "--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"} cmd := []string{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
if signalCliSendRequest.RecipientType == ds.Number { if signalCliSendRequest.RecipientType == ds.Number {
cmd = append(cmd, signalCliSendRequest.Recipients...) cmd = append(cmd, signalCliSendRequest.Recipients...)
} else if signalCliSendRequest.RecipientType == ds.Group { } else if signalCliSendRequest.RecipientType == ds.Group {
@ -709,48 +717,23 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.S
cmd = append(cmd, "--view-once") cmd = append(cmd, "--view-once")
} }
rawData, err = s.cliClient.Execute(true, cmd, signalCliSendRequest.Message) rawData, err := s.cliClient.Execute(true, cmd, signalCliSendRequest.Message)
if err != nil { if err != nil {
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry) cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
if strings.Contains(err.Error(), signalCliV2GroupError) {
return nil, errors.New("Cannot send message to group - please first update your profile.")
}
return nil, err return nil, err
} }
} resp.Timestamp, err = strconv.ParseInt(strings.TrimSuffix(rawData, "\n"), 10, 64)
if err != nil {
type SignalCliSendResponse struct { cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
Timestamp int64 `json:"timestamp"` return nil, errors.New(strings.Replace(rawData, "\n", "", -1)) //in case we can't parse the timestamp, it means signal-cli threw an error. So instead of returning the parsing error, return the actual error from signal-cli
Results []struct {
RecipientAddress struct {
Uuid string `json:"uuid"`
Number string `json:"number"`
Username string `json:"username"`
} `json:"recipientAddress"`
Type string `json:"type"`
} `json:"results"`
}
var signalCliSendResponse SignalCliSendResponse
err = json.Unmarshal([]byte(rawData), &signalCliSendResponse)
if err != nil {
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
if strings.Contains(err.Error(), signalCliV2GroupError) {
return nil, errors.New("Cannot send message to group - please first update your profile.")
} }
return nil, err
} }
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry) cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
resp := ds.SendMessageResponse{Timestamp: strconv.FormatInt(signalCliSendResponse.Timestamp, 10)}
for _, entry := range signalCliSendResponse.Results {
if entry.Type != "SUCCESS" {
if resp.Errors == nil {
resp.Errors = &ds.SendMessageErrors{}
}
sendMessageError := ds.SendMessageError{Uuid: entry.RecipientAddress.Uuid, Number: entry.RecipientAddress.Number, Username: entry.RecipientAddress.Username, Reason: entry.Type}
resp.Errors.Recipients = append(resp.Errors.Recipients, sendMessageError)
}
}
return &resp, nil return &resp, nil
} }
@ -766,8 +749,6 @@ func (s *SignalClient) About() About {
} }
func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha string) error { func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc { if s.signalCliMode == JsonRpc {
type Request struct { type Request struct {
UseVoice bool `json:"voice,omitempty"` UseVoice bool `json:"voice,omitempty"`
@ -784,11 +765,12 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
request.Captcha = captcha request.Captcha = captcha
} }
jsonRpc2Client, err = s.getJsonRpc2Client() jsonRpc2Client, err := s.getJsonRpc2Client()
if err != nil { if err != nil {
return err return err
} }
_, err = jsonRpc2Client.getRaw("register", nil, request) _, err = jsonRpc2Client.getRaw("register", nil, request)
return err
} else { } else {
command := []string{"--config", s.signalCliConfig, "-a", number, "register"} command := []string{"--config", s.signalCliConfig, "-a", number, "register"}
@ -800,16 +782,9 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
command = append(command, []string{"--captcha", captcha}...) command = append(command, []string{"--captcha", captcha}...)
} }
_, err = s.cliClient.Execute(true, command, "") _, err := s.cliClient.Execute(true, command, "")
return err
} }
if err != nil {
if !useVoice && strings.Contains(err.Error(), "StatusCode: 400 (InvalidTransportModeException)") {
return &InvalidTransportError{Description: "Couldn't use SMS verification to register number."}
}
}
return err
} }
func (s *SignalClient) UnregisterNumber(number string, deleteAccount bool, deleteLocalData bool) error { func (s *SignalClient) UnregisterNumber(number string, deleteAccount bool, deleteLocalData bool) error {
@ -916,7 +891,7 @@ func (s *SignalClient) VerifyRegisteredNumber(number string, token string, pin s
} }
} }
func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*ds.SendMessageResponse, error) { func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*SendResponse, error) {
recipientType := ds.Number recipientType := ds.Number
if isGroup { if isGroup {
recipientType = ds.Group recipientType = ds.Group
@ -925,8 +900,8 @@ func (s *SignalClient) SendV1(number string, message string, recipients []string
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments, signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments,
RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil, RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil,
QuoteMentions: nil, TextMode: nil, EditTimestamp: nil, LinkPreview: nil} QuoteMentions: nil, TextMode: nil, EditTimestamp: nil, LinkPreview: nil}
resp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
return resp, err return timestamp, err
} }
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) { func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
@ -946,7 +921,7 @@ func (s *SignalClient) getJsonRpc2Clients() []*JsonRpc2Client {
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention, func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention,
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool, quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]ds.SendMessageResponse, error) { linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]SendResponse, error) {
if len(recps) == 0 { if len(recps) == 0 {
return nil, errors.New("Please provide at least one recipient") return nil, errors.New("Please provide at least one recipient")
} }
@ -992,17 +967,17 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.") return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.")
} }
responses := []ds.SendMessageResponse{} timestamps := []SendResponse{}
for _, group := range groups { for _, group := range groups {
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments, signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp, RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions, QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce} TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
responses = append(responses, *resp) timestamps = append(timestamps, *timestamp)
} }
if len(numbers) > 0 { if len(numbers) > 0 {
@ -1010,11 +985,11 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp, RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions, QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce} TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
responses = append(responses, *resp) timestamps = append(timestamps, *timestamp)
} }
if len(usernames) > 0 { if len(usernames) > 0 {
@ -1022,14 +997,14 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp, RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions, QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce} TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
responses = append(responses, *resp) timestamps = append(timestamps, *timestamp)
} }
return &responses, nil return &timestamps, nil
} }
func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) { func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) {
@ -1347,25 +1322,6 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi
return s.updateGroupAdmins(number, groupId, admins, false) return s.updateGroupAdmins(number, groupId, admins, false)
} }
func signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry SignalCliGroupEntry) ExpandedGroupEntry {
var groupEntry ExpandedGroupEntry
groupEntry.InternalId = signalCliGroupEntry.Id
groupEntry.Name = signalCliGroupEntry.Name
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
groupEntry.Member = signalCliGroupEntry.IsMember
groupEntry.Description = signalCliGroupEntry.Description
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
groupEntry.Members = signalCliGroupEntry.Members
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
groupEntry.Admins = signalCliGroupEntry.Admins
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
return groupEntry
}
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) { func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
groupEntries := []ExpandedGroupEntry{} groupEntries := []ExpandedGroupEntry{}
@ -1396,7 +1352,22 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
} }
for _, signalCliGroupEntry := range signalCliGroupEntries { for _, signalCliGroupEntry := range signalCliGroupEntries {
groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry)) var groupEntry ExpandedGroupEntry
groupEntry.InternalId = signalCliGroupEntry.Id
groupEntry.Name = signalCliGroupEntry.Name
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
groupEntry.Description = signalCliGroupEntry.Description
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
groupEntry.Members = signalCliGroupEntry.Members
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
groupEntry.Admins = signalCliGroupEntry.Admins
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
groupEntries = append(groupEntries, groupEntry)
} }
return groupEntries, nil return groupEntries, nil
@ -1411,7 +1382,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
groupEntries := []GroupEntry{} groupEntries := []GroupEntry{}
for _, expandedGroupEntry := range expandedGroupEntries { for _, expandedGroupEntry := range expandedGroupEntries {
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name, groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description, Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink} Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
members := []string{} members := []string{}

View File

@ -23,11 +23,3 @@ type InternalError struct {
func (e *InternalError) Error() string { func (e *InternalError) Error() string {
return e.Description return e.Description
} }
type InvalidTransportError struct {
Description string
}
func (e *InvalidTransportError) Error() string {
return e.Description
}

View File

@ -1,93 +0,0 @@
package client
import (
"encoding/json"
"testing"
)
// sampleListGroupsJSON mirrors the shape of signal-cli's `listGroups` JSON output
// (see signal-cli's ListGroupsCommand / the jsonrpc man page). It contains a group
// the account is still in (isMember=true), a group it has left or been removed from
// (isMember=false) — the "ghost" group that the REST API previously could not
// distinguish — and a blocked-but-still-member group to verify the two flags are
// mapped independently.
const sampleListGroupsJSON = `[
{
"id": "Pmpi+EfPWmsxiomLe9Nx2XF9HOE483p6iKiFj65iMwI=",
"name": "Current Group",
"description": "still a member",
"isMember": true,
"isBlocked": false,
"members": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
"pendingMembers": [],
"requestingMembers": [],
"admins": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
"groupInviteLink": "",
"permissionAddMember": "EVERY_MEMBER",
"permissionSendMessage": "EVERY_MEMBER"
},
{
"id": "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MGFiY2RlZmdoaWo=",
"name": "Left Group",
"description": "removed or left",
"isMember": false,
"isBlocked": false,
"members": [],
"pendingMembers": [],
"requestingMembers": [],
"admins": [],
"groupInviteLink": ""
},
{
"id": "YmxvY2tlZGdyb3VwaWQwMDAwMDAwMDAwMDAwMDAwMDA=",
"name": "Blocked But Member",
"description": "blocked yet still a member",
"isMember": true,
"isBlocked": true,
"members": [],
"pendingMembers": [],
"requestingMembers": [],
"admins": [],
"groupInviteLink": ""
}
]`
// TestSignalCliGroupEntryToExpandedGroupEntry verifies that the signal-cli isMember
// flag is carried through to the REST ExpandedGroupEntry.Member field, independently
// of the isBlocked -> Blocked mapping.
func TestSignalCliGroupEntryToExpandedGroupEntry(t *testing.T) {
var entries []SignalCliGroupEntry
if err := json.Unmarshal([]byte(sampleListGroupsJSON), &entries); err != nil {
t.Fatalf("failed to unmarshal sample listGroups JSON: %v", err)
}
if len(entries) != 3 {
t.Fatalf("expected 3 group entries, got %d", len(entries))
}
cases := []struct {
name string
wantMember bool
wantBlocked bool
}{
{"Current Group", true, false},
{"Left Group", false, false},
{"Blocked But Member", true, true},
}
for i, c := range cases {
got := signalCliGroupEntryToExpandedGroupEntry(entries[i])
if got.Name != c.name {
t.Errorf("entry %d: Name = %q, want %q", i, got.Name, c.name)
}
if got.Member != c.wantMember {
t.Errorf("%s: Member = %v, want %v (must reflect signal-cli isMember)", c.name, got.Member, c.wantMember)
}
if got.Blocked != c.wantBlocked {
t.Errorf("%s: Blocked = %v, want %v", c.name, got.Blocked, c.wantBlocked)
}
if got.InternalId != entries[i].Id {
t.Errorf("%s: InternalId = %q, want %q", c.name, got.InternalId, entries[i].Id)
}
}
}

View File

@ -186,8 +186,6 @@ func (r *JsonRpc2Client) getRaw(command string, account *string, args interface{
ChallengeTokens: challengeTokens, ChallengeTokens: challengeTokens,
Err: errors.New(resp.Err.Message), Err: errors.New(resp.Err.Message),
} }
} else if resp.Err.Code == -4 {
return "", errors.New("Unable to complete request due to untrusted recipient keys")
} }
return "", errors.New(resp.Err.Message) return "", errors.New(resp.Err.Message)
} }

View File

@ -58,19 +58,3 @@ type GroupPermissions struct {
EditGroup string `json:"edit_group" enums:"only-admins,every-member"` EditGroup string `json:"edit_group" enums:"only-admins,every-member"`
SendMessages string `json:"send_messages" enums:"only-admins,every-member"` SendMessages string `json:"send_messages" enums:"only-admins,every-member"`
} }
type SendMessageError struct {
Username string `json:"username,omitempty"`
Number string `json:"number,omitempty"`
Uuid string `json:"uuid,omitempty"`
Reason string `json:"reason,omitempty"`
}
type SendMessageErrors struct {
Recipients []SendMessageError `json:"recipients,omitempty"`
}
type SendMessageResponse struct {
Timestamp string `json:"timestamp"`
Errors *SendMessageErrors `json:"errors,omitempty"`
}

View File

@ -4,64 +4,44 @@ These files are generated using the [swaggo/swag](https://github.com/swaggo/swag
There are two steps, first generating the docs and then running the web server. There are two steps, first generating the docs and then running the web server.
## With docker compose (recommended) ## Generating the docs
1. Build the docs Regenerate the files with your local source code changes.
```bash
docker compose build
```
2. Serve the docs
```bash
docker compose up
```
3. Go to http://127.0.0.1:8080/swagger/index.html to view the docs
* If you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html
## Locally
Install [go](https://go.dev/).
### Generating the docs
1. Set the current working dir to `src` 1. Set the current working dir to `src`
```bash ```bash
cd src cd src
``` ```
1. Run swag to generate the docs 1. Run swag to generate the docs
* Option 1, via go * Option 1, via docker
```bash ```bash
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json" docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault
``` ```
* Option 2, directly with swag * Option 2, install swag and run the command line tool
```bash ```bash
swag init --requiredByDefault --outputTypes "go,json" swag init --requiredByDefault
```
* Option 3, swag via docker
```bash
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault --outputTypes "go,json"
```
1. Set the current working dir to `src/docs`
```bash
cd docs
```
1. Add the signal-cli receive V1 schemas
* Download the `signal-cli-x.y.z-json-schemas.tar.gz` schema files from https://github.com/AsamK/signal-cli/releases
* Extract the files
* Run the script to add the schemas
```bash
go run add_v1_receive_schemas.go ./path-to-signal-cli-json-schema-folder
``` ```
### Run the web server ## Run the web server
Run the web server to visualize the generated docs. Run the web server to visualize the generated docs.
1. Navigate to the `src` folder
```bash
cd src
```
1. Run the main script 1. Run the main script
```bash * Option 1, via docker, run the command at the root of the repository
go run main.go ```bash
``` docker compose up
1. Go to http://127.0.0.1:8080/swagger/index.html ```
* Option 2, install go and run the command line tool
```bash
cd src
```
```bash
go run main.go
```
## Navigate to the docs
The docs are served at: http://127.0.0.1:8080/swagger/index.html
When serving with docker, if you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html

View File

@ -1,327 +0,0 @@
//go:build ignore
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
_ "github.com/bbernhard/signal-cli-rest-api/docs"
)
const (
goDocsPath = "docs.go"
jsonDocsPath = "swagger.json"
openMarker = "const docTemplate = `"
closeMarker = "`\n\n// SwaggerInfo"
schemesTemplateValue = "{{ marshal .Schemes }}"
schemesPlaceholderToken = "__SWAG_SCHEMES_PLACEHOLDER__"
receivePrefix = "receive."
receivePathKey = "/v1/receive/{number}"
receiveWrapper = "data.Message"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: go run update_receive_docs.go <receiveDir>\n")
os.Exit(1)
}
receiveDir := os.Args[1]
if err := run(receiveDir); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run(receiveDir string) error {
definitions := make(map[string]interface{})
titleByFile, err := addReceiveSchemas(definitions, receiveDir)
if err != nil {
return err
}
updateReceiveSchemaRefs(definitions, titleByFile)
addEnvelopeWrapperDefinition(definitions)
if err := updateDocsGo(definitions); err != nil {
return err
}
if err := updateSwaggerJSON(definitions); err != nil {
return err
}
fmt.Printf("updated %s\n", goDocsPath)
fmt.Printf("updated %s\n", jsonDocsPath)
return nil
}
func updateDocsGo(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(goDocsPath)
if err != nil {
return fmt.Errorf("read %s: %w", goDocsPath, err)
}
template, templateStart, templateEnd, err := extractDocTemplate(string(content))
if err != nil {
return err
}
updatedTemplate, err := updateJSONDocument(toValidJson(template), receiveDefinitions)
if err != nil {
return err
}
updatedTemplate = encodeForGoRawString(updatedTemplate)
updated := string(content[:templateStart]) + updatedTemplate + string(content[templateEnd:])
if err := os.WriteFile(goDocsPath, []byte(updated), 0644); err != nil {
return fmt.Errorf("write %s: %w", goDocsPath, err)
}
return nil
}
func updateSwaggerJSON(receiveDefinitions map[string]interface{}) error {
content, err := os.ReadFile(jsonDocsPath)
if err != nil {
return fmt.Errorf("read %s: %w", jsonDocsPath, err)
}
updated, err := updateJSONDocument(string(content), receiveDefinitions)
if err != nil {
return err
}
if err := os.WriteFile(jsonDocsPath, []byte(updated), 0644); err != nil {
return fmt.Errorf("write %s: %w", jsonDocsPath, err)
}
return nil
}
func extractDocTemplate(content string) (string, int, int, error) {
start := strings.Index(content, openMarker)
if start == -1 {
return "", -1, -1, fmt.Errorf("could not find docTemplate start in %s", goDocsPath)
}
start += len(openMarker)
endOffset := strings.Index(content[start:], closeMarker)
if endOffset == -1 {
return "", -1, -1, fmt.Errorf("could not find docTemplate end in %s", goDocsPath)
}
end := start + endOffset
return content[start:end], start, end, nil
}
func toValidJson(content string) string {
content = strings.ReplaceAll(content, "` + \"`\" + `", "`")
content = strings.Replace(content, schemesTemplateValue, `"`+schemesPlaceholderToken+`"`, 1)
return content
}
func encodeForGoRawString(content string) string {
content = strings.ReplaceAll(content, "`", "` + \"`\" + `")
content = strings.Replace(content, `"`+schemesPlaceholderToken+`"`, schemesTemplateValue, 1)
return content
}
func updateJSONDocument(content string, receiveDefinitions map[string]interface{}) (string, error) {
var document map[string]interface{}
if err := json.Unmarshal([]byte(content), &document); err != nil {
return "", fmt.Errorf("parse document: %w", err)
}
if err := applyReceiveSchemaUpdates(document, receiveDefinitions); err != nil {
return "", err
}
raw, err := json.MarshalIndent(document, "", " ")
if err != nil {
return "", fmt.Errorf("marshal document: %w", err)
}
return string(raw), nil
}
func addReceiveSchemas(definitions map[string]interface{}, receiveDir string) (map[string]string, error) {
entries, err := os.ReadDir(receiveDir)
if err != nil {
return nil, fmt.Errorf("read %s: %w", receiveDir, err)
}
files := make([]string, 0)
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".schema.json") {
continue
}
files = append(files, entry.Name())
}
sort.Strings(files)
titleByFile := make(map[string]string, len(files))
for _, name := range files {
fullPath := filepath.Join(receiveDir, name)
data, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("read schema file %s: %w", fullPath, err)
}
var schemaObj map[string]interface{}
if err := json.Unmarshal(data, &schemaObj); err != nil {
return nil, fmt.Errorf("parse schema file %s: %w", fullPath, err)
}
title, ok := schemaObj["title"].(string)
if !ok || strings.TrimSpace(title) == "" {
return nil, fmt.Errorf("schema file %s is missing title", fullPath)
}
titleByFile[name] = title
definitions[receivePrefix+title] = removeSchemaKeysRecursive(schemaObj, "")
}
return titleByFile, nil
}
func removeSchemaKeysRecursive(value interface{}, parentKey string) interface{} {
switch typed := value.(type) {
case map[string]interface{}:
updated := make(map[string]interface{}, len(typed))
for key, item := range typed {
if key == "$schema" || key == "$id" {
continue
}
if key == "title" && parentKey != "properties" {
continue
}
updated[key] = removeSchemaKeysRecursive(item, key)
}
return updated
case []interface{}:
updated := make([]interface{}, len(typed))
for idx, item := range typed {
updated[idx] = removeSchemaKeysRecursive(item, parentKey)
}
return updated
default:
return value
}
}
func updateReceiveSchemaRefs(definitions map[string]interface{}, titleByFile map[string]string) {
for key, value := range definitions {
if !strings.HasPrefix(key, receivePrefix) {
continue
}
definitions[key] = rewriteSchemaRefs(value, titleByFile)
}
}
func rewriteSchemaRefs(value interface{}, titleByFile map[string]string) interface{} {
switch typed := value.(type) {
case map[string]interface{}:
updated := make(map[string]interface{}, len(typed))
for k, v := range typed {
if k == "$ref" {
if refValue, ok := v.(string); ok {
if strings.HasSuffix(refValue, ".schema.json") {
base := filepath.Base(refValue)
if title, exists := titleByFile[base]; exists {
updated[k] = "#/definitions/" + receivePrefix + title
continue
}
}
}
}
updated[k] = rewriteSchemaRefs(v, titleByFile)
}
return updated
case []interface{}:
updated := make([]interface{}, len(typed))
for idx, item := range typed {
updated[idx] = rewriteSchemaRefs(item, titleByFile)
}
return updated
default:
return value
}
}
func addEnvelopeWrapperDefinition(definitions map[string]interface{}) {
definitions[receiveWrapper] = map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"account": map[string]interface{}{"type": "string"},
"envelope": map[string]interface{}{"$ref": "#/definitions/receive.MessageEnvelope"},
},
"required": []interface{}{"account", "envelope"},
}
}
func applyReceiveSchemaUpdates(document map[string]interface{}, receiveDefinitions map[string]interface{}) error {
definitions, err := getObject(document, "definitions")
if err != nil {
return err
}
for key := range definitions {
if strings.HasPrefix(key, receivePrefix) || key == receiveWrapper {
delete(definitions, key)
}
}
for key, value := range receiveDefinitions {
definitions[key] = value
}
paths, err := getObject(document, "paths")
if err != nil {
return err
}
receivePath, err := getObject(paths, receivePathKey)
if err != nil {
return err
}
receiveGet, err := getObject(receivePath, "get")
if err != nil {
return err
}
responses, err := getObject(receiveGet, "responses")
if err != nil {
return err
}
response200, err := getObject(responses, "200")
if err != nil {
return err
}
response200["schema"] = map[string]interface{}{
"$ref": "#/definitions/" + receiveWrapper,
}
return nil
}
func getObject(parent map[string]interface{}, key string) (map[string]interface{}, error) {
value, ok := parent[key]
if !ok {
return nil, fmt.Errorf("missing key %q", key)
}
obj, ok := value.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("key %q is not an object", key)
}
return obj, nil
}

View File

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DOCS_DIR="$ROOT_DIR/src/docs"
INDEX_FILE="$DOCS_DIR/index.html"
OUT_DIR="${1:?output directory argument is required}"
DOCS_VERSION="${2:?docs version argument is required}"
NORMALIZED_DOCS_VERSION="${DOCS_VERSION#v}"
echo "Normalized docs version: $NORMALIZED_DOCS_VERSION"
rm -rf "$OUT_DIR"
mkdir -p "$OUT_DIR"
cp "$DOCS_DIR/swagger.json" "$OUT_DIR/swagger.json"
cp "$INDEX_FILE" "$OUT_DIR/index.html"
touch "$OUT_DIR/.nojekyll"
jq --arg version "$NORMALIZED_DOCS_VERSION" '.info.version = $version' "$OUT_DIR/swagger.json" > "$OUT_DIR/swagger.json.tmp"
mv "$OUT_DIR/swagger.json.tmp" "$OUT_DIR/swagger.json"
echo "Static bundle created at: $OUT_DIR"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2590
src/docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
module github.com/bbernhard/signal-cli-rest-api module github.com/bbernhard/signal-cli-rest-api
go 1.25.0 go 1.24.0
require ( require (
github.com/bbernhard/gluasql v0.2.0
github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9
github.com/cyphar/filepath-securejoin v0.2.4 github.com/cyphar/filepath-securejoin v0.2.4
github.com/gabriel-vasile/mimetype v1.4.8 github.com/gabriel-vasile/mimetype v1.4.8
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.0
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.1 github.com/sirupsen/logrus v1.9.1
@ -18,14 +17,13 @@ require (
github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4 github.com/swaggo/swag v1.16.4
github.com/tidwall/sjson v1.2.5 github.com/tidwall/sjson v1.2.5
github.com/yuin/gopher-lua v1.1.2 github.com/yuin/gopher-lua v1.1.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
layeh.com/gopher-luar v1.0.11 layeh.com/gopher-luar v1.0.11
) )
require ( require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.12.8 // indirect github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect
@ -38,16 +36,13 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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-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/goccy/go-json v0.10.5 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
@ -57,11 +52,11 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.14.0 // indirect golang.org/x/arch v0.14.0 // indirect
golang.org/x/crypto v0.52.0 // indirect golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.55.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.45.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.44.0 // indirect golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -1,16 +1,10 @@
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 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 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 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= 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.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= 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/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/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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -24,10 +18,6 @@ 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
@ -36,8 +26,6 @@ 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-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 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 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 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 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= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
@ -54,35 +42,21 @@ 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/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 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-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 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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 h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= 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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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.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 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
@ -93,35 +67,23 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 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 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
@ -160,31 +122,27 @@ 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/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/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 v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
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 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -194,10 +152,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4=
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -205,25 +161,19 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= 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= 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 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -349,18 +349,6 @@ func main() {
} }
for _, pluginConfig := range pluginConfigs.Configs { 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" { if pluginConfig.Method == "GET" {
plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig)) plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
} else if pluginConfig.Method == "POST" { } else if pluginConfig.Method == "POST" {

View File

@ -1,30 +1,27 @@
package main package main
import ( import (
"errors" "github.com/yuin/gopher-lua"
"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/cjoudrey/gluahttp"
"github.com/gin-gonic/gin" "layeh.com/gopher-luar"
log "github.com/sirupsen/logrus"
lua "github.com/yuin/gopher-lua"
luajson "layeh.com/gopher-json" luajson "layeh.com/gopher-json"
luar "layeh.com/gopher-luar" "github.com/gin-gonic/gin"
"io"
log "github.com/sirupsen/logrus"
"github.com/bbernhard/signal-cli-rest-api/utils"
"github.com/bbernhard/signal-cli-rest-api/api"
"strings"
"net/http"
) )
type PluginInputData struct { type PluginInputData struct {
Params map[string]string Params map[string]string
QueryParams map[string]string QueryParams map[string]string
Payload string Payload string
} }
type PluginOutputData struct { type PluginOutputData struct {
payload string payload string
httpStatusCode int httpStatusCode int
} }
@ -44,7 +41,7 @@ func (p *PluginOutputData) HttpStatusCode() int {
return p.httpStatusCode return p.httpStatusCode
} }
func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) { func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
jsonData, err := io.ReadAll(c.Request.Body) jsonData, err := io.ReadAll(c.Request.Body)
if err != nil { if err != nil {
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"}) c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
@ -53,13 +50,13 @@ func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
} }
pluginInputData := &PluginInputData{ pluginInputData := &PluginInputData{
Params: make(map[string]string), Params: make(map[string]string),
QueryParams: make(map[string]string), QueryParams: make(map[string]string),
Payload: string(jsonData), Payload: string(jsonData),
} }
pluginOutputData := &PluginOutputData{ pluginOutputData := &PluginOutputData{
payload: "", payload: "",
httpStatusCode: 200, httpStatusCode: 200,
} }
@ -81,10 +78,8 @@ func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData)) l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader) l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
luajson.Preload(l) luajson.Preload(l)
gluasql.Preload(l)
defer l.Close() defer l.Close()
if err := l.DoFile(pluginConfig.ScriptPath); err != nil { if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
log.Error("Error executing lua script: ", err)
c.JSON(400, api.Error{Msg: err.Error()}) c.JSON(400, api.Error{Msg: err.Error()})
return return
} }
@ -92,138 +87,16 @@ func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload()) 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 { type plugHandler struct {
} }
func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc { func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc {
fn := func(c *gin.Context) { fn := func(c *gin.Context) {
if pluginConfig.Version == 1 { execPlugin(c, pluginConfig)
execPluginV1(c, pluginConfig)
} else {
execPluginV2(c, pluginConfig)
}
} }
return gin.HandlerFunc(fn) return gin.HandlerFunc(fn)
} }
func (p plugHandler) InitPlugin(pluginConfig utils.PluginConfig) error { //exported
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

View File

@ -1,18 +1,16 @@
package utils package utils
import ( import (
"gopkg.in/yaml.v2"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"gopkg.in/yaml.v2"
) )
type PluginConfig struct { type PluginConfig struct {
Endpoint string `yaml:"endpoint"` Endpoint string `yaml:"endpoint"`
Method string `yaml:"method"` Method string `yaml:"method"`
Version int `yaml:"version,omitempty"`
ScriptPath string ScriptPath string
} }
@ -42,7 +40,6 @@ func (c *PluginConfigs) Load(baseDirectory string) error {
} }
var pluginConfig PluginConfig var pluginConfig PluginConfig
pluginConfig.Version = 1
err = yaml.Unmarshal(data, &pluginConfig) err = yaml.Unmarshal(data, &pluginConfig)
if err != nil { if err != nil {
return err return err

View File

@ -6,5 +6,4 @@ import (
type PluginHandler interface { type PluginHandler interface {
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
InitPlugin(pluginConfig PluginConfig) error
} }

View File

@ -23,7 +23,6 @@ const (
MonoSpaceBegin = 6 MonoSpaceBegin = 6
StrikethroughBegin = 8 StrikethroughBegin = 8
SpoilerBegin = 9 SpoilerBegin = 9
BoldItalicBegin = 10
) )
const EscapeCharacter rune = '\\' const EscapeCharacter rune = '\\'
@ -120,14 +119,7 @@ func (l *TextstyleParser) handleToken(tokenType int, signalCliStylingType string
} else { } else {
if l.tokens.Peek().Token == tokenType { if l.tokens.Peek().Token == tokenType {
tokenBeginState := l.tokens.Pop() tokenBeginState := l.tokens.Pop()
length := getUtf16StringLength(l.fullString) - tokenBeginState.BeginPos l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(getUtf16StringLength(l.fullString)-tokenBeginState.BeginPos)+":"+signalCliStylingType)
if tokenType == BoldItalicBegin {
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Bold)
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Italic)
} else {
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+signalCliStylingType)
}
} else { } else {
l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType}) l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType})
} }
@ -145,27 +137,14 @@ func (l *TextstyleParser) Parse() (string, []string) {
nextRune := l.peek() nextRune := l.peek()
if c == '*' { if c == '*' {
if nextRune == '*' { if nextRune == '*' { //Bold
l.next() l.next()
if prevChar == EscapeCharacter {
if l.peek() == '*' { // Check for *** before treating it as ** prevChar = c
l.next() l.fullString += "**"
continue
if prevChar == EscapeCharacter {
prevChar = c
l.fullString += "***"
continue
}
l.handleToken(BoldItalicBegin, "BOLD_ITALIC")
} else {
if prevChar == EscapeCharacter {
prevChar = c
l.fullString += "**"
continue
}
l.handleToken(BoldBegin, Bold)
} }
l.handleToken(BoldBegin, Bold)
} else { //Italic } else { //Italic
if prevChar == EscapeCharacter { if prevChar == EscapeCharacter {
prevChar = c prevChar = c

View File

@ -155,11 +155,5 @@ func TestEscapeNew(t *testing.T) {
message, signalCliFormatStrings := textstyleParser.Parse() message, signalCliFormatStrings := textstyleParser.Parse()
expectMessageEqual(t, message, "Test ** * ~ Escape") expectMessageEqual(t, message, "Test ** * ~ Escape")
expectFormatStringsEqual(t, signalCliFormatStrings, []string{}) expectFormatStringsEqual(t, signalCliFormatStrings, []string{})
}
func TestBoldItalic(t *testing.T) {
textstyleParser := NewTextstyleParser("***Bold Italic Text***")
message, signalCliFormatStrings := textstyleParser.Parse()
expectMessageEqual(t, message, "Bold Italic Text")
expectFormatStringsEqual(t, signalCliFormatStrings, []string{"0:16:BOLD", "0:16:ITALIC"})
} }