Compare commits

...

86 Commits
0.98 ... master

Author SHA1 Message Date
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
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
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
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
Bernhard B
1b1873ce17 add json-rpc-native mode to issue template 2026-04-19 21:52:44 +02:00
ildyria
252389efbe Pin dependencies 2026-04-19 11:26:49 +02:00
Bernhard B
8b8614bb18 updated signal-cli-native to latest version 2026-04-16 22:00:50 +02:00
Bernhard B.
f159947e07
Merge pull request #833 from Gara-Dorta/optional-group-creation-fields
Mark name and members as optional fields in CreateGroupRequest
2026-04-09 22:48:04 +02:00
Bernhard B.
2838e1f879
Merge pull request #828 from Gara-Dorta/additional-api-checks
refactor: add missing checks on required fields
2026-04-09 22:18:55 +02:00
Era Dorta
59d6912f21 Mark name and members as optional fields in CreateGroupRequest 2026-04-08 19:26:42 +02:00
Era Dorta
5a883826ae refactor: add missing checks on required fields 2026-04-08 19:10:48 +02:00
Bernhard B.
2a776618e9
Merge pull request #827 from Gara-Dorta/split-reaction-request-class
Split reaction request class
2026-04-07 19:53:41 +02:00
Bernhard B.
fc99aba2f4
Merge pull request #831 from philljolly/fix/jsonrpc-variable-shadowing
fix: resolve Go variable shadowing that silently swallows errors in JSON-RPC mode
2026-04-07 19:51:54 +02:00
Phill Jolliffe
a01c2a61fa fix: resolve Go variable shadowing that silently swallows errors in JSON-RPC mode
24 functions in client.go use 'jsonRpc2Client, err :=' inside if blocks
where 'var err error' is already declared at function scope. The := creates
a new block-scoped err that shadows the outer one, causing 15 functions to
silently return nil instead of the actual error in JSON-RPC mode.

Fixes #506
2026-04-07 14:12:57 +01:00
Era Dorta
e46b1c9ea7 Merge branch 'master' into split-reaction-request-class 2026-04-06 21:34:31 +02:00
Bernhard B.
33829b2fa5
Merge pull request #829 from Gara-Dorta/readme-docs
Better formatting for the docs Readme instructions
2026-04-06 21:29:09 +02:00
Era Dorta
2911bc3632 Merge branch 'master' into readme-docs 2026-04-06 21:26:17 +02:00
Bernhard B.
bc0ea66c04
Merge pull request #830 from Gara-Dorta/docs-version
Set docs version dynamically.
2026-04-06 21:07:44 +02:00
Bernhard B.
b3ba748dd3
Merge pull request #826 from Gara-Dorta/add-required-to-json-schema
Add required to json schema
2026-04-06 21:05:52 +02:00
Era Dorta
3dd0001a32 Set docs version dynamically. 2026-04-06 18:15:06 +02:00
Era Dorta
b51505c8d9 Better formatting for the docs Readme instructions 2026-04-06 17:35:45 +02:00
Era Dorta
87ab2bb398 refactor: split Reaction in two classes to mark the Reaction field as optional 2026-04-06 17:05:45 +02:00
Era Dorta
4e541848e2 docs: regenerated docs with required fields 2026-04-06 16:57:57 +02:00
Era Dorta
40f299deff fix: add omitempty and generate swagger json with required fields 2026-04-06 16:57:14 +02:00
Bernhard B
7a9b9919ef updated signal-cli-native.patch 2026-04-04 21:36:01 +02:00
Bernhard B
d1d8ddc711 updated cross build instructions 2026-04-04 21:14:48 +02:00
Bernhard B
8345672499 updated signal-cli to v0.14.2 2026-04-04 21:08:51 +02:00
Bernhard B
eb34542216 increased timeout to account for slow startup on slow machines 2026-04-01 22:47:21 +02:00
Bernhard B
7687c5240b added API endpoints for pinning/unpinning messages in groups
see #820
2026-04-01 22:47:21 +02:00
Bernhard B.
ef16efca12
Merge pull request #786 from CodeShellDev/patch-1
Update secured-signal-api (API Gateway)
2026-03-27 21:05:35 +01:00
Bernhard B
5c6fd14944 improved error handling in jsonrpc2-helper 2026-03-22 22:18:16 +01:00
Bernhard B
6ca5ff1aee added expand query parameter to /groups endpoints
see #790
2026-03-21 21:38:49 +01:00
Bernhard B
f142e8089c added new json-rpc-native mode 2026-03-21 19:49:26 +01:00
Bernhard B
d45b906aa9 regenerated swagger documentation 2026-03-21 19:48:10 +01:00
Bernhard B
5435b12e81 Revert "added query parameter 'use_only_uuid_as_identifier' to group(s) GET endpoint"
This reverts commit 44ce4fe83d964f39d6677fa46186bc1126fd069c.
2026-03-19 17:24:30 +01:00
Bernhard B
07260c0811 fixed bug in json-rpc webhook
* only post messages that were received via 'receive'

see #813
2026-03-14 15:54:49 +01:00
Bernhard B
eeacb488ad updated signal-cli-native version 2026-03-13 22:42:36 +01:00
Bernhard B
baca954dcc updated swagger documentation 2026-03-13 21:24:15 +01:00
Bernhard B
44ce4fe83d added query parameter 'use_only_uuid_as_identifier' to group(s) GET endpoint
see #790
2026-03-13 21:18:28 +01:00
CodeShell
5317ba9362
update type of secured-signal-api => API Gateway 2026-02-12 17:56:56 +00:00
42 changed files with 10310 additions and 8274 deletions

View File

