Compare commits

..

85 Commits

Author SHA1 Message Date
Bernhard B.
2fe9eb5bf3
Merge pull request #880 from bbernhard/dependabot/go_modules/src/github.com/gorilla/websocket-1.5.3
Bump github.com/gorilla/websocket from 1.5.0 to 1.5.3 in /src
2026-08-26 22:24:54 +02:00
dependabot[bot]
1491a52285
Bump github.com/gorilla/websocket from 1.5.0 to 1.5.3 in /src
Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.5.0 to 1.5.3.
- [Release notes](https://github.com/gorilla/websocket/releases)
- [Commits](https://github.com/gorilla/websocket/compare/v1.5.0...v1.5.3)

---
updated-dependencies:
- dependency-name: github.com/gorilla/websocket
  dependency-version: 1.5.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 21:37:32 +00:00
Bernhard B
a08db23a31 fixed bug in textstyleparser
* the textstyleparser now properly handles `***` and marks the
  text as bold and italic.

see #879
2026-08-16 22:51:31 +02:00
Bernhard B
5cc379fde0 only (re-)build github pages documentation when a new release is published
see #872
2026-08-09 21:39:35 +02:00
Bernhard B
1a2da6c79b updated signal-cli to v0.14.7 2026-08-01 15:06:43 +02:00
Bernhard B
54507eba20 handle status code -4 in the getRaw method of the JsonRpc2Client 2026-07-23 22:51:35 +02:00
Bernhard B
6a225e62c1 fixed bug in send endpoint
* in case a message is sent to either multiple recipients or a group,
  it is always possible that the message couldn't be sent to some
  recipients (e.g because of an untrusted identity). This is now
  reflected in the API by returning an additional `errors` object in
  that case.
2026-07-23 22:47:13 +02:00
Bernhard B
0e36e15f60 remove debug logging 2026-07-18 22:21:40 +02:00
Bernhard B
7138ffde7c fixed version in deploy-pages.yml 2026-07-18 13:09:22 +02:00
Bernhard B
7c12204889 execute deploy-pages workflow for every commit 2026-07-18 13:05:41 +02:00
Bernhard B
bef11316e3 dummy change to test github workflow 2026-07-18 13:03:20 +02:00
Bernhard B
2d03a6c0ce added debug output to deploy-pages.yml 2026-07-18 12:53:53 +02:00
Bernhard B.
d258f01d59
Merge pull request #869 from Gara-Dorta/json-schema-dowload
feat: download the json schemas from the signal-cli releases
2026-07-14 21:50:32 +02:00
Gara Dorta
b13a74f43e feat: download the json schemas from the signal-cli releases 2026-07-14 20:08:28 +02:00
Bernhard B.
be0842fdb2
Merge pull request #844 from Gara-Dorta/deploy-pages
feat: deploy pages with CI
2026-07-13 22:29:46 +02:00
Bernhard B.
17b86412f3
Merge pull request #868 from bbernhard/dependabot/go_modules/src/golang.org/x/crypto-0.52.0
Bump golang.org/x/crypto from 0.51.0 to 0.52.0 in /src
2026-07-11 22:23:26 +02:00
dependabot[bot]
2ee3907d9e
Bump golang.org/x/crypto from 0.51.0 to 0.52.0 in /src
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.51.0 to 0.52.0.
- [Commits](https://github.com/golang/crypto/compare/v0.51.0...v0.52.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.52.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 21:35:10 +00:00
Bernhard B.
af64b772cd
Merge pull request #865 from bbernhard/dependabot/go_modules/src/golang.org/x/net-0.55.0
Bump golang.org/x/net from 0.47.0 to 0.55.0 in /src
2026-07-02 21:57:51 +02:00
dependabot[bot]
e34cd5af87
Bump golang.org/x/net from 0.47.0 to 0.55.0 in /src
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.47.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.47.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 06:13:13 +00:00
Bernhard B.
fe9df012f1
Merge pull request #859 from pinkustar/expose-group-membership
Expose group membership (isMember) on the /v1/groups endpoints
2026-06-25 22:14:25 +02:00
Bernhard B
a4f5855b65 updated signal-cli to v0.14.5 2026-06-11 16:12:56 +02:00
PINKU
ede08b64d8 expose group membership isMember via the REST API 2026-06-05 10:10:04 +02:00
Bernhard B
81efbd25e1 use java 25 in check-docs.yml 2026-06-01 20:47:46 +02:00
Bernhard B
9e7fa344a8 fixed typo in check-docs.yml 2026-06-01 20:35:05 +02:00
Bernhard B
25eaf7a5eb fixed bug in check-docs.yml
* forgot to add 'sudo'
2026-06-01 20:29:24 +02:00
Bernhard B
60b19339c3 updated check-docs.yml
* build signal-cli json schemas inside the CI job
2026-06-01 20:25:00 +02:00
Bernhard B.
c88c3f18c2
Merge pull request #853 from Gara-Dorta/release-fix
feat: add receive schemas to check-docks worflow
2026-06-01 20:13:58 +02:00
Bernhard B
e6810926ca fixed bug in Dockerfile
* make signal-cli-native binary executable
2026-05-28 23:58:15 +02:00
Bernhard B
595e53d397 updated gitignore 2026-05-28 23:58:15 +02:00
Bernhard B.
d4e8664273
Merge pull request #854 from SAY-5/fix-attachment-base64-prefix-oob
fix: prevent slice out of range when attachment data starts with base64,
2026-05-26 22:50:03 +02:00
Bernhard B
52d902cda4 temporary fix to get the build working again 2026-05-26 22:11:26 +02:00
Bernhard B
d1a3956c8c download gradle manually 2026-05-25 21:52:06 +02:00
Bernhard B
7d8f62a4bb updated go.sum 2026-05-25 17:33:51 +02:00
SAY-5
6b89035dd5 fix: prevent slice out of range when attachment data starts with base64,
Signed-off-by: SAY-5 <say.apm35@gmail.com>
2026-05-24 16:21:46 -07:00
Bernhard B
99157a035f renamed file 2026-05-24 23:44:21 +02:00
Bernhard B
9a2e93a6fa build json-schemas directly from the signal-cli source 2026-05-24 21:27:07 +02:00
Bernhard B
c8c461627f updated signal-cli to v0.14.4.1 2026-05-24 21:25:57 +02:00
Gara Dorta
e63536cb7a feat: add receive schemas to check-docks worflow 2026-05-23 20:51:47 +02:00
Bernhard B
6e7941b1c1 improved plugin mechanism + added sqlite3 plugin 2026-05-23 13:18:37 +02:00
Bernhard B.
5c0bd056a7
Merge pull request #842 from Gara-Dorta/receive-swagger
Add swagger definitions for the receive end point
2026-05-17 22:35:43 +02:00
Bernhard B.
ad5d3b76db
Merge pull request #849 from Gara-Dorta/check-docs-are-updated
Check docs are updated
2026-05-17 22:24:09 +02:00
Gara Dorta
0aaab36e5f Enable read of repo 2026-05-15 19:50:35 +02:00
Gara Dorta
44ac16d49c check docs before a release 2026-05-15 19:47:51 +02:00
Gara Dorta
e8c8b54d52 Merge branch 'master' into check-docs-are-updated 2026-05-15 19:41:52 +02:00
Gara Dorta
91bdd60c7a Additional refactor 2026-05-15 19:31:52 +02:00
Gara Dorta
a7c91737b8 Refactor 2026-05-15 19:28:57 +02:00
Gara Dorta
af48a4304b Regenerate docs 2026-05-15 18:55:26 +02:00
Gara Dorta
9155e505af Remove check for running swag init first 2026-05-15 18:52:32 +02:00
Gara Dorta
d0ec5b1b28 Better variable naming 2026-05-15 18:51:22 +02:00
Gara Dorta
fa0f67fa69 Remove uneeded if statement 2026-05-15 18:34:50 +02:00
Gara Dorta
7aa70683aa Simplify the add_v1_receive_schemas.go my marshalling the whole document 2026-05-15 18:29:20 +02:00
Gara Dorta
82f7151212 fix: download the schemas from signal-cli's fork 2026-05-10 23:23:39 +01:00
Gara Dorta
e68cabf88f fix: the docs regarless of architecture 2026-05-10 22:18:07 +01:00
Gara Dorta
4bbadbf29e fix: download schemas from signal-cli releases 2026-05-10 22:14:19 +01:00
Gara Dorta
b37aac4d5f Merge branch 'master' into receive-swagger 2026-05-10 22:12:48 +01:00
Bernhard B.
db63fd15e0
Merge pull request #848 from arnehuang/add-permissions-block
Add empty permissions block at workflow level
2026-05-10 23:03:19 +02:00
Bernhard B.
650367e88a
Merge pull request #847 from arnehuang/pin-actions-checkout-sha
Pin actions/checkout to a commit SHA
2026-05-10 23:02:13 +02:00
Arne Huang
69457e8f81 Add empty permissions block at workflow level
Caps GITHUB_TOKEN's blast radius. None of these workflows need any
GitHub API write scope — they only push to Docker Hub — so the safest
default is permissions: {}, matching the posture used by AsamK/signal-cli.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:00:28 -07:00
Arne Huang
2e8171d84c Pin actions/checkout to a commit SHA
Follow-up to #838: actions/checkout was the only third-party action
left on a mutable ref (@master). Pin it to v6.0.2's commit SHA, matching
the pattern used for docker/setup-qemu-action, docker/setup-buildx-action,
and docker/login-action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:56:36 -07:00
Bernhard B
419b18331d improved Dockerfile
* do not build signal-cli-native in Dockerfile, but instead use
  pre-built binary from bbernhard/libsignal-client-builds
2026-05-08 22:39:21 +02:00
Gara Dorta
16c3214862 Add update docs ci 2026-05-06 12:40:34 +01:00
Gara Dorta
6e1698e820 feat: deploy pages with CI
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 12:01:26 +01:00
Era Dorta
a168cf5547 Simplify the instructions in the readme 2026-05-04 14:59:48 +01:00
Era Dorta
1f222d7261 Add receive definitions to the docs files 2026-05-04 14:09:43 +01:00
Era Dorta
6ac432b509 Update script to also modify the json file
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 14:09:12 +01:00
Era Dorta
948a522ad8 Add back json generation 2026-05-04 13:44:20 +01:00
Era Dorta
6fa06f9611 Add back swagger.json file 2026-05-04 13:39:43 +01:00
Era Dorta
eda99213a7 fix: remove swagger.json and swagger.yaml files 2026-05-04 13:36:25 +01:00
Era Dorta
2ac55eec07 Split comments in docker build 2026-05-04 13:02:51 +01:00
Era Dorta
cb5e64a6d5 Add the schemas before building the binary 2026-05-04 13:00:45 +01:00
Era Dorta
0a7c53a10a fix: docs paths in docker build 2026-05-04 12:50:49 +01:00
Era Dorta
09252b4b87 Merge branch 'master' into receive-swagger 2026-05-04 12:10:18 +01:00
Bernhard B
881e9203c1 switch back to apt-download for arm64 package 2026-05-02 22:55:15 +02:00
Bernhard B.
e2523b102c
Merge pull request #838 from ildyria/pin-dependencies
Pin dependencies
2026-05-02 22:04:49 +02:00
Bernhard B
184231020c changed source for libsignal-client
* use github actions builds instead of precompiled shared objects
2026-04-26 23:54:22 +02:00
Bernhard B
52e058eb06 temporarily fix build 2026-04-25 23:15:31 +02:00
Bernhard B
e8a80ef896 fixed signal-cli-native version 2026-04-25 22:33:47 +02:00
Bernhard B
97197f1fbe updated signal-cli-native version 2026-04-24 19:31:57 +02:00
Bernhard B
e19dc961fd updated signal-cli-native.patch 2026-04-23 23:10:18 +02:00
Bernhard B
246ebfd4e6 updated signal-cli to v0.14.3 2026-04-23 22:14:20 +02:00
Bernhard B
40232de0d0 handle InvalidTransportModeException error
see #837
2026-04-20 23:33:19 +02:00
Era Dorta
0f2c12f28e Use tabs 2026-04-20 00:21:33 +02:00
Era Dorta
e712494d7f Only build docs on x86_64 2026-04-20 00:20:37 +02:00
Era Dorta
e0af0091c9 Add receive v1 json schemas to swagger
This is done with a custom script to embed the schemas from signal-cli into the docs.go file
2026-04-20 00:07:59 +02:00
ildyria
252389efbe Pin dependencies 2026-04-19 11:26:49 +02:00
43 changed files with 10048 additions and 9138 deletions

43
.github/workflows/check-docs.yml vendored Normal file
View File

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

38
.github/workflows/deploy-pages.yml vendored Normal file
View File

@ -0,0 +1,38 @@
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,18 +4,24 @@ 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@v3 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Install Podman - name: Install Podman
@ -24,11 +30,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@ -4,18 +4,24 @@ 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@v3 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Install Podman - name: Install Podman
@ -24,11 +30,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup needs: setup
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
ref: ${{ github.ref }} ref: ${{ github.ref }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

1
.gitignore vendored
View File

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

View File

@ -1,9 +1,7 @@
ARG SIGNAL_CLI_VERSION=0.14.2 ARG SIGNAL_CLI_VERSION=0.14.7
ARG LIBSIGNAL_CLIENT_VERSION=0.90.0 ARG LIBSIGNAL_CLIENT_VERSION=0.99.1
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
@ -12,12 +10,18 @@ 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
COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries RUN dpkg-reconfigure debconf --frontend=noninteractive \
COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch && 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/*
#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)"; \
@ -28,13 +32,6 @@ 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
@ -43,71 +40,29 @@ 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 \
&& git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \ && wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& cd signal-cli-${SIGNAL_CLI_VERSION}-source \ && tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& git checkout -q v${SIGNAL_CLI_VERSION} \ && cp signal-cli-native-v${SIGNAL_CLI_VERSION}/x86-64/signal-cli-native /tmp/signal-cli-native \
&& cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \ && chmod +x /tmp/signal-cli-native; \
&& 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 \
echo "Use native image from @morph027 (https://packaging.gitlab.io/signal-cli/) for arm64 - many thanks to @morph027" \ cd /tmp \
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \ && wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/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 \ && tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& mkdir -p /tmp/signal-cli-native \ && cp signal-cli-native-v${SIGNAL_CLI_VERSION}/arm64/signal-cli-native /tmp/signal-cli-native \
&& cd /tmp/signal-cli-native \ && chmod +x /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-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \ && touch /tmp/signal-cli-native; \
else \ else \
echo "Unknown architecture"; \ echo "Unknown architecture"; \
fi; fi;
@ -139,10 +94,22 @@ 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
@ -177,7 +144,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-${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-native /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 5d127e4c..7ca155f9 100644 index 64e29294..5d0e53cc 100644
--- a/build.gradle.kts --- a/build.gradle.kts
+++ b/build.gradle.kts +++ b/build.gradle.kts
@@ -91,6 +91,7 @@ dependencies { @@ -104,6 +104,7 @@ dependencies {
implementation(libs.logback) implementation(libs.micronaut.json.schema.generator)
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))
@@ -107,6 +108,10 @@ configurations { @@ -120,6 +121,10 @@ configurations {
} }
} }

View File

@ -40,45 +40,56 @@ 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. The script file (with the file suffix `.lua`) contains the implementation of the endpoint. Each plugin must implement a `exec` function and can optionally implement a `init` function. The `exec` function gets called whenever the plugin is called. The `init` function only gets called once during the startup and can be used to perform initialization tasks.
Example: Example:
``` ```
local http = require("http") --
local json = require("json") function exec()
local http = require("http")
local json = require("json")
local url = "http://127.0.0.1:8080/v2/send" local url = "http://127.0.0.1:8080/v2/send"
local customEndpointPayload = json.decode(pluginInputData.payload) local customEndpointPayload = json.decode(pluginInputData.payload)
local sendEndpointPayload = { 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,2 +1,3 @@
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")
local url = "http://127.0.0.1:8080/v2/send" function exec()
local url = "http://127.0.0.1:8080/v2/send"
local customEndpointPayload = json.decode(pluginInputData.payload)
local sendEndpointPayload = {
recipients = {customEndpointPayload.recipient},
message = customEndpointPayload.message,
number = pluginInputData.Params.number
}
local customEndpointPayload = json.decode(pluginInputData.payload) local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
print(encodedSendEndpointPayload)
local sendEndpointPayload = { response, error_message = http.request("POST", url, {
recipients = {customEndpointPayload.recipient}, timeout="30s",
message = customEndpointPayload.message, headers={
number = pluginInputData.Params.number Accept="*/*",
} ["Content-Type"]="application/json"
},
body=encodedSendEndpointPayload
})
local encodedSendEndpointPayload = json.encode(sendEndpointPayload) pluginOutputData:SetPayload(response["body"])
print(encodedSendEndpointPayload) pluginOutputData:SetHttpStatusCode(response.status_code)
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

