mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-05-26 14:44:15 +00:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a3956c8c | ||
|
|
7d8f62a4bb | ||
|
|
99157a035f | ||
|
|
9a2e93a6fa | ||
|
|
c8c461627f | ||
|
|
6e7941b1c1 | ||
|
|
5c0bd056a7 | ||
|
|
ad5d3b76db | ||
|
|
0aaab36e5f | ||
|
|
44ac16d49c | ||
|
|
e8c8b54d52 | ||
|
|
91bdd60c7a | ||
|
|
a7c91737b8 | ||
|
|
af48a4304b | ||
|
|
9155e505af | ||
|
|
d0ec5b1b28 | ||
|
|
fa0f67fa69 | ||
|
|
7aa70683aa | ||
|
|
82f7151212 | ||
|
|
e68cabf88f | ||
|
|
4bbadbf29e | ||
|
|
b37aac4d5f | ||
|
|
db63fd15e0 | ||
|
|
650367e88a | ||
|
|
69457e8f81 | ||
|
|
2e8171d84c | ||
|
|
419b18331d | ||
|
|
16c3214862 | ||
|
|
a168cf5547 | ||
|
|
1f222d7261 | ||
|
|
6ac432b509 | ||
|
|
948a522ad8 | ||
|
|
6fa06f9611 | ||
|
|
eda99213a7 | ||
|
|
2ac55eec07 | ||
|
|
cb5e64a6d5 | ||
|
|
0a7c53a10a | ||
|
|
09252b4b87 | ||
|
|
881e9203c1 | ||
|
|
e2523b102c | ||
|
|
184231020c | ||
|
|
52e058eb06 | ||
|
|
e8a80ef896 | ||
|
|
97197f1fbe | ||
|
|
e19dc961fd | ||
|
|
246ebfd4e6 | ||
|
|
40232de0d0 | ||
|
|
0f2c12f28e | ||
|
|
e712494d7f | ||
|
|
e0af0091c9 | ||
|
|
1b1873ce17 | ||
|
|
252389efbe | ||
|
|
8b8614bb18 | ||
|
|
f159947e07 | ||
|
|
2838e1f879 | ||
|
|
59d6912f21 | ||
|
|
5a883826ae | ||
|
|
2a776618e9 | ||
|
|
fc99aba2f4 | ||
|
|
a01c2a61fa | ||
|
|
e46b1c9ea7 | ||
|
|
33829b2fa5 | ||
|
|
2911bc3632 | ||
|
|
bc0ea66c04 | ||
|
|
b3ba748dd3 | ||
|
|
3dd0001a32 | ||
|
|
b51505c8d9 | ||
|
|
87ab2bb398 | ||
|
|
4e541848e2 | ||
|
|
40f299deff | ||
|
|
7a9b9919ef | ||
|
|
d1d8ddc711 | ||
|
|
8345672499 | ||
|
|
eb34542216 | ||
|
|
7687c5240b | ||
|
|
ef16efca12 | ||
|
|
5c6fd14944 | ||
|
|
6ca5ff1aee | ||
|
|
f142e8089c | ||
|
|
d45b906aa9 | ||
|
|
5435b12e81 | ||
|
|
07260c0811 | ||
|
|
eeacb488ad | ||
|
|
baca954dcc | ||
|
|
44ce4fe83d | ||
|
|
5317ba9362 |
1
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -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
28
.github/workflows/check-docs.yml
vendored
Normal 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
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -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 }}
|
||||
|
||||
14
.github/workflows/release-dev-version.yml
vendored
14
.github/workflows/release-dev-version.yml
vendored
@ -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 }}
|
||||
|
||||
14
.github/workflows/release-productive-version.yml
vendored
14
.github/workflows/release-productive-version.yml
vendored
@ -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 }}
|
||||
|
||||
107
Dockerfile
107
Dockerfile
@ -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
|
||||
|
||||
@ -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**.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
13
ext/libraries/libsignal-client/Cross.toml
Normal file
13
ext/libraries/libsignal-client/Cross.toml
Normal 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"
|
||||
]
|
||||
@ -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`
|
||||
|
||||
@ -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
|
||||
|
||||
BIN
ext/libraries/libsignal-client/v0.90.0/arm64/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.90.0/arm64/libsignal_jni.so
Normal file
Binary file not shown.
BIN
ext/libraries/libsignal-client/v0.90.0/armv7/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.90.0/armv7/libsignal_jni.so
Normal file
Binary file not shown.
BIN
ext/libraries/libsignal-client/v0.90.0/x86-64/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.90.0/x86-64/libsignal_jni.so
Normal file
Binary file not shown.
BIN
ext/libraries/libsignal-client/v0.92.1/arm64/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.92.1/arm64/libsignal_jni.so
Normal file
Binary file not shown.
BIN
ext/libraries/libsignal-client/v0.92.1/armv7/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.92.1/armv7/libsignal_jni.so
Normal file
Binary file not shown.
BIN
ext/libraries/libsignal-client/v0.92.1/x86-64/libsignal_jni.so
Normal file
BIN
ext/libraries/libsignal-client/v0.92.1/x86-64/libsignal_jni.so
Normal file
Binary file not shown.
@ -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.
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
endpoint: my-custom-send-endpoint/:number
|
||||
method: POST
|
||||
version: 2
|
||||
|
||||
@ -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
|
||||
|
||||
29
plugins/migrate-v1-plugin-to-v2.md
Normal file
29
plugins/migrate-v1-plugin-to-v2.md
Normal 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
|
||||
```
|
||||
|
||||
|
||||
35
plugins/persistence/README.md
Normal file
35
plugins/persistence/README.md
Normal 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?
|
||||
|
||||
3
plugins/persistence/persist-message.def
Normal file
3
plugins/persistence/persist-message.def
Normal file
@ -0,0 +1,3 @@
|
||||
endpoint: persistence/persist-message
|
||||
method: POST
|
||||
version: 2
|
||||
37
plugins/persistence/persist-message.lua
Normal file
37
plugins/persistence/persist-message.lua
Normal 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
|
||||
3
plugins/persistence/query-message.def
Normal file
3
plugins/persistence/query-message.def
Normal file
@ -0,0 +1,3 @@
|
||||
endpoint: persistence/query-message
|
||||
method: GET
|
||||
version: 2
|
||||
35
plugins/persistence/query-message.lua
Normal file
35
plugins/persistence/query-message.lua
Normal 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
|
||||
287
src/api/api.go
287
src/api/api.go
@ -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()})
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
327
src/docs/add_v1_receive_schemas.go
Normal file
327
src/docs/add_v1_receive_schemas.go
Normal 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
|
||||
}
|
||||
7268
src/docs/docs.go
7268
src/docs/docs.go
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
@ -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
|
||||
|
||||
54
src/go.sum
54
src/go.sum
@ -1,10 +1,16 @@
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/bbernhard/gluasql v0.2.0 h1:2UanAHDSbNQeWTr+utubePjItTOSVRE157zuC5rIl3g=
|
||||
github.com/bbernhard/gluasql v0.2.0/go.mod h1:swUFFVYyknwnRX8bfeHaKtNLc1/oeWftlzvlBx8pW5Y=
|
||||
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -18,6 +24,10 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59 h1:puVbTWGX6h0pdi6J8n6dilweYk7jU8LElfw8yBxLHEk=
|
||||
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59/go.mod h1:aVtgxAf6Bfs0hCj+KzIH7Y1aAxg7/7FlslouCh94VVQ=
|
||||
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8 h1:h1DBe5+9JIArCVsBV14fA+RHDXWY8ynUheDL5ZVPOTg=
|
||||
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
@ -26,6 +36,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
@ -42,21 +54,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk=
|
||||
github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw=
|
||||
github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b h1:9HQYGbaDnuRLMuM//SZVkZJ43ANmMStSAQAx4aQX3II=
|
||||
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
@ -67,23 +93,35 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
|
||||
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
||||
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
|
||||
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
@ -122,8 +160,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
|
||||
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
|
||||
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -154,6 +196,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
|
||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -169,11 +213,17 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
|
||||
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc=
|
||||
gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
19
src/main.go
19
src/main.go
@ -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" {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -6,4 +6,5 @@ import (
|
||||
|
||||
type PluginHandler interface {
|
||||
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
|
||||
InitPlugin(pluginConfig PluginConfig) error
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user