@ -44,6 +44,7 @@ body:
- Normal Mode
- Native Mode
- JSON-RPC Mode
- JSON-RPC-Native Mode
- type: dropdown
validations:
required: true

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

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
ARG SIGNAL_CLI_VERSION=0.14.1
ARG LIBSIGNAL_CLIENT_VERSION=0.87.4
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.14.1+morph027+1
ARG SIGNAL_CLI_VERSION=0.14.4.1
ARG LIBSIGNAL_CLIENT_VERSION=0.94.1
ARG SWAG_VERSION=1.16.4
ARG GRAALVM_VERSION=25.0.2
ARG BUILD_VERSION_ARG=unset
@ -12,12 +10,18 @@ FROM golang:1.26-trixie AS buildcontainer
ARG SIGNAL_CLI_VERSION
ARG LIBSIGNAL_CLIENT_VERSION
ARG SWAG_VERSION
ARG GRAALVM_VERSION
ARG BUILD_VERSION_ARG
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION
COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch
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 openjdk-25-jdk \
&& 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
RUN arch="$(uname -m)"; \
@ -28,13 +32,6 @@ RUN arch="$(uname -m)"; \
*) echo "Unknown architecture" && exit 1 ;; \
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 && \
dpkg-reconfigure --frontend=noninteractive locales && \
update-locale LANG=en_US.UTF-8
@ -43,71 +40,36 @@ ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"
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 cd /tmp \
&& wget https://services.gradle.org/distributions/gradle-9.5.1-bin.zip \
&& unzip -d /opt/gradle gradle-9.5.1-bin.zip
ENV PATH=$PATH:/opt/gradle/gradle-9.5.1/bin
RUN git clone https://github.com/AsamK/signal-cli.git --branch v${SIGNAL_CLI_VERSION} --single-branch signal-cli-source \
&& cd signal-cli-source \
&& /opt/gradle/gradle-9.5.1/bin/gradle jsonSchemas
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
RUN cd /tmp/ \
&& 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
# 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 \
cd /tmp \
&& git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \
&& cd signal-cli-${SIGNAL_CLI_VERSION}-source \
&& git checkout -q v${SIGNAL_CLI_VERSION} \
&& cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \
&& export GRAALVM_HOME=/tmp/graalvm \
&& export PATH=/tmp/graalvm/bin:$PATH \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
&& sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' src/main/java/org/asamk/signal/BaseConfig.java \
&& ./gradlew build \
&& ./gradlew installDist \
&& ls build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls build/install/signal-cli/lib/libsignal-client-* && echo "\n\n" && exit 1) \
&& cd /tmp \
&& cp signal-cli-${SIGNAL_CLI_VERSION}-source/build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal-client.jar \
&& zip -qu libsignal-client.jar libsignal_jni.so \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
&& git apply /tmp/signal-cli-native.patch \
&& ./gradlew -q nativeCompile; \
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/x86-64/signal-cli-native /tmp/signal-cli-native; \
elif [ "$(uname -m)" = "aarch64" ] ; then \
echo "Use native image from @morph027 (https://packaging.gitlab.io/signal-cli/) for arm64 - many thanks to @morph027" \
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \
&& 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 \
&& mkdir -p /tmp/signal-cli-native \
&& cd /tmp/signal-cli-native \
#&& wget https://gitlab.com/packaging/signal-cli/-/jobs/3716873649/artifacts/download?file_type=archive -O /tmp/signal-cli-native/archive.zip \
#&& unzip archive.zip \
#&& mv signal-cli-native-arm64/*deb . \
&& apt-get update \
&& apt-get download signal-cli-native=${SIGNAL_CLI_NATIVE_PACKAGE_VERSION} \
&& ar x *.deb \
&& tar xf data.tar.gz \
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
&& cp /tmp/signal-cli-native/usr/bin/signal-cli-native /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
cd /tmp \
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/arm64/signal-cli-native /tmp/signal-cli-native; \
elif [ "$(uname -m)" = "armv7l" ] ; then \
echo "GRAALVM doesn't support 32bit" \
&& 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 \
&& touch /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
&& touch /tmp/signal-cli-native; \
else \
echo "Unknown architecture"; \
fi;
@ -139,10 +101,19 @@ COPY src/main.go /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/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 \
&& cp -r /tmp/signal-cli-source/build/generated/META-INF/schemas signal-cli-schemas \
&& go run add_v1_receive_schemas.go signal-cli-schemas
# build signal-cli-rest-api
RUN ls -la /tmp/signal-cli-rest-api-src
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init
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
@ -177,7 +148,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 /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/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
COPY entrypoint.sh /entrypoint.sh

View File