@ -0,0 +1,29 @@
Migrating a plugin from version `1` to version `2` is really easy.
* Change your plugin definition (`*.def`) file
and set the version to `2`
e.g:
```
endpoint: my-custom-send-endpoint/:number
method: POST
version: 2
```
* Change your plugin script
and implement the `exec` (and optionally the `init`) functions.
e.g:
```
function exec()
-- your plugin code goes here
end
function init()
-- if your script needs some additional setup (e.g a sqlite database, a config file, etc)
-- the initialization can be done here.
end
```

View File

@ -0,0 +1,35 @@
# Persistence Plugin
Plugin which writes every received message to a sqlite3 database.
## Howto enable this plugin
* Download the `persist-message.def`, `persist-message.lua`, `query-message.def` and `query-message.lua` files and put them in a `plugins` folder on your filesystem
* Create a `persistence` folder on your host system. In this folder the docker container then creates the sqlite3 database.
* Adapt your `docker-compose.yml` to enable the plugin and map the required resources into the docker container
```
services:
signal-cli-rest-api:
image: bbernhard/signal-cli-rest-api:latest
environment:
- MODE=json-rpc #supported modes: json-rpc, native, normal (choose the mode you want; the plugin works with all modes)
- ENABLE_PLUGINS=true # enable plugins
- "./plugins:/plugins" #map "plugins" folder from the host system into the docker container
- "./persistence;/persistence" #map "persistence" folder from the host system into the docker container
- RECEIVE_WEBHOOK_URL=http://127.0.0.1:8080/v1/plugins/persistence/persist-message #register an internal webhook endpoint
```
* Restart your docker container
Every message that is received is then written to the `messages.db` inside the `persistence` folder.
The stored messages can then be received via the REST API with:
`curl -X GET 'http://127.0.0.1:8080/v1/plugins/persistence/query-message'`
## Debugging and Troubleshooting
* Make sure that the docker container has write permissions to the `persistence` folder
* On the host system, check if the `messages.db` gets created in the `persistence` folder
* Check the logs. Do you see any error?

View File

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

View File

@ -0,0 +1,37 @@
local http = require("http")
local json = require("json")
local sqlite = require("sqlite3").new();
function exec()
ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rw" });
if ok then
local data = json.decode(pluginInputData.payload);
if data.params and data.params.envelope and data.params.envelope.dataMessage then
local strippedPayload = json.encode(data.params.envelope)
res, err = sqlite:exec("insert into messages(data) values(?)", strippedPayload)
if err == nil then
pluginOutputData:SetHttpStatusCode(200)
else
pluginOutputData:SetHttpStatusCode(400)
pluginOutputData:SetPayload("Couldn't persist data to sqlite db")
end
else
pluginOutputData:SetHttpStatusCode(200)
end
else
pluginOutputData:SetHttpStatusCode(400)
pluginOutputData:SetPayload("Couldn't persist data to sqlite db")
end
end
function init()
ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rwc" });
if ok then
res, err = sqlite:exec("create table if not exists messages (id INTEGER PRIMARY KEY, data json, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)");
if err ~= nil then
print(err)
return nil, err
end
end
return nil, nil
end

View File

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

View File

@ -0,0 +1,35 @@
local http = require("http")
local json = require("json")
local sqlite = require("sqlite3").new();
function exec()
ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rw" });
if ok then
res, err = sqlite:query("select data, timestamp from messages")
if err == nil then
for _, row in ipairs(res) do
row.data = json.decode(row.data)
end
pluginOutputData:SetPayload(json.encode(res))
pluginOutputData:SetHttpStatusCode(200)
else
pluginOutputData:SetHttpStatusCode(400)
pluginOutputData:SetPayload("Couldn't query data from sqlite db")
end
else
pluginOutputData:SetHttpStatusCode(400)
pluginOutputData:SetPayload("Couldn't query data from sqlite db")
end
end
function init()
ok, err = sqlite:open("/persistence/messages.db", { cache = "shared", mode = "rwc" });
if ok then
res, err = sqlite:exec("create table if not exists messages (id INTEGER PRIMARY KEY, data json, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)");
if err ~= nil then
print(err)
return nil, err
end
end
return nil, nil
end