@ -58,13 +58,14 @@ The `signal-cli-rest-api` supports three different modes of execution, which can
* **`normal` Mode: (Default)** The `signal-cli` executable is invoked for every REST API request. Being a Java application, each REST call requires a new startup of the JVM (Java Virtual Machine), increasing the latency and hence leading to the slowest mode of operation.
* **`native` Mode:** A precompiled binary `signal-cli-native` (using GraalVM) is used for every REST API request. This results in a much lower latency & memory usage on each call. On the `armv7` platform this mode is not available and falls back to `normal`. The native mode may also be less stable, due to the experimental state of GraalVM compiler.
* `json-rpc` Mode: A single, JVM-based `signal-cli` instance is spawned as daemon process. This mode is usually the fastest, but requires more memory as the JVM keeps running.
* `json-rpc-native` Mode: Uses the `signal-cli-native` binary and starts it in daemon mode (this mode basically combines the advantages of the `native` mode and the `json-rpc` mode).
| mode | speed | resident memory usage |
| ---------: | :------------------------------------------------------- | :-------------------- |
| `normal` | :heavy_check_mark: | normal |
| `native` | :heavy_check_mark: :heavy_check_mark: | normal |
| `json-rpc` | :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: | increased |
| `json-rpc-native` | :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: | normal |
**Example of running `signal-cli-rest` in `native` mode**
@ -123,7 +124,7 @@ The Swagger API documentation can be found [here](https://bbernhard.github.io/si
| [signalbot](https://pypi.org/project/signalbot/) | Library | Python | Framework to build Signal bots | [@signalbot-org](https://github.com/orgs/signalbot-org/people) |
| [signal-cli-to-file](https://github.com/jneidel/signal-cli-to-file) | Script | JavaScript | Save incoming signal messages as files | [@jneidel](https://github.com/jneidel) |
| [signal-rest-ts](https://www.npmjs.com/package/signal-rest-ts) | Library | TypeScript | TypeScript module to build Signal bots | [@pseudogeneric](https://github.com/pseudogeneric) |
| [secured-signal-api](https://github.com/codeshelldev/secured-signal-api) | Proxy | Go | Secure Docker Proxy with many QoL Features | [@codeshelldev](https://github.com/codeshelldev) |
| [secured-signal-api](https://github.com/codeshelldev/secured-signal-api) | API Gateway | Go | Secure Docker API Gateway with many QoL Features | [@codeshelldev](https://github.com/codeshelldev) |
In case you need more functionality, please **file a ticket** or **create a PR**.

View File

@ -27,7 +27,7 @@ cap_prefix="-cap_"
caps="$cap_prefix$(seq -s ",$cap_prefix" 0 $(cat /proc/sys/kernel/cap_last_cap))"
# TODO: check mode
if [ "$MODE" = "json-rpc" ]
if [ "$MODE" = "json-rpc" ] || [ "$MODE" = "json-rpc-native" ]
then
/usr/bin/jsonrpc2-helper
if [ -n "$JAVA_OPTS" ] ; then

View File

@ -0,0 +1,13 @@
[target.aarch64-unknown-linux-gnu]
pre-build = [
"apt update && apt install -y unzip",
"curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v25.9/protoc-25.9-linux-x86_64.zip && unzip protoc-25.9-linux-x86_64.zip -d /usr/",
"chmod 755 /usr/bin/protoc"
]
[target.armv7-unknown-linux-gnueabihf]
pre-build = [
"apt update && apt install -y unzip",
"curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v25.9/protoc-25.9-linux-x86_64.zip && unzip protoc-25.9-linux-x86_64.zip -d /usr/",
"chmod 755 /usr/bin/protoc"
]

View File

@ -4,7 +4,8 @@
* download new release from `https://github.com/signalapp/libsignal-client/releases`
* unzip + change into directory
* cd into `java` directory
* run `rm -rf target` to remove any build artifacts from previous builds
* copy `Cross.toml` to downloaded libsignal-client folder
* run `cross build --target x86_64-unknown-linux-gnu --release -p libsignal-jni`
run `cross build --target armv7-unknown-linux-gnueabihf --release -p libsignal-jni`

View File

@ -1,22 +1,23 @@
diff --git a/build.gradle.kts b/build.gradle.kts
index d8e36ea4..dba4dae3 100644
index 64e29294..5d0e53cc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -91,6 +91,7 @@ dependencies {
implementation(libs.logback)
implementation(libs.zxing)
@@ -104,6 +104,7 @@ dependencies {
implementation(libs.micronaut.json.schema.generator)
}
implementation(project(":libsignal-cli"))
+ implementation(files("/tmp/libsignal-client.jar"))
}
+ implementation(files("/tmp/libsignal-client.jar"))
configurations {
@@ -99,6 +100,9 @@ configurations {
testImplementation(libs.junit.jupiter)
testImplementation(platform(libs.junit.jupiter.bom))
@@ -120,6 +121,10 @@ configurations {
}
}
+configurations.all {
+ exclude(group = "org.signal", module = "libsignal-client")
+}
+
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false

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
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 `:`.
If you write a new plugin, it is recommended to use the `version: 2` of the plugin mechanism. (`version: 1` is deprecated!)
The `method` parameter specifies the HTTP method that is used for the endpoint registration.
# The script file
The script file (with the file suffix `.lua`) contains the implementation of the endpoint.
The script file (with the file suffix `.lua`) contains the implementation of the endpoint. Each plugin must implement a `exec` function and can optionally implement a `init` function. The `exec` function gets called whenever the plugin is called. The `init` function only gets called once during the startup and can be used to perform initialization tasks.
Example:
```
local http = require("http")
local json = require("json")
--
function exec()
local http = require("http")
local json = require("json")
local url = "http://127.0.0.1:8080/v2/send"
local url = "http://127.0.0.1:8080/v2/send"
local customEndpointPayload = json.decode(pluginInputData.payload)
local customEndpointPayload = json.decode(pluginInputData.payload)
local sendEndpointPayload = {
recipients = {customEndpointPayload.recipient},
message = customEndpointPayload.message,
number = pluginInputData.Params.number
}
local sendEndpointPayload = {
recipients = {customEndpointPayload.recipient},
message = customEndpointPayload.message,
number = pluginInputData.Params.number
}
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
response, error_message = http.request("POST", url, {
timeout="30s",
headers={
Accept="*/*",
["Content-Type"]="application/json"
},
body=encodedSendEndpointPayload
})
response, error_message = http.request("POST", url, {
timeout="30s",
headers={
Accept="*/*",
["Content-Type"]="application/json"
},
body=encodedSendEndpointPayload
})
pluginOutputData:SetPayload(response["body"])
pluginOutputData:SetHttpStatusCode(response.status_code)
pluginOutputData:SetPayload(response["body"])
pluginOutputData:SetHttpStatusCode(response.status_code)
end
-- optional init function
function init()
end
```
What the lua script does, is parse the JSON payload from the custom request, extract the `recipient` and the `message` from the payload and the `number` from the URL parameter and call the `/v2/send` endpoint with those parameters. The HTTP status code and the body that is returned by the HTTP request is then returned to the caller (this is done via the `pluginOutputData:SetPayload` and `pluginOutputData:SetHttpStatusCode` functions.

View File

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

View File

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

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

@ -34,26 +34,37 @@ const (
type UpdateContactRequest struct {
Recipient string `json:"recipient"`
Name *string `json:"name"`
ExpirationInSeconds *int `json:"expiration_in_seconds"`
Name *string `json:"name,omitempty"`
ExpirationInSeconds *int `json:"expiration_in_seconds,omitempty"`
}
type CreateGroupRequest struct {
Name string `json:"name"`
Members []string `json:"members"`
Description string `json:"description"`
Permissions ds.GroupPermissions `json:"permissions"`
GroupLinkState string `json:"group_link" enums:"disabled,enabled,enabled-with-approval"`
ExpirationTime *int `json:"expiration_time"`
Name string `json:"name,omitempty"`
Members []string `json:"members,omitempty"`
Description string `json:"description,omitempty"`
Permissions ds.GroupPermissions `json:"permissions,omitempty"`
GroupLinkState string `json:"group_link,omitempty" enums:"disabled,enabled,enabled-with-approval"`
ExpirationTime *int `json:"expiration_time,omitempty"`
}
type UpdateGroupRequest struct {
Base64Avatar *string `json:"base64_avatar"`
Description *string `json:"description"`
Name *string `json:"name"`
ExpirationTime *int `json:"expiration_time"`
GroupLinkState *string `json:"group_link" enums:"disabled,enabled,enabled-with-approval"`
Permissions *ds.GroupPermissions `json:"permissions"`
Base64Avatar *string `json:"base64_avatar,omitempty"`
Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
ExpirationTime *int `json:"expiration_time,omitempty"`
GroupLinkState *string `json:"group_link,omitempty" enums:"disabled,enabled,enabled-with-approval"`
Permissions *ds.GroupPermissions `json:"permissions,omitempty"`
}
type PinMessageInGroupRequest struct {
TargetAuthor string `json:"target_author"`
Timestamp int64 `json:"timestamp"`
Duration *int `json:"duration,omitempty"`
}
type UnpinMessageInGroupRequest struct {
TargetAuthor string `json:"target_author"`
Timestamp int64 `json:"timestamp"`
}
type ChangeGroupMembersRequest struct {
@ -73,26 +84,33 @@ type Configuration struct {
}
type RegisterNumberRequest struct {
UseVoice bool `json:"use_voice"`
Captcha string `json:"captcha"`
UseVoice bool `json:"use_voice,omitempty"`
Captcha string `json:"captcha,omitempty"`
}
type UnregisterNumberRequest struct {
DeleteAccount bool `json:"delete_account" example:"false"`
DeleteLocalData bool `json:"delete_local_data" example:"false"`
DeleteAccount bool `json:"delete_account,omitempty" example:"false"`
DeleteLocalData bool `json:"delete_local_data,omitempty" example:"false"`
}
type VerifyNumberSettings struct {
Pin string `json:"pin"`
Pin string `json:"pin,omitempty"`
}
type Reaction struct {
type SendReactionRequest struct {
Recipient string `json:"recipient"`
Reaction string `json:"reaction"`
TargetAuthor string `json:"target_author"`
Timestamp int64 `json:"timestamp"`
}
type RemoveReactionRequest struct {
Recipient string `json:"recipient"`
Reaction string `json:"reaction,omitempty"`
TargetAuthor string `json:"target_author"`
Timestamp int64 `json:"timestamp"`
}
type Receipt struct {
Recipient string `json:"recipient"`
ReceiptType string `json:"receipt_type" enums:"read,viewed"`
@ -103,27 +121,27 @@ type SendMessageV1 struct {
Number string `json:"number"`
Recipients []string `json:"recipients"`
Message string `json:"message"`
Base64Attachment string `json:"base64_attachment" example:"'<BASE64 ENCODED DATA>' OR 'data:<MIME-TYPE>;base64,<BASE64 ENCODED DATA>' OR 'data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>'"`
IsGroup bool `json:"is_group"`
Base64Attachment string `json:"base64_attachment,omitempty" example:"'<BASE64 ENCODED DATA>' OR 'data:<MIME-TYPE>;base64,<BASE64 ENCODED DATA>' OR 'data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>'"`
IsGroup bool `json:"is_group,omitempty"`
}
type SendMessageV2 struct {
Number string `json:"number"`
Recipients []string `json:"recipients"`
Recipient string `json:"recipient" swaggerignore:"true"` //some REST API consumers (like the Synology NAS) do not support an array as recipients, so we provide this string parameter here as backup. In order to not confuse anyone, the parameter won't be exposed in the Swagger UI (most users are fine with the recipients parameter).
Recipient string `json:"recipient,omitempty" swaggerignore:"true"` //some REST API consumers (like the Synology NAS) do not support an array as recipients, so we provide this string parameter here as backup. In order to not confuse anyone, the parameter won't be exposed in the Swagger UI (most users are fine with the recipients parameter).
Message string `json:"message"`
Base64Attachments []string `json:"base64_attachments" example:"<BASE64 ENCODED DATA>,data:<MIME-TYPE>;base64<comma><BASE64 ENCODED DATA>,data:<MIME-TYPE>;filename=<FILENAME>;base64<comma><BASE64 ENCODED DATA>"`
Sticker string `json:"sticker"`
Mentions []ds.MessageMention `json:"mentions"`
QuoteTimestamp *int64 `json:"quote_timestamp"`
QuoteAuthor *string `json:"quote_author"`
QuoteMessage *string `json:"quote_message"`
QuoteMentions []ds.MessageMention `json:"quote_mentions"`
TextMode *string `json:"text_mode" enums:"normal,styled"`
EditTimestamp *int64 `json:"edit_timestamp"`
NotifySelf *bool `json:"notify_self"`
LinkPreview *ds.LinkPreviewType `json:"link_preview"`
ViewOnce *bool `json:"view_once"`
Base64Attachments []string `json:"base64_attachments,omitempty" example:"<BASE64 ENCODED DATA>,data:<MIME-TYPE>;base64<comma><BASE64 ENCODED DATA>,data:<MIME-TYPE>;filename=<FILENAME>;base64<comma><BASE64 ENCODED DATA>"`
Sticker string `json:"sticker,omitempty"`
Mentions []ds.MessageMention `json:"mentions,omitempty"`
QuoteTimestamp *int64 `json:"quote_timestamp,omitempty"`
QuoteAuthor *string `json:"quote_author,omitempty"`
QuoteMessage *string `json:"quote_message,omitempty"`
QuoteMentions []ds.MessageMention `json:"quote_mentions,omitempty"`
TextMode *string `json:"text_mode,omitempty" enums:"normal,styled"`
EditTimestamp *int64 `json:"edit_timestamp,omitempty"`
NotifySelf *bool `json:"notify_self,omitempty"`
LinkPreview *ds.LinkPreviewType `json:"link_preview,omitempty"`
ViewOnce *bool `json:"view_once,omitempty"`
}
type TypingIndicatorRequest struct {
@ -146,13 +164,13 @@ type CreateGroupResponse struct {
type UpdateProfileRequest struct {
Name string `json:"name"`
Base64Avatar string `json:"base64_avatar"`
About *string `json:"about"`
Base64Avatar string `json:"base64_avatar,omitempty"`
About *string `json:"about,omitempty"`
}
type TrustIdentityRequest struct {
VerifiedSafetyNumber *string `json:"verified_safety_number"`
TrustAllKnownKeys *bool `json:"trust_all_known_keys" example:"false"`
VerifiedSafetyNumber *string `json:"verified_safety_number,omitempty"`
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
}
type SendMessageResponse struct {
@ -192,8 +210,8 @@ type RateLimitChallengeRequest struct {
}
type UpdateAccountSettingsRequest struct {
DiscoverableByNumber *bool `json:"discoverable_by_number"`
ShareNumber *bool `json:"share_number"`
DiscoverableByNumber *bool `json:"discoverable_by_number,omitempty"`
ShareNumber *bool `json:"share_number,omitempty"`
}
type SetUsernameRequest struct {
@ -215,7 +233,7 @@ type RemoteDeleteRequest struct {
}
type DeleteLocalAccountDataRequest struct {
IgnoreRegistered bool `json:"ignore_registered" example:"false"`
IgnoreRegistered bool `json:"ignore_registered,omitempty" example:"false"`
}
type DeviceLinkUriResponse struct {
@ -226,7 +244,7 @@ type CreatePollRequest struct {
Recipient string `json:"recipient" example:"<phone number> OR <username> OR <group id>"`
Question string `json:"question" example:"What's your favourite fruit?"`
Answers []string `json:"answers" example:"apple,banana,orange"`
AllowMultipleSelections *bool `json:"allow_multiple_selections" example:"true"`
AllowMultipleSelections *bool `json:"allow_multiple_selections,omitempty" example:"true"`
}
type CreatePollResponse struct {
@ -306,8 +324,14 @@ func (a *Api) RegisterNumber(c *gin.Context) {
err = a.signalClient.RegisterNumber(number, req.UseVoice, req.Captcha)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
switch err.(type) {
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)
}
@ -855,6 +879,11 @@ func (a *Api) AddMembersToGroup(c *gin.Context) {
return
}
if len(req.Members) == 0 {
c.JSON(400, Error{Msg: "Couldn't process request - group members missing"})
return
}
err = a.signalClient.AddMembersToGroup(number, groupId, req.Members)
if err != nil {
switch err.(type) {
@ -1024,6 +1053,7 @@ func (a *Api) RemoveAdminsFromGroup(c *gin.Context) {
// @Success 200 {object} []client.GroupEntry
// @Failure 400 {object} Error
// @Param number path string true "Registered Phone Number"
// @Param expand query bool false "Expand the response to show more details (default: false)"
// @Router /v1/groups/{number} [get]
func (a *Api) GetGroups(c *gin.Context) {
number, err := url.PathUnescape(c.Param("number"))
@ -1032,12 +1062,26 @@ func (a *Api) GetGroups(c *gin.Context) {
return
}
groups, err := a.signalClient.GetGroups(number)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
expand := c.DefaultQuery("expand", "false")
if expand != "true" && expand != "false" {
c.JSON(400, Error{Msg: "Couldn't process request - expand parameter needs to be either 'true' or 'false'"})
return
}
var groups any
if StringToBool(expand) {
groups, err = a.signalClient.GetGroupsExpanded(number)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
} else {
groups, err = a.signalClient.GetGroups(number)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
}
c.JSON(200, groups)
}
@ -1050,6 +1094,7 @@ func (a *Api) GetGroups(c *gin.Context) {
// @Failure 400 {object} Error
// @Param number path string true "Registered Phone Number"
// @Param groupid path string true "Group ID"
// @Param expand query bool false "Expand the response to show more details (default: false)"
// @Router /v1/groups/{number}/{groupid} [get]
func (a *Api) GetGroup(c *gin.Context) {
number, err := url.PathUnescape(c.Param("number"))
@ -1059,12 +1104,27 @@ func (a *Api) GetGroup(c *gin.Context) {
}
groupId := c.Param("groupid")
groupEntry, err := a.signalClient.GetGroup(number, groupId)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
expand := c.DefaultQuery("expand", "false")
if expand != "true" && expand != "false" {
c.JSON(400, Error{Msg: "Couldn't process request - expand parameter needs to be either 'true' or 'false'"})
return
}
var groupEntry any
if StringToBool(expand) {
groupEntry, err = a.signalClient.GetGroupExpanded(number, groupId)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
} else {
groupEntry, err = a.signalClient.GetGroup(number, groupId)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
}
if groupEntry != nil {
c.JSON(200, groupEntry)
} else {
@ -1142,6 +1202,113 @@ func (a *Api) DeleteGroup(c *gin.Context) {
}
}
// @Summary Pin a message in a Signal Group.
// @Tags Groups
// @Description Pin a message in a Signal Group.
// @Accept json
// @Produce json
// @Success 200 {string} string "OK"
// @Failure 400 {object} Error
// @Param data body PinMessageInGroupRequest true "Pin"
// @Param number path string true "Registered Phone Number"
// @Param groupid path string true "Group Id"
// @Router /v1/groups/{number}/{groupid}/pin-message [post]
func (a *Api) PinMessageInGroup(c *gin.Context) {
base64EncodedGroupId := c.Param("groupid")
number, err := url.PathUnescape(c.Param("number"))
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - malformed number"})
return
}
if base64EncodedGroupId == "" {
c.JSON(400, Error{Msg: "Please specify a group id"})
return
}
groupId, err := client.ConvertGroupIdToInternalGroupId(base64EncodedGroupId)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
var req PinMessageInGroupRequest
err = c.BindJSON(&req)
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
return
}
duration := -1
if req.Duration != nil {
duration = *req.Duration
}
err = a.signalClient.PinMessageInGroup(number, groupId, req.TargetAuthor, req.Timestamp, duration)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
c.Status(201)
}
// @Summary Unpin a message in a Signal Group.
// @Tags Groups
// @Description Unpin a message in a Signal Group.
// @Accept json
// @Produce json
// @Success 200 {string} string "OK"
// @Failure 400 {object} Error
// @Param data body UnpinMessageInGroupRequest true "Unpin"
// @Param number path string true "Registered Phone Number"
// @Param groupid path string true "Group Id"
// @Router /v1/groups/{number}/{groupid}/pin-message [delete]
func (a *Api) UnpinMessageInGroup(c *gin.Context) {
base64EncodedGroupId := c.Param("groupid")
number, err := url.PathUnescape(c.Param("number"))
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - malformed number"})
return
}
if base64EncodedGroupId == "" {
c.JSON(400, Error{Msg: "Please specify a group id"})
return
}
groupId, err := client.ConvertGroupIdToInternalGroupId(base64EncodedGroupId)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
var req UnpinMessageInGroupRequest
err = c.BindJSON(&req)
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
return
}
if req.TargetAuthor == "" {
c.JSON(400, Error{Msg: "Couldn't process request - target author missing"})
return
}
if req.Timestamp == 0 {
c.JSON(400, Error{Msg: "Couldn't process request - timestamp missing"})
return
}
err = a.signalClient.UnpinMessageInGroup(number, groupId, req.TargetAuthor, req.Timestamp)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return
}
c.Status(201)
}
// @Summary Link device and generate QR code.
// @Tags Devices
// @Description Link device and generate QR code
@ -1708,11 +1875,11 @@ func (a *Api) UpdateGroup(c *gin.Context) {
// @Produce json
// @Success 204 {string} OK
// @Failure 400 {object} Error
// @Param data body Reaction true "Reaction"
// @Param data body SendReactionRequest true "Reaction"
// @Param number path string true "Registered phone number"
// @Router /v1/reactions/{number} [post]
func (a *Api) SendReaction(c *gin.Context) {
var req Reaction
var req SendReactionRequest
err := c.BindJSON(&req)
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
@ -1761,11 +1928,11 @@ func (a *Api) SendReaction(c *gin.Context) {
// @Produce json
// @Success 204 {string} OK
// @Failure 400 {object} Error
// @Param data body Reaction true "Reaction"
// @Param data body RemoveReactionRequest true "Reaction"
// @Param number path string true "Registered phone number"
// @Router /v1/reactions/{number} [delete]
func (a *Api) RemoveReaction(c *gin.Context) {
var req Reaction
var req RemoveReactionRequest
err := c.BindJSON(&req)
if err != nil {
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
@ -2239,6 +2406,16 @@ func (a *Api) SubmitRateLimitChallenge(c *gin.Context) {
return
}
if req.ChallengeToken == "" {
c.JSON(400, Error{Msg: "Couldn't process request - challenge token missing"})
return
}
if req.Captcha == "" {
c.JSON(400, Error{Msg: "Couldn't process request - captcha missing"})
return
}
err = a.signalClient.SubmitRateLimitChallenge(number, req.ChallengeToken, req.Captcha)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})

View File

@ -126,6 +126,30 @@ type GroupEntry struct {
Permissions ds.GroupPermissions `json:"permissions"`
}
type GroupMember struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
}
type GroupAdmin struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
}
type ExpandedGroupEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
InternalId string `json:"internal_id"`
Members []GroupMember `json:"members"`
Blocked bool `json:"blocked"`
PendingInvites []GroupMember `json:"pending_invites"`
PendingRequests []GroupMember `json:"pending_requests"`
InviteLink string `json:"invite_link"`
Admins []GroupAdmin `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
}
type IdentityEntry struct {
Number string `json:"number"`
Status string `json:"status"`
@ -135,31 +159,21 @@ type IdentityEntry struct {
Uuid string `json:"uuid"`
}
type SignalCliGroupMember struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
}
type SignalCliGroupAdmin struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
}
type SignalCliGroupEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
IsMember bool `json:"isMember"`
IsBlocked bool `json:"isBlocked"`
Members []SignalCliGroupMember `json:"members"`
PendingMembers []SignalCliGroupMember `json:"pendingMembers"`
RequestingMembers []SignalCliGroupMember `json:"requestingMembers"`
GroupInviteLink string `json:"groupInviteLink"`
Admins []SignalCliGroupAdmin `json:"admins"`
Uuid string `json:"uuid"`
PermissionEditDetails string `json:"permissionEditDetails"`
PermissionAddMember string `json:"permissionAddMember"`
PermissionSendMessage string `json:"permissionSendMessage"`
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
IsMember bool `json:"isMember"`
IsBlocked bool `json:"isBlocked"`
Members []GroupMember `json:"members"`
PendingMembers []GroupMember `json:"pendingMembers"`
RequestingMembers []GroupMember `json:"requestingMembers"`
GroupInviteLink string `json:"groupInviteLink"`
Admins []GroupAdmin `json:"admins"`
Uuid string `json:"uuid"`
PermissionEditDetails string `json:"permissionEditDetails"`
PermissionAddMember string `json:"permissionAddMember"`
PermissionSendMessage string `json:"permissionSendMessage"`
}
type SignalCliIdentityEntry struct {
@ -735,6 +749,8 @@ func (s *SignalClient) About() About {
}
func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
UseVoice bool `json:"voice,omitempty"`
@ -751,12 +767,11 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
request.Captcha = captcha
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
_, err = jsonRpc2Client.getRaw("register", nil, request)
return err
} else {
command := []string{"--config", s.signalCliConfig, "-a", number, "register"}
@ -768,9 +783,16 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
command = append(command, []string{"--captcha", captcha}...)
}
_, err := s.cliClient.Execute(true, command, "")
return err
_, err = s.cliClient.Execute(true, command, "")
}
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 {
@ -1066,8 +1088,8 @@ func (s *SignalClient) CreateGroup(number string, name string, members []string,
var internalGroupId string
if s.signalCliMode == JsonRpc {
type Request struct {
Name string `json:"name"`
Members []string `json:"members"`
Name string `json:"name,omitempty"`
Members []string `json:"members,omitempty"`
Link string `json:"link,omitempty"`
Description string `json:"description,omitempty"`
EditGroupPermissions string `json:"setPermissionEditDetails,omitempty"`
@ -1121,8 +1143,16 @@ func (s *SignalClient) CreateGroup(number string, name string, members []string,
}
internalGroupId = resp.GroupId
} else {
cmd := []string{"--config", s.signalCliConfig, "-a", number, "updateGroup", "-n", name, "-m"}
cmd = append(cmd, prefixUsernameMembers(members)...)
cmd := []string{"--config", s.signalCliConfig, "-a", number, "updateGroup"}
if name != "" {
cmd = append(cmd, []string{"--n", name}...)
}
if len(members) > 0 {
cmd = append(cmd, "-m")
cmd = append(cmd, prefixUsernameMembers(members)...)
}
if addMembersPermission != DefaultGroupPermission {
cmd = append(cmd, []string{"--set-permission-add-member", addMembersPermission.String()}...)
@ -1300,15 +1330,16 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi
return s.updateGroupAdmins(number, groupId, admins, false)
}
func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
groupEntries := []GroupEntry{}
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
groupEntries := []ExpandedGroupEntry{}
var signalCliGroupEntries []SignalCliGroupEntry
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return groupEntries, err
}
@ -1329,7 +1360,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
}
for _, signalCliGroupEntry := range signalCliGroupEntries {
var groupEntry GroupEntry
var groupEntry ExpandedGroupEntry
groupEntry.InternalId = signalCliGroupEntry.Id
groupEntry.Name = signalCliGroupEntry.Name
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
@ -1338,9 +1369,32 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
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
}
func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
expandedGroupEntries, err := s.GetGroupsExpanded(number)
if err != nil {
return []GroupEntry{}, err
}
groupEntries := []GroupEntry{}
for _, expandedGroupEntry := range expandedGroupEntries {
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
members := []string{}
for _, val := range signalCliGroupEntry.Members {
for _, val := range expandedGroupEntry.Members {
identifier := val.Number
if identifier == "" {
identifier = val.Uuid
@ -1349,28 +1403,28 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
}
groupEntry.Members = members
pendingMembers := []string{}
for _, val := range signalCliGroupEntry.PendingMembers {
pendingInvites := []string{}
for _, val := range expandedGroupEntry.PendingInvites {
identifier := val.Number
if identifier == "" {
identifier = val.Uuid
}
pendingMembers = append(pendingMembers, identifier)
pendingInvites = append(pendingInvites, identifier)
}
groupEntry.PendingInvites = pendingMembers
groupEntry.PendingInvites = pendingInvites
requestingMembers := []string{}
for _, val := range signalCliGroupEntry.RequestingMembers {
pendingRequests := []string{}
for _, val := range expandedGroupEntry.PendingRequests {
identifier := val.Number
if identifier == "" {
identifier = val.Uuid
}
requestingMembers = append(requestingMembers, identifier)
pendingRequests = append(pendingRequests, identifier)
}
groupEntry.PendingRequests = requestingMembers
groupEntry.PendingRequests = pendingRequests
admins := []string{}
for _, val := range signalCliGroupEntry.Admins {
for _, val := range expandedGroupEntry.Admins {
identifier := val.Number
if identifier == "" {
identifier = val.Uuid
@ -1379,8 +1433,6 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
}
groupEntry.Admins = admins
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
groupEntries = append(groupEntries, groupEntry)
}
@ -1404,8 +1456,85 @@ func (s *SignalClient) GetGroup(number string, groupId string) (*GroupEntry, err
return nil, nil
}
func (s *SignalClient) GetGroupExpanded(number string, groupId string) (*ExpandedGroupEntry, error) {
groupEntry := ExpandedGroupEntry{}
groups, err := s.GetGroupsExpanded(number)
if err != nil {
return nil, err
}
for _, group := range groups {
if group.Id == groupId {
groupEntry = group
return &groupEntry, nil
}
}
return nil, nil
}
func (s *SignalClient) PinMessageInGroup(number string, groupId string, targetAuthor string, timestamp int64, duration int) error {
if s.signalCliMode == JsonRpc {
type Request struct {
TargetAuthor string `json:"target-author"`
TargetTimestamp int64 `json:"target-timestamp"`
PinDuration int `json:"pin-duration"`
GroupId string `json:"group-id"`
}
req := Request{TargetAuthor: targetAuthor, TargetTimestamp: timestamp, PinDuration: duration, GroupId: groupId}
jsonRpc2Client, err := s.getJsonRpc2Client()
if err != nil {
return err
}
_, err = jsonRpc2Client.getRaw("sendPinMessage", &number, req)
if err != nil {
return err
}
} else {
cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "sendPinMessage", "-g", groupId,
"-a", targetAuthor, "-t", strconv.FormatInt(timestamp, 10), "-d", strconv.Itoa(duration)}
_, err := s.cliClient.Execute(true, cmd, "")
if err != nil {
return err
}
}
return nil
}
func (s *SignalClient) UnpinMessageInGroup(number string, groupId string, targetAuthor string, timestamp int64) error {
if s.signalCliMode == JsonRpc {
type Request struct {
TargetAuthor string `json:"target-author"`
TargetTimestamp int64 `json:"target-timestamp"`
GroupId string `json:"group-id"`
}
req := Request{TargetAuthor: targetAuthor, TargetTimestamp: timestamp, GroupId: groupId}
jsonRpc2Client, err := s.getJsonRpc2Client()
if err != nil {
return err
}
_, err = jsonRpc2Client.getRaw("sendUnpinMessage", &number, req)
if err != nil {
return err
}
} else {
cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "sendUnpinMessage", "-g", groupId,
"-a", targetAuthor, "-t", strconv.FormatInt(timestamp, 10)}
_, err := s.cliClient.Execute(true, cmd, "")
if err != nil {
return err
}
}
return nil
}
func (s *SignalClient) GetAvatar(number string, id string, avatarType AvatarType) ([]byte, error) {
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if avatarType == GroupAvatar {
@ -1432,7 +1561,7 @@ func (s *SignalClient) GetAvatar(number string, id string, avatarType AvatarType
request.Profile = id
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return []byte{}, err
}
@ -1616,9 +1745,10 @@ func (s *SignalClient) GetAccounts() ([]string, error) {
accounts := make([]string, 0)
var rawData string
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return accounts, err
}
@ -1788,10 +1918,11 @@ func (s *SignalClient) UpdateProfile(number string, profileName string, base64Av
func (s *SignalClient) ListIdentities(number string) (*[]IdentityEntry, error) {
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
identityEntries := []IdentityEntry{}
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return nil, err
}
@ -1826,6 +1957,7 @@ func (s *SignalClient) ListIdentities(number string) (*[]IdentityEntry, error) {
func (s *SignalClient) TrustIdentity(number string, numberToTrust string, verifiedSafetyNumber *string, trustAllKnownKeys *bool) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
VerifiedSafetyNumber string `json:"verified-safety-number,omitempty"`
@ -1842,7 +1974,7 @@ func (s *SignalClient) TrustIdentity(number string, numberToTrust string, verifi
request.TrustAllKnownKeys = *trustAllKnownKeys
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -1865,12 +1997,13 @@ func (s *SignalClient) TrustIdentity(number string, numberToTrust string, verifi
func (s *SignalClient) BlockGroup(number string, groupId string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
GroupId string `json:"groupId"`
}
request := Request{GroupId: groupId}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -1883,12 +2016,13 @@ func (s *SignalClient) BlockGroup(number string, groupId string) error {
func (s *SignalClient) JoinGroup(number string, groupId string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
GroupId string `json:"groupId"`
}
request := Request{GroupId: groupId}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -1901,12 +2035,13 @@ func (s *SignalClient) JoinGroup(number string, groupId string) error {
func (s *SignalClient) QuitGroup(number string, groupId string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
GroupId string `json:"groupId"`
}
request := Request{GroupId: groupId}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2049,6 +2184,7 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
func (s *SignalClient) SendReaction(number string, recipient string, emoji string, target_author string, timestamp int64, remove bool) error {
// see https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#sendreaction
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
isGroup := false
if strings.HasPrefix(recipient, groupPrefix) {
@ -2083,7 +2219,7 @@ func (s *SignalClient) SendReaction(number string, recipient string, emoji strin
if remove {
request.Remove = remove
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2112,6 +2248,7 @@ func (s *SignalClient) SendReaction(number string, recipient string, emoji strin
func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type string, timestamp int64) error {
// see https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#sendreceipt
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
if s.signalCliMode == JsonRpc {
@ -2125,7 +2262,7 @@ func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type
request.ReceiptType = receipt_type
request.Timestamp = timestamp
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2148,6 +2285,7 @@ func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type
func (s *SignalClient) SendStartTyping(number string, recipient string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
isGroup := false
if strings.HasPrefix(recipient, groupPrefix) {
@ -2170,7 +2308,7 @@ func (s *SignalClient) SendStartTyping(number string, recipient string) error {
request.GroupId = recp
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2190,6 +2328,7 @@ func (s *SignalClient) SendStartTyping(number string, recipient string) error {
func (s *SignalClient) SendStopTyping(number string, recipient string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
isGroup := false
if strings.HasPrefix(recipient, groupPrefix) {
@ -2213,7 +2352,7 @@ func (s *SignalClient) SendStopTyping(number string, recipient string) error {
request.GroupId = recp
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2291,8 +2430,9 @@ func (s *SignalClient) SearchForNumbers(number string, numbers []string) ([]Sear
func (s *SignalClient) SendContacts(number string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2306,6 +2446,7 @@ func (s *SignalClient) SendContacts(number string) error {
func (s *SignalClient) UpdateContact(number string, recipient string, name *string, expirationInSeconds *int) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
Recipient string `json:"recipient"`
@ -2319,7 +2460,7 @@ func (s *SignalClient) UpdateContact(number string, recipient string, name *stri
if expirationInSeconds != nil {
request.Expiration = *expirationInSeconds
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2339,12 +2480,13 @@ func (s *SignalClient) UpdateContact(number string, recipient string, name *stri
func (s *SignalClient) AddDevice(number string, uri string) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
Uri string `json:"uri"`
}
request := Request{Uri: uri}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2367,9 +2509,10 @@ func (s *SignalClient) ListDevices(number string) ([]ListDevicesResponse, error)
}
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return resp, err
}
@ -2404,12 +2547,13 @@ func (s *SignalClient) ListDevices(number string) ([]ListDevicesResponse, error)
func (s *SignalClient) RemoveDevice(number string, deviceId int64) error {
var err error
var jsonRpc2Client *JsonRpc2Client
if s.signalCliMode == JsonRpc {
type Request struct {
DeviceId int64 `json:"deviceId"`
}
request := Request{DeviceId: deviceId}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -2466,13 +2610,14 @@ func (s *SignalClient) SetUsername(number string, username string) (SetUsernameR
var resp SetUsernameResponse
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if s.signalCliMode == JsonRpc {
type Request struct {
Username string `json:"username"`
}
request := Request{Username: username}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return resp, err
}
@ -2555,9 +2700,10 @@ func (s *SignalClient) ListInstalledStickerPacks(number string) ([]ListInstalled
resp := []ListInstalledStickerPacksResponse{}
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if s.signalCliMode == JsonRpc {
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return resp, err
}
@ -2638,6 +2784,7 @@ func (s *SignalClient) ListContacts(number string, allRecipients bool, recipient
resp := []ListContactsResponse{}
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
if s.signalCliMode == JsonRpc {
@ -2653,7 +2800,7 @@ func (s *SignalClient) ListContacts(number string, allRecipients bool, recipient
req.Recipient = recipient
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return nil, err
}
@ -2760,6 +2907,7 @@ func (s *SignalClient) RemovePin(number string) error {
func (s *SignalClient) RemoteDelete(number string, recipient string, timestamp int64) (RemoteDeleteResponse, error) {
// see https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#remotedelete
var err error
var jsonRpc2Client *JsonRpc2Client
var resp RemoteDeleteResponse
var rawData string
@ -2795,7 +2943,7 @@ func (s *SignalClient) RemoteDelete(number string, recipient string, timestamp i
}
request.Timestamp = timestamp
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return resp, err
}
@ -2834,6 +2982,7 @@ func (s *SignalClient) RemoteDelete(number string, recipient string, timestamp i
func (s *SignalClient) CreatePoll(number string, recipient string, question string, answers []string, allowMultipleSelections bool) (string, error) {
var err error
var jsonRpc2Client *JsonRpc2Client
var rawData string
type Response struct {
@ -2875,7 +3024,7 @@ func (s *SignalClient) CreatePoll(number string, recipient string, question stri
req.Username = recp
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return "", err
}
@ -2925,6 +3074,7 @@ func (s *SignalClient) CreatePoll(number string, recipient string, question stri
func (s *SignalClient) VoteInPoll(number string, recipient string, pollAuthor string, pollTimestamp int64, selectedAnswers []int32) error {
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
recipientType, err := getRecipientType(recipient)
@ -2968,7 +3118,7 @@ func (s *SignalClient) VoteInPoll(number string, recipient string, pollAuthor st
req.Username = recp
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}
@ -3011,6 +3161,7 @@ func (s *SignalClient) VoteInPoll(number string, recipient string, pollAuthor st
func (s *SignalClient) ClosePoll(number string, recipient string, pollTimestamp int64) error {
var err error
var jsonRpc2Client *JsonRpc2Client
recp := recipient
recipientType, err := getRecipientType(recipient)
@ -3044,7 +3195,7 @@ func (s *SignalClient) ClosePoll(number string, recipient string, pollTimestamp
req.Username = recp
}
jsonRpc2Client, err := s.getJsonRpc2Client()
jsonRpc2Client, err = s.getJsonRpc2Client()
if err != nil {
return err
}

View File

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

View File

@ -233,13 +233,6 @@ func (r *JsonRpc2Client) ReceiveData(number string, receiveWebhookUrl string) {
}
log.Debug("json-rpc received data: ", str)
if receiveWebhookUrl != "" {
err = postMessageToWebhook(receiveWebhookUrl, []byte(str))
if err != nil {
log.Error("Couldn't post data to webhook: ", err)
}
}
var resp1 JsonRpc2ReceivedMessage
json.Unmarshal([]byte(str), &resp1)
if resp1.Method == "receive" {
@ -254,6 +247,13 @@ func (r *JsonRpc2Client) ReceiveData(number string, receiveWebhookUrl string) {
continue
}
r.receivedMessagesMutex.Unlock()
if receiveWebhookUrl != "" {
err = postMessageToWebhook(receiveWebhookUrl, []byte(str))
if err != nil {
log.Error("Couldn't post data to webhook: ", err)
}
}
}
var resp2 JsonRpc2MessageResponse

View File

@ -1,32 +1,67 @@
These files are generated from the [swaggo/swag](https://github.com/swaggo/swag) tool.
# Documentation
To regenerate them, run in /src:
These files are generated using the [swaggo/swag](https://github.com/swaggo/swag) tool.
```bash
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init
```
There are two steps, first generating the docs and then running the web server.
Or, if you have `swag` installed:
## With docker compose (recommended)
```bash
swag init
```
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
Then run the app in `/src`
## Locally
```bash
go run main.go
```
Install [go](https://go.dev/).
Or with docker compose in the root of the repository
```bash
docker compose up
```
### Generating the docs
Then navigate to the following address to view the docs
1. Set the current working dir to `src`
```bash
cd src
```
1. Run swag to generate the docs
* Option 1, via go
```bash
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json"
```
* Option 2, directly with swag
```bash
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/Gara-Dorta/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
```
http://127.0.0.1:8080/swagger/index.html
### Run the web server
On docker you'll get a Network error, replace the IP for the docker internal IP in the error, e.g:
Run the web server to visualize the generated docs.
http://172.18.0.2:8080/swagger/index.html
1. Navigate to the `src` folder
```bash
cd src
```
1. Run the main script
```bash
go run main.go
```
1. Go to http://127.0.0.1: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
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ module github.com/bbernhard/signal-cli-rest-api
go 1.24.0
require (
github.com/bbernhard/gluasql v0.2.0
github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9
github.com/cyphar/filepath-securejoin v0.2.4
github.com/gabriel-vasile/mimetype v1.4.8
@ -17,13 +18,14 @@ require (
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
github.com/tidwall/sjson v1.2.5
github.com/yuin/gopher-lua v1.1.1
github.com/yuin/gopher-lua v1.1.2
gopkg.in/yaml.v2 v2.4.0
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
layeh.com/gopher-luar v1.0.11
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
@ -36,13 +38,16 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.44 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect

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/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/bbernhard/gluasql v0.2.0 h1:2UanAHDSbNQeWTr+utubePjItTOSVRE157zuC5rIl3g=
github.com/bbernhard/gluasql v0.2.0/go.mod h1:swUFFVYyknwnRX8bfeHaKtNLc1/oeWftlzvlBx8pW5Y=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -18,6 +24,10 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59 h1:puVbTWGX6h0pdi6J8n6dilweYk7jU8LElfw8yBxLHEk=
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59/go.mod h1:aVtgxAf6Bfs0hCj+KzIH7Y1aAxg7/7FlslouCh94VVQ=
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8 h1:h1DBe5+9JIArCVsBV14fA+RHDXWY8ynUheDL5ZVPOTg=
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
@ -26,6 +36,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
@ -42,21 +54,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk=
github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw=
github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b h1:9HQYGbaDnuRLMuM//SZVkZJ43ANmMStSAQAx4aQX3II=
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
@ -67,23 +93,35 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
@ -122,8 +160,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -154,6 +196,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -169,11 +213,17 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc=
gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -84,6 +84,7 @@ func main() {
} else {
docs.SwaggerInfo.Schemes = []string{"https", "http"}
}
docs.SwaggerInfo.Version = utils.GetEnv("BUILD_VERSION", "1.0")
router := gin.New()
router.Use(gin.LoggerWithConfig(gin.LoggerConfig{
@ -123,7 +124,7 @@ func main() {
mode := utils.GetEnv("MODE", "normal")
if mode == "normal" {
signalCliMode = client.Normal
} else if mode == "json-rpc" {
} else if mode == "json-rpc" || mode == "json-rpc-native" {
signalCliMode = client.JsonRpc
} else if mode == "native" {
signalCliMode = client.Native
@ -163,7 +164,7 @@ func main() {
jsonRpc2ClientConfigPathPath := *signalCliConfig + "/jsonrpc2.yml"
signalCliApiConfigPath := *signalCliConfig + "/api-config.yml"
signalClient := client.NewSignalClient(*signalCliConfig, *attachmentTmpDir, *avatarTmpDir, signalCliMode, jsonRpc2ClientConfigPathPath, signalCliApiConfigPath, webhookUrl)
err = signalClient.Init(15)
err = signalClient.Init(60)
if err != nil {
log.Fatal("Couldn't init Signal Client: ", err.Error())
}
@ -225,6 +226,8 @@ func main() {
groups.DELETE(":number/:groupid/members", api.RemoveMembersFromGroup)
groups.POST(":number/:groupid/admins", api.AddAdminsToGroup)
groups.DELETE(":number/:groupid/admins", api.RemoveAdminsFromGroup)
groups.POST(":number/:groupid/pin-message", api.PinMessageInGroup)
groups.DELETE(":number/:groupid/pin-message", api.UnpinMessageInGroup)
}
link := v1.Group("qrcodelink")
@ -346,6 +349,18 @@ func main() {
}
for _, pluginConfig := range pluginConfigs.Configs {
if pluginConfig.Version > 1 {
err = pluginHandler.InitPlugin(pluginConfig)
if err != nil {
log.Error("Couldn't initialize plugin ", pluginConfig.Endpoint)
continue
}
} else {
log.Info("Plugin ", pluginConfig.Endpoint, " still uses plugin version 1. Consider migrating to version 2! (see https://github.com/bbernhard/signal-cli-rest-api/plugins/migrate-v1-plugin-to-v2.md)")
}
log.Info("Registering plugin ", pluginConfig.Endpoint)
if pluginConfig.Method == "GET" {
plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
} else if pluginConfig.Method == "POST" {

View File

@ -1,27 +1,30 @@
package main
import (
"github.com/yuin/gopher-lua"
"github.com/cjoudrey/gluahttp"
"layeh.com/gopher-luar"
luajson "layeh.com/gopher-json"
"github.com/gin-gonic/gin"
"errors"
"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"
"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 {
Params map[string]string
Params map[string]string
QueryParams map[string]string
Payload string
Payload string
}
type PluginOutputData struct {
payload string
payload string
httpStatusCode int
}
@ -41,7 +44,7 @@ func (p *PluginOutputData) HttpStatusCode() int {
return p.httpStatusCode
}
func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
jsonData, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
@ -50,13 +53,13 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
}
pluginInputData := &PluginInputData{
Params: make(map[string]string),
Params: make(map[string]string),
QueryParams: make(map[string]string),
Payload: string(jsonData),
Payload: string(jsonData),
}
pluginOutputData := &PluginOutputData{
payload: "",
payload: "",
httpStatusCode: 200,
}
@ -78,8 +81,10 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
luajson.Preload(l)
gluasql.Preload(l)
defer l.Close()
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
log.Error("Error executing lua script: ", err)
c.JSON(400, api.Error{Msg: err.Error()})
return
}
@ -87,16 +92,138 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload())
}
func execPluginV2(c *gin.Context, pluginConfig utils.PluginConfig) {
jsonData, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
log.Error(err.Error())
return
}
pluginInputData := &PluginInputData{
Params: make(map[string]string),
QueryParams: make(map[string]string),
Payload: string(jsonData),
}
pluginOutputData := &PluginOutputData{
payload: "",
httpStatusCode: 200,
}
parts := strings.Split(pluginConfig.Endpoint, "/")
for _, part := range parts {
if strings.HasPrefix(part, ":") {
paramName := strings.TrimPrefix(part, ":")
pluginInputData.Params[paramName] = c.Param(paramName)
}
}
queryParams := c.Request.URL.Query()
for key, values := range queryParams {
pluginInputData.QueryParams[key] = values[0]
}
l := lua.NewState()
l.SetGlobal("pluginInputData", luar.New(l, pluginInputData))
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
luajson.Preload(l)
gluasql.Preload(l)
defer l.Close()
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
log.Error("Error executing lua script: ", err)
c.JSON(400, api.Error{Msg: err.Error()})
return
}
// Get global "exec"
lv := l.GetGlobal("exec")
// Check if it exists and is a function
if fn, ok := lv.(*lua.LFunction); ok {
err := l.CallByParam(lua.P{
Fn: fn,
NRet: 1, // exec function returns one value
Protect: true,
})
if err != nil {
log.Error("Couldn't execute plugin: ", err.Error())
c.JSON(400, "Couldn't execute plugin: "+err.Error())
return
}
ret := l.Get(-1)
l.Pop(1)
if ret != lua.LNil {
log.Error("Couldn't execute plugin")
c.JSON(400, "Couldn't execute plugin")
}
c.Data(
pluginOutputData.HttpStatusCode(),
"application/json",
[]byte(pluginOutputData.Payload()),
)
} else {
log.Error("Couldn't execute plugin. No exec function implemented!")
c.JSON(400, "Couldn't execute plugin. No exec function implemented!")
}
}
type plugHandler struct {
}
func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc {
fn := func(c *gin.Context) {
execPlugin(c, pluginConfig)
if pluginConfig.Version == 1 {
execPluginV1(c, pluginConfig)
} else {
execPluginV2(c, pluginConfig)
}
}
return gin.HandlerFunc(fn)
}
//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

View File

@ -13,7 +13,7 @@ import (
const supervisorctlConfigTemplate = `
[program:%s]
process_name=%s
command=signal-cli --output=json --config %s%s daemon %s%s%s%s --tcp 127.0.0.1:%d
command=%s --output=json --config %s%s daemon %s%s%s%s --tcp 127.0.0.1:%d
autostart=true
autorestart=true
startretries=10
@ -43,6 +43,14 @@ func main() {
jsonRpc2ClientConfig.AddEntry(utils.MULTI_ACCOUNT_NUMBER, utils.JsonRpc2ClientConfigEntry{TcpPort: tcpPort})
signalCliBinary := "signal-cli"
signalMode := utils.GetEnv("MODE", "json-rpc")
if signalMode == "json-rpc-native" {
signalCliBinary = "signal-cli-native"
} else if signalMode != "json-rpc" {
log.Fatal("The mode needs to be either 'json-rpc' or 'json-rpc-native'")
}
signalCliIgnoreAttachments := ""
ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "")
if ignoreAttachments == "true" {
@ -91,7 +99,7 @@ func main() {
//write supervisorctl config
supervisorctlConfigFilename := "/etc/supervisor/conf.d/" + "signal-cli-json-rpc-1.conf"
supervisorctlConfig := fmt.Sprintf(supervisorctlConfigTemplate, supervisorctlProgramName, supervisorctlProgramName,
supervisorctlConfig := fmt.Sprintf(supervisorctlConfigTemplate, supervisorctlProgramName, supervisorctlProgramName, signalCliBinary,
signalCliConfigDir, trustNewIdentities, signalCliIgnoreAttachments, signalCliIgnoreStories,
signalCliIgnoreAvatars, signalCliIgnoreStickers, tcpPort,
supervisorctlProgramName, supervisorctlProgramName)

View File

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

View File

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