View File

@ -173,10 +173,6 @@ 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"`
} }
@ -324,8 +320,14 @@ 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 {
c.JSON(400, gin.H{"error": err.Error()}) switch err.(type) {
return case *client.InvalidTransportError:
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)
} }
@ -470,7 +472,6 @@ 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 {
@ -483,12 +484,12 @@ func (a *Api) Send(c *gin.Context) {
base64Attachments = append(base64Attachments, req.Base64Attachment) base64Attachments = append(base64Attachments, req.Base64Attachment)
} }
timestamp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup) resp, 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, SendMessageResponse{Timestamp: strconv.FormatInt(timestamp.Timestamp, 10)}) c.JSON(201, resp)
} }
// @Summary Send a signal message. // @Summary Send a signal message.
@ -496,7 +497,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} SendMessageResponse // @Success 201 {object} ds.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]
@ -568,7 +569,7 @@ func (a *Api) SendV2(c *gin.Context) {
return return
} }
c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt((*data)[0].Timestamp, 10)}) c.JSON(201, data)
} }
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,6 +44,9 @@ 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,6 +45,9 @@ 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,6 +119,7 @@ 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"`
@ -143,6 +144,7 @@ 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"`
@ -477,8 +479,8 @@ func validateLinkPreview(message string, linkPreview *ds.LinkPreviewType) error
return nil return nil
} }
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*SendResponse, error) { func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.SendMessageResponse, error) {
var resp SendResponse var rawData string
var linkPreviewAttachmentEntry *AttachmentEntry = nil var linkPreviewAttachmentEntry *AttachmentEntry = nil
if len(signalCliSendRequest.Recipients) == 0 { if len(signalCliSendRequest.Recipients) == 0 {
@ -613,23 +615,13 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
} }
} }
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{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"} cmd := []string{"--output", "json", "--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 {
@ -717,23 +709,48 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
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 {
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry) type SignalCliSendResponse struct {
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 Timestamp int64 `json:"timestamp"`
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
} }
@ -749,6 +766,8 @@ 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"`
@ -765,12 +784,11 @@ 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"}
@ -782,9 +800,16 @@ 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 {
@ -891,7 +916,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) (*SendResponse, error) { func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*ds.SendMessageResponse, error) {
recipientType := ds.Number recipientType := ds.Number
if isGroup { if isGroup {
recipientType = ds.Group recipientType = ds.Group
@ -900,8 +925,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}
timestamp, err := s.send(signalCliSendRequest) resp, err := s.send(signalCliSendRequest)
return timestamp, err return resp, err
} }
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) { func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
@ -921,7 +946,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) (*[]SendResponse, error) { linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]ds.SendMessageResponse, 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")
} }
@ -967,17 +992,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.")
} }
timestamps := []SendResponse{} responses := []ds.SendMessageResponse{}
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}
timestamp, err := s.send(signalCliSendRequest) resp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
timestamps = append(timestamps, *timestamp) responses = append(responses, *resp)
} }
if len(numbers) > 0 { if len(numbers) > 0 {
@ -985,11 +1010,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}
timestamp, err := s.send(signalCliSendRequest) resp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
timestamps = append(timestamps, *timestamp) responses = append(responses, *resp)
} }
if len(usernames) > 0 { if len(usernames) > 0 {
@ -997,14 +1022,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}
timestamp, err := s.send(signalCliSendRequest) resp, err := s.send(signalCliSendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
timestamps = append(timestamps, *timestamp) responses = append(responses, *resp)
} }
return &timestamps, nil return &responses, 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) {
@ -1322,6 +1347,25 @@ 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{}
@ -1352,22 +1396,7 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
} }
for _, signalCliGroupEntry := range signalCliGroupEntries { for _, signalCliGroupEntry := range signalCliGroupEntries {
var groupEntry ExpandedGroupEntry groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry))
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
@ -1382,7 +1411,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, Description: expandedGroupEntry.Description, Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description,
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink} Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
members := []string{} members := []string{}

View File

@ -23,3 +23,11 @@ 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
}

93
src/client/groups_test.go Normal file
View File

@ -0,0 +1,93 @@
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,6 +186,8 @@ 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,3 +58,19 @@ 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,44 +4,64 @@ 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.
## Generating the docs ## With docker compose (recommended)
Regenerate the files with your local source code changes. 1. Build the docs
```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 docker * Option 1, via go
```bash ```bash
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json"
``` ```
* Option 2, install swag and run the command line tool * Option 2, directly with swag
```bash ```bash
swag init --requiredByDefault swag init --requiredByDefault --outputTypes "go,json"
```
* 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
* Option 1, via docker, run the command at the root of the repository ```bash
```bash go run main.go
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

@ -0,0 +1,327 @@
//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
}

24
src/docs/build-static-bundle.sh Executable file
View File

@ -0,0 +1,24 @@
#!/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

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: "src/docs/swagger.json", //Location of Open API spec in the repo url: "swagger.json",
dom_id: '#swagger-ui', dom_id: '#swagger-ui',
deepLinking: true, deepLinking: true,
presets: [ presets: [

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,15 @@
module github.com/bbernhard/signal-cli-rest-api module github.com/bbernhard/signal-cli-rest-api
go 1.24.0 go 1.25.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.0 github.com/gorilla/websocket v1.5.3
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
@ -17,13 +18,14 @@ 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.1 github.com/yuin/gopher-lua v1.1.2
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
@ -36,13 +38,16 @@ 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
@ -52,11 +57,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.45.0 // indirect golang.org/x/crypto v0.52.0 // indirect
golang.org/x/net v0.47.0 // indirect golang.org/x/net v0.55.0 // indirect
golang.org/x/sys v0.38.0 // indirect golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.31.0 // indirect golang.org/x/text v0.37.0 // indirect
golang.org/x/tools v0.38.0 // indirect golang.org/x/tools v0.44.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,10 +1,16 @@
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 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=
@ -18,6 +24,10 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.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=
@ -26,6 +36,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-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=
@ -42,21 +54,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
@ -67,23 +93,35 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/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=
@ -122,27 +160,31 @@ 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.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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=
@ -152,8 +194,10 @@ 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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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=
@ -161,19 +205,25 @@ 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
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,6 +349,18 @@ 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,27 +1,30 @@
package main package main
import ( import (
"github.com/yuin/gopher-lua" "errors"
"github.com/cjoudrey/gluahttp"
"layeh.com/gopher-luar"
luajson "layeh.com/gopher-json"
"github.com/gin-gonic/gin"
"io" "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" "net/http"
"strings"
gluasql "github.com/bbernhard/gluasql"
"github.com/bbernhard/signal-cli-rest-api/api"
"github.com/bbernhard/signal-cli-rest-api/utils"
"github.com/cjoudrey/gluahttp"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
lua "github.com/yuin/gopher-lua"
luajson "layeh.com/gopher-json"
luar "layeh.com/gopher-luar"
) )
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
} }
@ -41,7 +44,7 @@ func (p *PluginOutputData) HttpStatusCode() int {
return p.httpStatusCode return p.httpStatusCode
} }
func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) { func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
jsonData, err := io.ReadAll(c.Request.Body) 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"})
@ -50,13 +53,13 @@ func execPlugin(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,
} }
@ -78,8 +81,10 @@ func execPlugin(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
} }
@ -87,16 +92,138 @@ func execPlugin(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) {
execPlugin(c, pluginConfig) if pluginConfig.Version == 1 {
execPluginV1(c, pluginConfig)
} else {
execPluginV2(c, pluginConfig)
}
} }
return gin.HandlerFunc(fn) return gin.HandlerFunc(fn)
} }
//exported func (p plugHandler) InitPlugin(pluginConfig utils.PluginConfig) error {
l := lua.NewState()
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
luajson.Preload(l)
gluasql.Preload(l)
defer l.Close()
err := l.DoFile(pluginConfig.ScriptPath)
if err != nil {
log.Error("Error executing lua script: ", err)
}
// Get global "init"
lv := l.GetGlobal("init")
// Check if it exists and is a function
if fn, ok := lv.(*lua.LFunction); ok {
err := l.CallByParam(lua.P{
Fn: fn,
NRet: 2, // init function returns two values
Protect: true,
})
if err != nil {
return err
}
_ = l.Get(-2)
errVal := l.Get(-1)
l.Pop(2)
if errVal != lua.LNil {
return errors.New("Couldn't initialize lua script: " + errVal.String())
}
}
return nil
}
// exported
var PluginHandler plugHandler var PluginHandler plugHandler

View File

@ -1,16 +1,18 @@
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
} }
@ -40,6 +42,7 @@ 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,4 +6,5 @@ import (
type PluginHandler interface { type PluginHandler interface {
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
InitPlugin(pluginConfig PluginConfig) error
} }

View File

@ -23,6 +23,7 @@ const (
MonoSpaceBegin = 6 MonoSpaceBegin = 6
StrikethroughBegin = 8 StrikethroughBegin = 8
SpoilerBegin = 9 SpoilerBegin = 9
BoldItalicBegin = 10
) )
const EscapeCharacter rune = '\\' const EscapeCharacter rune = '\\'
@ -119,7 +120,14 @@ 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()
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(getUtf16StringLength(l.fullString)-tokenBeginState.BeginPos)+":"+signalCliStylingType) length := getUtf16StringLength(l.fullString) - tokenBeginState.BeginPos
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})
} }
@ -137,14 +145,27 @@ func (l *TextstyleParser) Parse() (string, []string) {
nextRune := l.peek() nextRune := l.peek()
if c == '*' { if c == '*' {
if nextRune == '*' { //Bold if nextRune == '*' {
l.next() l.next()
if prevChar == EscapeCharacter {
prevChar = c if l.peek() == '*' { // Check for *** before treating it as **
l.fullString += "**" l.next()
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,5 +155,11 @@ 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"})
} }