mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-09-01 06:38:12 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe9eb5bf3 | ||
|
|
1491a52285 | ||
|
|
a08db23a31 | ||
|
|
5cc379fde0 | ||
|
|
1a2da6c79b | ||
|
|
54507eba20 | ||
|
|
6a225e62c1 | ||
|
|
0e36e15f60 | ||
|
|
7138ffde7c | ||
|
|
7c12204889 | ||
|
|
bef11316e3 | ||
|
|
2d03a6c0ce | ||
|
|
d258f01d59 | ||
|
|
b13a74f43e | ||
|
|
be0842fdb2 | ||
|
|
17b86412f3 | ||
|
|
2ee3907d9e | ||
|
|
af64b772cd | ||
|
|
e34cd5af87 | ||
|
|
fe9df012f1 | ||
|
|
a4f5855b65 | ||
|
|
ede08b64d8 | ||
|
|
81efbd25e1 | ||
|
|
9e7fa344a8 | ||
|
|
25eaf7a5eb | ||
|
|
60b19339c3 | ||
|
|
c88c3f18c2 | ||
|
|
e6810926ca | ||
|
|
595e53d397 | ||
|
|
d4e8664273 | ||
|
|
52d902cda4 | ||
|
|
d1a3956c8c | ||
|
|
7d8f62a4bb | ||
|
|
6b89035dd5 | ||
|
|
99157a035f | ||
|
|
9a2e93a6fa | ||
|
|
c8c461627f | ||
|
|
e63536cb7a | ||
|
|
6e7941b1c1 | ||
|
|
5c0bd056a7 | ||
|
|
ad5d3b76db | ||
|
|
0aaab36e5f | ||
|
|
44ac16d49c | ||
|
|
e8c8b54d52 | ||
|
|
91bdd60c7a | ||
|
|
a7c91737b8 | ||
|
|
af48a4304b | ||
|
|
9155e505af | ||
|
|
d0ec5b1b28 | ||
|
|
fa0f67fa69 | ||
|
|
7aa70683aa | ||
|
|
82f7151212 | ||
|
|
e68cabf88f | ||
|
|
4bbadbf29e | ||
|
|
b37aac4d5f | ||
|
|
db63fd15e0 | ||
|
|
650367e88a | ||
|
|
69457e8f81 | ||
|
|
2e8171d84c | ||
|
|
419b18331d | ||
|
|
16c3214862 | ||
|
|
6e1698e820 | ||
|
|
a168cf5547 | ||
|
|
1f222d7261 | ||
|
|
6ac432b509 | ||
|
|
948a522ad8 | ||
|
|
6fa06f9611 | ||
|
|
eda99213a7 | ||
|
|
2ac55eec07 | ||
|
|
cb5e64a6d5 | ||
|
|
0a7c53a10a | ||
|
|
09252b4b87 | ||
|
|
881e9203c1 | ||
|
|
e2523b102c | ||
|
|
184231020c | ||
|
|
52e058eb06 | ||
|
|
e8a80ef896 | ||
|
|
97197f1fbe | ||
|
|
e19dc961fd | ||
|
|
246ebfd4e6 | ||
|
|
40232de0d0 | ||
|
|
0f2c12f28e | ||
|
|
e712494d7f | ||
|
|
e0af0091c9 | ||
|
|
252389efbe |
43
.github/workflows/check-docs.yml
vendored
Normal file
43
.github/workflows/check-docs.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: Check Generated Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-docs:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.0.0
|
||||||
|
with:
|
||||||
|
go-version-file: src/go.mod
|
||||||
|
cache-dependency-path: src/go.sum
|
||||||
|
|
||||||
|
- name: Regenerate docs
|
||||||
|
working-directory: src
|
||||||
|
run: go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault
|
||||||
|
|
||||||
|
- name: Build json schemas
|
||||||
|
run: |
|
||||||
|
export JAVA_HOME="$JAVA_HOME_25_X64"
|
||||||
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
|
SIGNAL_CLI_VERSION="$(grep -m1 '^ARG SIGNAL_CLI_VERSION=' Dockerfile | cut -d= -f2)"
|
||||||
|
git clone --depth 1 --branch v${SIGNAL_CLI_VERSION} https://github.com/AsamK/signal-cli/ signal-cli
|
||||||
|
cd signal-cli
|
||||||
|
./gradlew jsonSchemas
|
||||||
|
cp -r build/generated/META-INF/schemas ${GITHUB_WORKSPACE}/src/docs/signal-cli-schemas
|
||||||
|
|
||||||
|
- name: Inject receive schemas
|
||||||
|
working-directory: src/docs
|
||||||
|
run: |
|
||||||
|
go run add_v1_receive_schemas.go signal-cli-schemas
|
||||||
|
|
||||||
|
- name: Fail if docs are out of date
|
||||||
|
run: |
|
||||||
|
git diff --exit-code -- src/docs/docs.go src/docs/swagger.json src/docs/swagger.yaml
|
||||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -8,14 +8,16 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- '**' #every branch
|
- '**' #every branch
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Available platforms
|
- name: Available platforms
|
||||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||||
- name: Install Podman
|
- name: Install Podman
|
||||||
@ -25,11 +27,11 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|||||||
38
.github/workflows/deploy-pages.yml
vendored
Normal file
38
.github/workflows/deploy-pages.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: Deploy API Docs to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v6
|
||||||
|
|
||||||
|
- name: Build static documentation bundle
|
||||||
|
run: |
|
||||||
|
src/docs/build-static-bundle.sh site "${{ github.event.release.tag_name }}"
|
||||||
|
|
||||||
|
- name: Upload Pages artifact
|
||||||
|
uses: actions/upload-pages-artifact@v5
|
||||||
|
with:
|
||||||
|
path: site
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v5
|
||||||
16
.github/workflows/release-dev-version.yml
vendored
16
.github/workflows/release-dev-version.yml
vendored
@ -4,18 +4,24 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'Version'
|
description: 'Version'
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check-docs:
|
||||||
|
uses: ./.github/workflows/check-docs.yml
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
needs: check-docs
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Available platforms
|
- name: Available platforms
|
||||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||||
- name: Install Podman
|
- name: Install Podman
|
||||||
@ -24,11 +30,11 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|||||||
16
.github/workflows/release-productive-version.yml
vendored
16
.github/workflows/release-productive-version.yml
vendored
@ -4,18 +4,24 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'Version'
|
description: 'Version'
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check-docs:
|
||||||
|
uses: ./.github/workflows/check-docs.yml
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
needs: check-docs
|
||||||
steps:
|
steps:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Available platforms
|
- name: Available platforms
|
||||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||||
- name: Install Podman
|
- name: Install Podman
|
||||||
@ -24,11 +30,11 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ signal-cli-config
|
|||||||
src/main
|
src/main
|
||||||
src/signal-cli-rest-api
|
src/signal-cli-rest-api
|
||||||
.idea/
|
.idea/
|
||||||
|
./persistence
|
||||||
|
|||||||
107
Dockerfile
107
Dockerfile
@ -1,9 +1,7 @@
|
|||||||
ARG SIGNAL_CLI_VERSION=0.14.2
|
ARG SIGNAL_CLI_VERSION=0.14.7
|
||||||
ARG LIBSIGNAL_CLIENT_VERSION=0.90.0
|
ARG LIBSIGNAL_CLIENT_VERSION=0.99.1
|
||||||
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.14.2+morph027+2
|
|
||||||
|
|
||||||
ARG SWAG_VERSION=1.16.4
|
ARG SWAG_VERSION=1.16.4
|
||||||
ARG GRAALVM_VERSION=25.0.2
|
|
||||||
|
|
||||||
ARG BUILD_VERSION_ARG=unset
|
ARG BUILD_VERSION_ARG=unset
|
||||||
|
|
||||||
@ -12,12 +10,18 @@ FROM golang:1.26-trixie AS buildcontainer
|
|||||||
ARG SIGNAL_CLI_VERSION
|
ARG SIGNAL_CLI_VERSION
|
||||||
ARG LIBSIGNAL_CLIENT_VERSION
|
ARG LIBSIGNAL_CLIENT_VERSION
|
||||||
ARG SWAG_VERSION
|
ARG SWAG_VERSION
|
||||||
ARG GRAALVM_VERSION
|
|
||||||
ARG BUILD_VERSION_ARG
|
ARG BUILD_VERSION_ARG
|
||||||
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION
|
|
||||||
|
|
||||||
COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
|
RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
||||||
COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends \
|
||||||
|
wget git locales zip unzip \
|
||||||
|
file build-essential libz-dev zlib1g-dev binutils \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
#COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
|
||||||
|
RUN wget https://github.com/bbernhard/libsignal-client-builds/releases/download/v${LIBSIGNAL_CLIENT_VERSION}/libsignal-client-build-v${LIBSIGNAL_CLIENT_VERSION}.tar.gz -O /tmp/libsignal-client.tar.gz
|
||||||
|
RUN cd /tmp && mkdir -p /tmp/libsignal-client-libraries && tar xf libsignal-client.tar.gz && mv x86-64 armv7 arm64 -t libsignal-client-libraries
|
||||||
|
|
||||||
# use architecture specific libsignal_jni.so
|
# use architecture specific libsignal_jni.so
|
||||||
RUN arch="$(uname -m)"; \
|
RUN arch="$(uname -m)"; \
|
||||||
@ -28,13 +32,6 @@ RUN arch="$(uname -m)"; \
|
|||||||
*) echo "Unknown architecture" && exit 1 ;; \
|
*) echo "Unknown architecture" && exit 1 ;; \
|
||||||
esac;
|
esac;
|
||||||
|
|
||||||
RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends \
|
|
||||||
wget git locales zip unzip \
|
|
||||||
file build-essential libz-dev zlib1g-dev binutils \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||||
update-locale LANG=en_US.UTF-8
|
update-locale LANG=en_US.UTF-8
|
||||||
@ -43,71 +40,29 @@ ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"
|
|||||||
|
|
||||||
ENV LANG en_US.UTF-8
|
ENV LANG en_US.UTF-8
|
||||||
|
|
||||||
#RUN cd /tmp/ \
|
|
||||||
# && git clone https://github.com/swaggo/swag.git swag-${SWAG_VERSION} \
|
|
||||||
# && cd swag-${SWAG_VERSION} \
|
|
||||||
# && git checkout -q v${SWAG_VERSION} \
|
|
||||||
# && make -s < /dev/null > /dev/null \
|
|
||||||
# && cp /tmp/swag-${SWAG_VERSION}/swag /usr/bin/swag \
|
|
||||||
# && rm -r /tmp/swag-${SWAG_VERSION}
|
|
||||||
|
|
||||||
|
|
||||||
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
|
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
|
||||||
|
|
||||||
|
|
||||||
RUN cd /tmp/ \
|
RUN cd /tmp/ \
|
||||||
&& wget -nv https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz -O /tmp/signal-cli.tar.gz \
|
&& wget -nv https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz -O /tmp/signal-cli.tar.gz \
|
||||||
&& tar xf signal-cli.tar.gz
|
&& tar xf signal-cli.tar.gz
|
||||||
|
|
||||||
# build native image with graalvm
|
|
||||||
|
|
||||||
RUN arch="$(uname -m)"; \
|
|
||||||
case "$arch" in \
|
|
||||||
aarch64) wget -nv https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAALVM_VERSION}/graalvm-community-jdk-${GRAALVM_VERSION}_linux-aarch64_bin.tar.gz -O /tmp/gvm.tar.gz ;; \
|
|
||||||
armv7l) echo "GRAALVM doesn't support 32bit" ;; \
|
|
||||||
x86_64) wget -nv https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${GRAALVM_VERSION}/graalvm-community-jdk-${GRAALVM_VERSION}_linux-x64_bin.tar.gz -O /tmp/gvm.tar.gz ;; \
|
|
||||||
*) echo "Invalid architecture" ;; \
|
|
||||||
esac;
|
|
||||||
|
|
||||||
RUN if [ "$(uname -m)" = "x86_64" ]; then \
|
RUN if [ "$(uname -m)" = "x86_64" ]; then \
|
||||||
cd /tmp \
|
cd /tmp \
|
||||||
&& git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \
|
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
|
||||||
&& cd signal-cli-${SIGNAL_CLI_VERSION}-source \
|
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
|
||||||
&& git checkout -q v${SIGNAL_CLI_VERSION} \
|
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/x86-64/signal-cli-native /tmp/signal-cli-native \
|
||||||
&& cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \
|
&& chmod +x /tmp/signal-cli-native; \
|
||||||
&& export GRAALVM_HOME=/tmp/graalvm \
|
|
||||||
&& export PATH=/tmp/graalvm/bin:$PATH \
|
|
||||||
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
|
|
||||||
&& sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' src/main/java/org/asamk/signal/BaseConfig.java \
|
|
||||||
&& ./gradlew build \
|
|
||||||
&& ./gradlew installDist \
|
|
||||||
&& ls build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar || (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found. Maybe the version needs to be bumped in the signal-cli-rest-api Dockerfile?\n\n" && echo "Available version: \n" && ls build/install/signal-cli/lib/libsignal-client-* && echo "\n\n" && exit 1) \
|
|
||||||
&& cd /tmp \
|
|
||||||
&& cp signal-cli-${SIGNAL_CLI_VERSION}-source/build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal-client.jar \
|
|
||||||
&& zip -qu libsignal-client.jar libsignal_jni.so \
|
|
||||||
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
|
|
||||||
&& git apply /tmp/signal-cli-native.patch \
|
|
||||||
&& ./gradlew -q nativeCompile; \
|
|
||||||
elif [ "$(uname -m)" = "aarch64" ] ; then \
|
elif [ "$(uname -m)" = "aarch64" ] ; then \
|
||||||
echo "Use native image from @morph027 (https://packaging.gitlab.io/signal-cli/) for arm64 - many thanks to @morph027" \
|
cd /tmp \
|
||||||
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \
|
&& wget https://github.com/bbernhard/signal-cli-native-builds/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
|
||||||
&& echo "deb [signed-by=/usr/share/keyrings/signal-cli-native.pgp] https://packaging.gitlab.io/signal-cli signalcli main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
|
&& tar xvf signal-cli-native-v${SIGNAL_CLI_VERSION}.tar.gz \
|
||||||
&& mkdir -p /tmp/signal-cli-native \
|
&& cp signal-cli-native-v${SIGNAL_CLI_VERSION}/arm64/signal-cli-native /tmp/signal-cli-native \
|
||||||
&& cd /tmp/signal-cli-native \
|
&& chmod +x /tmp/signal-cli-native; \
|
||||||
#&& wget https://gitlab.com/packaging/signal-cli/-/jobs/3716873649/artifacts/download?file_type=archive -O /tmp/signal-cli-native/archive.zip \
|
|
||||||
#&& unzip archive.zip \
|
|
||||||
#&& mv signal-cli-native-arm64/*deb . \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get download signal-cli-native=${SIGNAL_CLI_NATIVE_PACKAGE_VERSION} \
|
|
||||||
&& ar x *.deb \
|
|
||||||
&& tar xf data.tar.gz \
|
|
||||||
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
|
|
||||||
&& cp /tmp/signal-cli-native/usr/bin/signal-cli-native /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
|
|
||||||
elif [ "$(uname -m)" = "armv7l" ] ; then \
|
elif [ "$(uname -m)" = "armv7l" ] ; then \
|
||||||
echo "GRAALVM doesn't support 32bit" \
|
echo "GRAALVM doesn't support 32bit" \
|
||||||
&& echo "Creating temporary file, otherwise the below copy doesn't work for armv7" \
|
&& echo "Creating temporary file, otherwise the below copy doesn't work for armv7" \
|
||||||
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
|
&& mkdir -p /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile \
|
||||||
&& touch /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli; \
|
&& touch /tmp/signal-cli-native; \
|
||||||
else \
|
else \
|
||||||
echo "Unknown architecture"; \
|
echo "Unknown architecture"; \
|
||||||
fi;
|
fi;
|
||||||
@ -139,10 +94,22 @@ COPY src/main.go /tmp/signal-cli-rest-api-src/
|
|||||||
COPY src/go.mod /tmp/signal-cli-rest-api-src/
|
COPY src/go.mod /tmp/signal-cli-rest-api-src/
|
||||||
COPY src/go.sum /tmp/signal-cli-rest-api-src/
|
COPY src/go.sum /tmp/signal-cli-rest-api-src/
|
||||||
COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/
|
COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/
|
||||||
|
COPY src/docs/add_v1_receive_schemas.go /tmp/signal-cli-rest-api-src/docs/add_v1_receive_schemas.go
|
||||||
|
|
||||||
|
RUN ls -la /tmp/signal-cli-rest-api-src
|
||||||
|
|
||||||
|
# build the docs
|
||||||
|
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault --outputTypes "go,json"
|
||||||
|
|
||||||
|
|
||||||
|
# manually add the json schemas for the receive V1 endpoint to the docs
|
||||||
|
RUN cd /tmp/signal-cli-rest-api-src/docs \
|
||||||
|
&& wget https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz \
|
||||||
|
&& mkdir signal-cli-schemas \
|
||||||
|
&& tar xf signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz -C signal-cli-schemas \
|
||||||
|
&& go run add_v1_receive_schemas.go signal-cli-schemas
|
||||||
|
|
||||||
# build signal-cli-rest-api
|
# build signal-cli-rest-api
|
||||||
RUN ls -la /tmp/signal-cli-rest-api-src
|
|
||||||
RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefault
|
|
||||||
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
|
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
|
||||||
RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v
|
RUN cd /tmp/signal-cli-rest-api-src && go test ./client -v && go test ./utils -v
|
||||||
|
|
||||||
@ -177,7 +144,7 @@ RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
|||||||
|
|
||||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api
|
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api
|
||||||
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
|
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
|
||||||
COPY --from=buildcontainer /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
|
COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
|
||||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
|
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
|
||||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
|
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
diff --git a/build.gradle.kts b/build.gradle.kts
|
diff --git a/build.gradle.kts b/build.gradle.kts
|
||||||
index 5d127e4c..7ca155f9 100644
|
index 64e29294..5d0e53cc 100644
|
||||||
--- a/build.gradle.kts
|
--- a/build.gradle.kts
|
||||||
+++ b/build.gradle.kts
|
+++ b/build.gradle.kts
|
||||||
@@ -91,6 +91,7 @@ dependencies {
|
@@ -104,6 +104,7 @@ dependencies {
|
||||||
implementation(libs.logback)
|
implementation(libs.micronaut.json.schema.generator)
|
||||||
implementation(libs.zxing)
|
}
|
||||||
implementation(project(":libsignal-cli"))
|
implementation(project(":libsignal-cli"))
|
||||||
+ implementation(files("/tmp/libsignal-client.jar"))
|
+ implementation(files("/tmp/libsignal-client.jar"))
|
||||||
|
|
||||||
testImplementation(libs.junit.jupiter)
|
testImplementation(libs.junit.jupiter)
|
||||||
testImplementation(platform(libs.junit.jupiter.bom))
|
testImplementation(platform(libs.junit.jupiter.bom))
|
||||||
@@ -107,6 +108,10 @@ configurations {
|
@@ -120,6 +121,10 @@ configurations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
endpoint: my-custom-send-endpoint/:number
|
||||||
method: POST
|
method: POST
|
||||||
|
version: 2
|
||||||
```
|
```
|
||||||
|
|
||||||
The `endpoint` specifies the URI of the newly created endpoint. All custom endpoints are registered under the `/v1/plugins` endpoint. So, our `my-custom-send-endpoint` will be available at `/v1/plugins/my-custom-endpoint`. If you want to use variables inside the endpoint, prefix them with a `:`.
|
The `endpoint` specifies the URI of the newly created endpoint. All custom endpoints are registered under the `/v1/plugins` endpoint. So, our `my-custom-send-endpoint` will be available at `/v1/plugins/my-custom-endpoint`. If you want to use variables inside the endpoint, prefix them with a `:`.
|
||||||
|
|
||||||
|
If you write a new plugin, it is recommended to use the `version: 2` of the plugin mechanism. (`version: 1` is deprecated!)
|
||||||
|
|
||||||
The `method` parameter specifies the HTTP method that is used for the endpoint registration.
|
The `method` parameter specifies the HTTP method that is used for the endpoint registration.
|
||||||
|
|
||||||
# The script file
|
# The script file
|
||||||
|
|
||||||
The script file (with the file suffix `.lua`) contains the implementation of the endpoint.
|
The script file (with the file suffix `.lua`) contains the implementation of the endpoint. Each plugin must implement a `exec` function and can optionally implement a `init` function. The `exec` function gets called whenever the plugin is called. The `init` function only gets called once during the startup and can be used to perform initialization tasks.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
local http = require("http")
|
--
|
||||||
local json = require("json")
|
function exec()
|
||||||
|
local http = require("http")
|
||||||
|
local json = require("json")
|
||||||
|
|
||||||
local url = "http://127.0.0.1:8080/v2/send"
|
local url = "http://127.0.0.1:8080/v2/send"
|
||||||
|
|
||||||
local customEndpointPayload = json.decode(pluginInputData.payload)
|
local customEndpointPayload = json.decode(pluginInputData.payload)
|
||||||
|
|
||||||
local sendEndpointPayload = {
|
local sendEndpointPayload = {
|
||||||
recipients = {customEndpointPayload.recipient},
|
recipients = {customEndpointPayload.recipient},
|
||||||
message = customEndpointPayload.message,
|
message = customEndpointPayload.message,
|
||||||
number = pluginInputData.Params.number
|
number = pluginInputData.Params.number
|
||||||
}
|
}
|
||||||
|
|
||||||
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
|
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
|
||||||
|
|
||||||
response, error_message = http.request("POST", url, {
|
response, error_message = http.request("POST", url, {
|
||||||
timeout="30s",
|
timeout="30s",
|
||||||
headers={
|
headers={
|
||||||
Accept="*/*",
|
Accept="*/*",
|
||||||
["Content-Type"]="application/json"
|
["Content-Type"]="application/json"
|
||||||
},
|
},
|
||||||
body=encodedSendEndpointPayload
|
body=encodedSendEndpointPayload
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginOutputData:SetPayload(response["body"])
|
pluginOutputData:SetPayload(response["body"])
|
||||||
pluginOutputData:SetHttpStatusCode(response.status_code)
|
pluginOutputData:SetHttpStatusCode(response.status_code)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- optional init function
|
||||||
|
function init()
|
||||||
|
|
||||||
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
What the lua script does, is parse the JSON payload from the custom request, extract the `recipient` and the `message` from the payload and the `number` from the URL parameter and call the `/v2/send` endpoint with those parameters. The HTTP status code and the body that is returned by the HTTP request is then returned to the caller (this is done via the `pluginOutputData:SetPayload` and `pluginOutputData:SetHttpStatusCode` functions.
|
What the lua script does, is parse the JSON payload from the custom request, extract the `recipient` and the `message` from the payload and the `number` from the URL parameter and call the `/v2/send` endpoint with those parameters. The HTTP status code and the body that is returned by the HTTP request is then returned to the caller (this is done via the `pluginOutputData:SetPayload` and `pluginOutputData:SetHttpStatusCode` functions.
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
endpoint: my-custom-send-endpoint/:number
|
endpoint: my-custom-send-endpoint/:number
|
||||||
method: POST
|
method: POST
|
||||||
|
version: 2
|
||||||
|
|||||||
@ -1,27 +1,27 @@
|
|||||||
local http = require("http")
|
local http = require("http")
|
||||||
local json = require("json")
|
local json = require("json")
|
||||||
|
|
||||||
local url = "http://127.0.0.1:8080/v2/send"
|
function exec()
|
||||||
|
local url = "http://127.0.0.1:8080/v2/send"
|
||||||
|
local customEndpointPayload = json.decode(pluginInputData.payload)
|
||||||
|
local sendEndpointPayload = {
|
||||||
|
recipients = {customEndpointPayload.recipient},
|
||||||
|
message = customEndpointPayload.message,
|
||||||
|
number = pluginInputData.Params.number
|
||||||
|
}
|
||||||
|
|
||||||
local customEndpointPayload = json.decode(pluginInputData.payload)
|
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
|
||||||
|
print(encodedSendEndpointPayload)
|
||||||
|
|
||||||
local sendEndpointPayload = {
|
response, error_message = http.request("POST", url, {
|
||||||
recipients = {customEndpointPayload.recipient},
|
timeout="30s",
|
||||||
message = customEndpointPayload.message,
|
headers={
|
||||||
number = pluginInputData.Params.number
|
Accept="*/*",
|
||||||
}
|
["Content-Type"]="application/json"
|
||||||
|
},
|
||||||
|
body=encodedSendEndpointPayload
|
||||||
|
})
|
||||||
|
|
||||||
local encodedSendEndpointPayload = json.encode(sendEndpointPayload)
|
pluginOutputData:SetPayload(response["body"])
|
||||||
print(encodedSendEndpointPayload)
|
pluginOutputData:SetHttpStatusCode(response.status_code)
|
||||||
|
end
|
||||||
response, error_message = http.request("POST", url, {
|
|
||||||
timeout="30s",
|
|
||||||
headers={
|
|
||||||
Accept="*/*",
|
|
||||||
["Content-Type"]="application/json"
|
|
||||||
},
|
|
||||||
body=encodedSendEndpointPayload
|
|
||||||
})
|
|
||||||
|
|
||||||
pluginOutputData:SetPayload(response["body"])
|
|
||||||
pluginOutputData:SetHttpStatusCode(response.status_code)
|
|
||||||
|
|||||||
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
|
||||||
@ -173,10 +173,6 @@ type TrustIdentityRequest struct {
|
|||||||
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
|
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMessageResponse struct {
|
|
||||||
Timestamp string `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteDeleteResponse struct {
|
type RemoteDeleteResponse struct {
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
@ -324,8 +320,14 @@ func (a *Api) RegisterNumber(c *gin.Context) {
|
|||||||
|
|
||||||
err = a.signalClient.RegisterNumber(number, req.UseVoice, req.Captcha)
|
err = a.signalClient.RegisterNumber(number, req.UseVoice, req.Captcha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
switch err.(type) {
|
||||||
return
|
case *client.InvalidTransportError:
|
||||||
|
c.JSON(400, Error{Msg: "Couldn't use SMS verification to register the specified number. Wait 60 seconds and try again with {\"use_voice\": true}"})
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
c.JSON(400, Error{Msg: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.Writer.WriteHeader(201)
|
c.Writer.WriteHeader(201)
|
||||||
}
|
}
|
||||||
@ -470,7 +472,6 @@ func (a *Api) VerifyRegisteredNumber(c *gin.Context) {
|
|||||||
// @Router /v1/send [post]
|
// @Router /v1/send [post]
|
||||||
// @Deprecated
|
// @Deprecated
|
||||||
func (a *Api) Send(c *gin.Context) {
|
func (a *Api) Send(c *gin.Context) {
|
||||||
|
|
||||||
var req SendMessageV1
|
var req SendMessageV1
|
||||||
err := c.BindJSON(&req)
|
err := c.BindJSON(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -483,12 +484,12 @@ func (a *Api) Send(c *gin.Context) {
|
|||||||
base64Attachments = append(base64Attachments, req.Base64Attachment)
|
base64Attachments = append(base64Attachments, req.Base64Attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup)
|
resp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, Error{Msg: err.Error()})
|
c.JSON(400, Error{Msg: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt(timestamp.Timestamp, 10)})
|
c.JSON(201, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Send a signal message.
|
// @Summary Send a signal message.
|
||||||
@ -496,7 +497,7 @@ func (a *Api) Send(c *gin.Context) {
|
|||||||
// @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes.
|
// @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes.
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 201 {object} SendMessageResponse
|
// @Success 201 {object} ds.SendMessageResponse
|
||||||
// @Failure 400 {object} SendMessageError
|
// @Failure 400 {object} SendMessageError
|
||||||
// @Param data body SendMessageV2 true "Input Data"
|
// @Param data body SendMessageV2 true "Input Data"
|
||||||
// @Router /v2/send [post]
|
// @Router /v2/send [post]
|
||||||
@ -568,7 +569,7 @@ func (a *Api) SendV2(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt((*data)[0].Timestamp, 10)})
|
c.JSON(201, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) {
|
func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) {
|
||||||
|
|||||||
@ -44,6 +44,9 @@ func (attachmentEntry *AttachmentEntry) extractMetaData(attachmentData string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachmentEntry.Base64 = attachmentData[base64FlagIndex+len("base64,"):]
|
attachmentEntry.Base64 = attachmentData[base64FlagIndex+len("base64,"):]
|
||||||
|
if base64FlagIndex == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
metaDataKeys := map[string]string{
|
metaDataKeys := map[string]string{
|
||||||
"data:": "MimeInfo",
|
"data:": "MimeInfo",
|
||||||
"filename=": "FileName",
|
"filename=": "FileName",
|
||||||
|
|||||||
@ -45,6 +45,9 @@ func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *tes
|
|||||||
{
|
{
|
||||||
"-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=",
|
"-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"base64 prefix at start +data", "base64,data:someData", false, "data:someData", false, "", "", "data:someData",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentTmp := flag.String("attachment-tmp-dir", string(os.PathSeparator)+"tmp"+string(os.PathSeparator), "Attachment tmp directory")
|
attachmentTmp := flag.String("attachment-tmp-dir", string(os.PathSeparator)+"tmp"+string(os.PathSeparator), "Attachment tmp directory")
|
||||||
|
|||||||
@ -119,6 +119,7 @@ type GroupEntry struct {
|
|||||||
InternalId string `json:"internal_id"`
|
InternalId string `json:"internal_id"`
|
||||||
Members []string `json:"members"`
|
Members []string `json:"members"`
|
||||||
Blocked bool `json:"blocked"`
|
Blocked bool `json:"blocked"`
|
||||||
|
Member bool `json:"member"`
|
||||||
PendingInvites []string `json:"pending_invites"`
|
PendingInvites []string `json:"pending_invites"`
|
||||||
PendingRequests []string `json:"pending_requests"`
|
PendingRequests []string `json:"pending_requests"`
|
||||||
InviteLink string `json:"invite_link"`
|
InviteLink string `json:"invite_link"`
|
||||||
@ -143,6 +144,7 @@ type ExpandedGroupEntry struct {
|
|||||||
InternalId string `json:"internal_id"`
|
InternalId string `json:"internal_id"`
|
||||||
Members []GroupMember `json:"members"`
|
Members []GroupMember `json:"members"`
|
||||||
Blocked bool `json:"blocked"`
|
Blocked bool `json:"blocked"`
|
||||||
|
Member bool `json:"member"`
|
||||||
PendingInvites []GroupMember `json:"pending_invites"`
|
PendingInvites []GroupMember `json:"pending_invites"`
|
||||||
PendingRequests []GroupMember `json:"pending_requests"`
|
PendingRequests []GroupMember `json:"pending_requests"`
|
||||||
InviteLink string `json:"invite_link"`
|
InviteLink string `json:"invite_link"`
|
||||||
@ -477,8 +479,8 @@ func validateLinkPreview(message string, linkPreview *ds.LinkPreviewType) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*SendResponse, error) {
|
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.SendMessageResponse, error) {
|
||||||
var resp SendResponse
|
var rawData string
|
||||||
var linkPreviewAttachmentEntry *AttachmentEntry = nil
|
var linkPreviewAttachmentEntry *AttachmentEntry = nil
|
||||||
|
|
||||||
if len(signalCliSendRequest.Recipients) == 0 {
|
if len(signalCliSendRequest.Recipients) == 0 {
|
||||||
@ -613,23 +615,13 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData, err := jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request)
|
rawData, err = jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(rawData), &resp)
|
|
||||||
if err != nil {
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
|
||||||
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cmd := []string{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
|
cmd := []string{"--output", "json", "--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
|
||||||
if signalCliSendRequest.RecipientType == ds.Number {
|
if signalCliSendRequest.RecipientType == ds.Number {
|
||||||
cmd = append(cmd, signalCliSendRequest.Recipients...)
|
cmd = append(cmd, signalCliSendRequest.Recipients...)
|
||||||
} else if signalCliSendRequest.RecipientType == ds.Group {
|
} else if signalCliSendRequest.RecipientType == ds.Group {
|
||||||
@ -717,23 +709,48 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
|
|||||||
cmd = append(cmd, "--view-once")
|
cmd = append(cmd, "--view-once")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData, err := s.cliClient.Execute(true, cmd, signalCliSendRequest.Message)
|
rawData, err = s.cliClient.Execute(true, cmd, signalCliSendRequest.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
|
||||||
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp.Timestamp, err = strconv.ParseInt(strings.TrimSuffix(rawData, "\n"), 10, 64)
|
}
|
||||||
if err != nil {
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
type SignalCliSendResponse struct {
|
||||||
return nil, errors.New(strings.Replace(rawData, "\n", "", -1)) //in case we can't parse the timestamp, it means signal-cli threw an error. So instead of returning the parsing error, return the actual error from signal-cli
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Results []struct {
|
||||||
|
RecipientAddress struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"recipientAddress"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var signalCliSendResponse SignalCliSendResponse
|
||||||
|
err = json.Unmarshal([]byte(rawData), &signalCliSendResponse)
|
||||||
|
if err != nil {
|
||||||
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
|
|
||||||
|
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
||||||
|
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
||||||
}
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
|
|
||||||
|
resp := ds.SendMessageResponse{Timestamp: strconv.FormatInt(signalCliSendResponse.Timestamp, 10)}
|
||||||
|
for _, entry := range signalCliSendResponse.Results {
|
||||||
|
if entry.Type != "SUCCESS" {
|
||||||
|
if resp.Errors == nil {
|
||||||
|
resp.Errors = &ds.SendMessageErrors{}
|
||||||
|
}
|
||||||
|
sendMessageError := ds.SendMessageError{Uuid: entry.RecipientAddress.Uuid, Number: entry.RecipientAddress.Number, Username: entry.RecipientAddress.Username, Reason: entry.Type}
|
||||||
|
resp.Errors.Recipients = append(resp.Errors.Recipients, sendMessageError)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,6 +766,8 @@ func (s *SignalClient) About() About {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha string) error {
|
func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha string) error {
|
||||||
|
var err error
|
||||||
|
var jsonRpc2Client *JsonRpc2Client
|
||||||
if s.signalCliMode == JsonRpc {
|
if s.signalCliMode == JsonRpc {
|
||||||
type Request struct {
|
type Request struct {
|
||||||
UseVoice bool `json:"voice,omitempty"`
|
UseVoice bool `json:"voice,omitempty"`
|
||||||
@ -765,12 +784,11 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
|
|||||||
request.Captcha = captcha
|
request.Captcha = captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonRpc2Client, err := s.getJsonRpc2Client()
|
jsonRpc2Client, err = s.getJsonRpc2Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = jsonRpc2Client.getRaw("register", nil, request)
|
_, err = jsonRpc2Client.getRaw("register", nil, request)
|
||||||
return err
|
|
||||||
} else {
|
} else {
|
||||||
command := []string{"--config", s.signalCliConfig, "-a", number, "register"}
|
command := []string{"--config", s.signalCliConfig, "-a", number, "register"}
|
||||||
|
|
||||||
@ -782,9 +800,16 @@ func (s *SignalClient) RegisterNumber(number string, useVoice bool, captcha stri
|
|||||||
command = append(command, []string{"--captcha", captcha}...)
|
command = append(command, []string{"--captcha", captcha}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.cliClient.Execute(true, command, "")
|
_, err = s.cliClient.Execute(true, command, "")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !useVoice && strings.Contains(err.Error(), "StatusCode: 400 (InvalidTransportModeException)") {
|
||||||
|
return &InvalidTransportError{Description: "Couldn't use SMS verification to register number."}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) UnregisterNumber(number string, deleteAccount bool, deleteLocalData bool) error {
|
func (s *SignalClient) UnregisterNumber(number string, deleteAccount bool, deleteLocalData bool) error {
|
||||||
@ -891,7 +916,7 @@ func (s *SignalClient) VerifyRegisteredNumber(number string, token string, pin s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*SendResponse, error) {
|
func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*ds.SendMessageResponse, error) {
|
||||||
recipientType := ds.Number
|
recipientType := ds.Number
|
||||||
if isGroup {
|
if isGroup {
|
||||||
recipientType = ds.Group
|
recipientType = ds.Group
|
||||||
@ -900,8 +925,8 @@ func (s *SignalClient) SendV1(number string, message string, recipients []string
|
|||||||
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments,
|
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments,
|
||||||
RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil,
|
RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil,
|
||||||
QuoteMentions: nil, TextMode: nil, EditTimestamp: nil, LinkPreview: nil}
|
QuoteMentions: nil, TextMode: nil, EditTimestamp: nil, LinkPreview: nil}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
return timestamp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
|
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
|
||||||
@ -921,7 +946,7 @@ func (s *SignalClient) getJsonRpc2Clients() []*JsonRpc2Client {
|
|||||||
|
|
||||||
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention,
|
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention,
|
||||||
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
|
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
|
||||||
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]SendResponse, error) {
|
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]ds.SendMessageResponse, error) {
|
||||||
if len(recps) == 0 {
|
if len(recps) == 0 {
|
||||||
return nil, errors.New("Please provide at least one recipient")
|
return nil, errors.New("Please provide at least one recipient")
|
||||||
}
|
}
|
||||||
@ -967,17 +992,17 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.")
|
return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.")
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamps := []SendResponse{}
|
responses := []ds.SendMessageResponse{}
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
|
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
|
||||||
RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
||||||
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
||||||
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(numbers) > 0 {
|
if len(numbers) > 0 {
|
||||||
@ -985,11 +1010,11 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
||||||
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
||||||
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(usernames) > 0 {
|
if len(usernames) > 0 {
|
||||||
@ -997,14 +1022,14 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
||||||
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
||||||
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ×tamps, nil
|
return &responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) {
|
func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) {
|
||||||
@ -1322,6 +1347,25 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi
|
|||||||
return s.updateGroupAdmins(number, groupId, admins, false)
|
return s.updateGroupAdmins(number, groupId, admins, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry SignalCliGroupEntry) ExpandedGroupEntry {
|
||||||
|
var groupEntry ExpandedGroupEntry
|
||||||
|
groupEntry.InternalId = signalCliGroupEntry.Id
|
||||||
|
groupEntry.Name = signalCliGroupEntry.Name
|
||||||
|
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
|
||||||
|
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
|
||||||
|
groupEntry.Member = signalCliGroupEntry.IsMember
|
||||||
|
groupEntry.Description = signalCliGroupEntry.Description
|
||||||
|
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
||||||
|
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
||||||
|
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
|
||||||
|
groupEntry.Members = signalCliGroupEntry.Members
|
||||||
|
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
|
||||||
|
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
|
||||||
|
groupEntry.Admins = signalCliGroupEntry.Admins
|
||||||
|
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
|
||||||
|
return groupEntry
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
|
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
|
||||||
groupEntries := []ExpandedGroupEntry{}
|
groupEntries := []ExpandedGroupEntry{}
|
||||||
|
|
||||||
@ -1352,22 +1396,7 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, signalCliGroupEntry := range signalCliGroupEntries {
|
for _, signalCliGroupEntry := range signalCliGroupEntries {
|
||||||
var groupEntry ExpandedGroupEntry
|
groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry))
|
||||||
groupEntry.InternalId = signalCliGroupEntry.Id
|
|
||||||
groupEntry.Name = signalCliGroupEntry.Name
|
|
||||||
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
|
|
||||||
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
|
|
||||||
groupEntry.Description = signalCliGroupEntry.Description
|
|
||||||
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
|
||||||
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
|
||||||
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
|
|
||||||
groupEntry.Members = signalCliGroupEntry.Members
|
|
||||||
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
|
|
||||||
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
|
|
||||||
groupEntry.Admins = signalCliGroupEntry.Admins
|
|
||||||
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
|
|
||||||
|
|
||||||
groupEntries = append(groupEntries, groupEntry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupEntries, nil
|
return groupEntries, nil
|
||||||
@ -1382,7 +1411,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
|
|||||||
groupEntries := []GroupEntry{}
|
groupEntries := []GroupEntry{}
|
||||||
for _, expandedGroupEntry := range expandedGroupEntries {
|
for _, expandedGroupEntry := range expandedGroupEntries {
|
||||||
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
|
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
|
||||||
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
|
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description,
|
||||||
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
|
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
|
||||||
|
|
||||||
members := []string{}
|
members := []string{}
|
||||||
|
|||||||
@ -23,3 +23,11 @@ type InternalError struct {
|
|||||||
func (e *InternalError) Error() string {
|
func (e *InternalError) Error() string {
|
||||||
return e.Description
|
return e.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InvalidTransportError struct {
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidTransportError) Error() string {
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|||||||
93
src/client/groups_test.go
Normal file
93
src/client/groups_test.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sampleListGroupsJSON mirrors the shape of signal-cli's `listGroups` JSON output
|
||||||
|
// (see signal-cli's ListGroupsCommand / the jsonrpc man page). It contains a group
|
||||||
|
// the account is still in (isMember=true), a group it has left or been removed from
|
||||||
|
// (isMember=false) — the "ghost" group that the REST API previously could not
|
||||||
|
// distinguish — and a blocked-but-still-member group to verify the two flags are
|
||||||
|
// mapped independently.
|
||||||
|
const sampleListGroupsJSON = `[
|
||||||
|
{
|
||||||
|
"id": "Pmpi+EfPWmsxiomLe9Nx2XF9HOE483p6iKiFj65iMwI=",
|
||||||
|
"name": "Current Group",
|
||||||
|
"description": "still a member",
|
||||||
|
"isMember": true,
|
||||||
|
"isBlocked": false,
|
||||||
|
"members": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
|
||||||
|
"groupInviteLink": "",
|
||||||
|
"permissionAddMember": "EVERY_MEMBER",
|
||||||
|
"permissionSendMessage": "EVERY_MEMBER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MGFiY2RlZmdoaWo=",
|
||||||
|
"name": "Left Group",
|
||||||
|
"description": "removed or left",
|
||||||
|
"isMember": false,
|
||||||
|
"isBlocked": false,
|
||||||
|
"members": [],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [],
|
||||||
|
"groupInviteLink": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "YmxvY2tlZGdyb3VwaWQwMDAwMDAwMDAwMDAwMDAwMDA=",
|
||||||
|
"name": "Blocked But Member",
|
||||||
|
"description": "blocked yet still a member",
|
||||||
|
"isMember": true,
|
||||||
|
"isBlocked": true,
|
||||||
|
"members": [],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [],
|
||||||
|
"groupInviteLink": ""
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
// TestSignalCliGroupEntryToExpandedGroupEntry verifies that the signal-cli isMember
|
||||||
|
// flag is carried through to the REST ExpandedGroupEntry.Member field, independently
|
||||||
|
// of the isBlocked -> Blocked mapping.
|
||||||
|
func TestSignalCliGroupEntryToExpandedGroupEntry(t *testing.T) {
|
||||||
|
var entries []SignalCliGroupEntry
|
||||||
|
if err := json.Unmarshal([]byte(sampleListGroupsJSON), &entries); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal sample listGroups JSON: %v", err)
|
||||||
|
}
|
||||||
|
if len(entries) != 3 {
|
||||||
|
t.Fatalf("expected 3 group entries, got %d", len(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
wantMember bool
|
||||||
|
wantBlocked bool
|
||||||
|
}{
|
||||||
|
{"Current Group", true, false},
|
||||||
|
{"Left Group", false, false},
|
||||||
|
{"Blocked But Member", true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
got := signalCliGroupEntryToExpandedGroupEntry(entries[i])
|
||||||
|
|
||||||
|
if got.Name != c.name {
|
||||||
|
t.Errorf("entry %d: Name = %q, want %q", i, got.Name, c.name)
|
||||||
|
}
|
||||||
|
if got.Member != c.wantMember {
|
||||||
|
t.Errorf("%s: Member = %v, want %v (must reflect signal-cli isMember)", c.name, got.Member, c.wantMember)
|
||||||
|
}
|
||||||
|
if got.Blocked != c.wantBlocked {
|
||||||
|
t.Errorf("%s: Blocked = %v, want %v", c.name, got.Blocked, c.wantBlocked)
|
||||||
|
}
|
||||||
|
if got.InternalId != entries[i].Id {
|
||||||
|
t.Errorf("%s: InternalId = %q, want %q", c.name, got.InternalId, entries[i].Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -186,6 +186,8 @@ func (r *JsonRpc2Client) getRaw(command string, account *string, args interface{
|
|||||||
ChallengeTokens: challengeTokens,
|
ChallengeTokens: challengeTokens,
|
||||||
Err: errors.New(resp.Err.Message),
|
Err: errors.New(resp.Err.Message),
|
||||||
}
|
}
|
||||||
|
} else if resp.Err.Code == -4 {
|
||||||
|
return "", errors.New("Unable to complete request due to untrusted recipient keys")
|
||||||
}
|
}
|
||||||
return "", errors.New(resp.Err.Message)
|
return "", errors.New(resp.Err.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,3 +58,19 @@ type GroupPermissions struct {
|
|||||||
EditGroup string `json:"edit_group" enums:"only-admins,every-member"`
|
EditGroup string `json:"edit_group" enums:"only-admins,every-member"`
|
||||||
SendMessages string `json:"send_messages" enums:"only-admins,every-member"`
|
SendMessages string `json:"send_messages" enums:"only-admins,every-member"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SendMessageError struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Number string `json:"number,omitempty"`
|
||||||
|
Uuid string `json:"uuid,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMessageErrors struct {
|
||||||
|
Recipients []SendMessageError `json:"recipients,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMessageResponse struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Errors *SendMessageErrors `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@ -4,44 +4,64 @@ These files are generated using the [swaggo/swag](https://github.com/swaggo/swag
|
|||||||
|
|
||||||
There are two steps, first generating the docs and then running the web server.
|
There are two steps, first generating the docs and then running the web server.
|
||||||
|
|
||||||
## Generating the docs
|
## With docker compose (recommended)
|
||||||
|
|
||||||
Regenerate the files with your local source code changes.
|
1. Build the docs
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
```
|
||||||
|
2. Serve the docs
|
||||||
|
```bash
|
||||||
|
docker compose up
|
||||||
|
```
|
||||||
|
3. Go to http://127.0.0.1:8080/swagger/index.html to view the docs
|
||||||
|
* If you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html
|
||||||
|
|
||||||
|
## Locally
|
||||||
|
|
||||||
|
Install [go](https://go.dev/).
|
||||||
|
|
||||||
|
### Generating the docs
|
||||||
|
|
||||||
1. Set the current working dir to `src`
|
1. Set the current working dir to `src`
|
||||||
```bash
|
```bash
|
||||||
cd src
|
cd src
|
||||||
```
|
```
|
||||||
1. Run swag to generate the docs
|
1. Run swag to generate the docs
|
||||||
* Option 1, via docker
|
* Option 1, via go
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault
|
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json"
|
||||||
```
|
```
|
||||||
* Option 2, install swag and run the command line tool
|
* Option 2, directly with swag
|
||||||
```bash
|
```bash
|
||||||
swag init --requiredByDefault
|
swag init --requiredByDefault --outputTypes "go,json"
|
||||||
|
```
|
||||||
|
* Option 3, swag via docker
|
||||||
|
```bash
|
||||||
|
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest init --requiredByDefault --outputTypes "go,json"
|
||||||
|
```
|
||||||
|
1. Set the current working dir to `src/docs`
|
||||||
|
```bash
|
||||||
|
cd docs
|
||||||
|
```
|
||||||
|
1. Add the signal-cli receive V1 schemas
|
||||||
|
* Download the `signal-cli-x.y.z-json-schemas.tar.gz` schema files from https://github.com/AsamK/signal-cli/releases
|
||||||
|
* Extract the files
|
||||||
|
* Run the script to add the schemas
|
||||||
|
```bash
|
||||||
|
go run add_v1_receive_schemas.go ./path-to-signal-cli-json-schema-folder
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run the web server
|
### Run the web server
|
||||||
|
|
||||||
Run the web server to visualize the generated docs.
|
Run the web server to visualize the generated docs.
|
||||||
|
|
||||||
|
1. Navigate to the `src` folder
|
||||||
|
```bash
|
||||||
|
cd src
|
||||||
|
```
|
||||||
1. Run the main script
|
1. Run the main script
|
||||||
* Option 1, via docker, run the command at the root of the repository
|
```bash
|
||||||
```bash
|
go run main.go
|
||||||
docker compose up
|
```
|
||||||
```
|
1. Go to http://127.0.0.1:8080/swagger/index.html
|
||||||
* Option 2, install go and run the command line tool
|
|
||||||
```bash
|
|
||||||
cd src
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
go run main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Navigate to the docs
|
|
||||||
|
|
||||||
The docs are served at: http://127.0.0.1:8080/swagger/index.html
|
|
||||||
|
|
||||||
When serving with docker, if you get a Network error, replace the IP for the docker internal IP in the error, e.g: http://172.18.0.2:8080/swagger/index.html
|
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
24
src/docs/build-static-bundle.sh
Executable file
24
src/docs/build-static-bundle.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
DOCS_DIR="$ROOT_DIR/src/docs"
|
||||||
|
INDEX_FILE="$DOCS_DIR/index.html"
|
||||||
|
OUT_DIR="${1:?output directory argument is required}"
|
||||||
|
DOCS_VERSION="${2:?docs version argument is required}"
|
||||||
|
NORMALIZED_DOCS_VERSION="${DOCS_VERSION#v}"
|
||||||
|
|
||||||
|
echo "Normalized docs version: $NORMALIZED_DOCS_VERSION"
|
||||||
|
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
cp "$DOCS_DIR/swagger.json" "$OUT_DIR/swagger.json"
|
||||||
|
cp "$INDEX_FILE" "$OUT_DIR/index.html"
|
||||||
|
touch "$OUT_DIR/.nojekyll"
|
||||||
|
|
||||||
|
jq --arg version "$NORMALIZED_DOCS_VERSION" '.info.version = $version' "$OUT_DIR/swagger.json" > "$OUT_DIR/swagger.json.tmp"
|
||||||
|
mv "$OUT_DIR/swagger.json.tmp" "$OUT_DIR/swagger.json"
|
||||||
|
|
||||||
|
echo "Static bundle created at: $OUT_DIR"
|
||||||
7534
src/docs/docs.go
7534
src/docs/docs.go
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@
|
|||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "src/docs/swagger.json", //Location of Open API spec in the repo
|
url: "swagger.json",
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
presets: [
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
21
src/go.mod
21
src/go.mod
@ -1,14 +1,15 @@
|
|||||||
module github.com/bbernhard/signal-cli-rest-api
|
module github.com/bbernhard/signal-cli-rest-api
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bbernhard/gluasql v0.2.0
|
||||||
github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9
|
github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4
|
github.com/cyphar/filepath-securejoin v0.2.4
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8
|
github.com/gabriel-vasile/mimetype v1.4.8
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.1
|
github.com/sirupsen/logrus v1.9.1
|
||||||
@ -17,13 +18,14 @@ require (
|
|||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/yuin/gopher-lua v1.1.1
|
github.com/yuin/gopher-lua v1.1.2
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
|
||||||
layeh.com/gopher-luar v1.0.11
|
layeh.com/gopher-luar v1.0.11
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.2.0 // indirect
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/bytedance/sonic v1.12.8 // indirect
|
github.com/bytedance/sonic v1.12.8 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||||
@ -36,13 +38,16 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.44 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
@ -52,11 +57,11 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.14.0 // indirect
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.52.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.55.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.44.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.4 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
86
src/go.sum
86
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 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/bbernhard/gluasql v0.2.0 h1:2UanAHDSbNQeWTr+utubePjItTOSVRE157zuC5rIl3g=
|
||||||
|
github.com/bbernhard/gluasql v0.2.0/go.mod h1:swUFFVYyknwnRX8bfeHaKtNLc1/oeWftlzvlBx8pW5Y=
|
||||||
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||||
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@ -18,6 +24,10 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59 h1:puVbTWGX6h0pdi6J8n6dilweYk7jU8LElfw8yBxLHEk=
|
||||||
|
github.com/dolthub/go-mysql-server v0.14.1-0.20230203234751-35092f80aa59/go.mod h1:aVtgxAf6Bfs0hCj+KzIH7Y1aAxg7/7FlslouCh94VVQ=
|
||||||
|
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8 h1:h1DBe5+9JIArCVsBV14fA+RHDXWY8ynUheDL5ZVPOTg=
|
||||||
|
github.com/dolthub/vitess v0.0.0-20230201234433-864c7d109df8/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
@ -26,6 +36,8 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
|
|||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||||
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
@ -42,21 +54,35 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||||
|
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk=
|
||||||
|
github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw=
|
||||||
|
github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b h1:9HQYGbaDnuRLMuM//SZVkZJ43ANmMStSAQAx4aQX3II=
|
||||||
|
github.com/junhsieh/goexamples v0.0.0-20210908032526-acdd3160140b/go.mod h1:JNqB8Da6SnlJvmZusESDfgqUkJXO6+/a1by1etsVJ2M=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
@ -67,23 +93,35 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
|
||||||
|
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
|
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
||||||
|
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
|
||||||
|
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
|
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
|
||||||
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
@ -122,27 +160,31 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||||
|
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
|
||||||
|
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
|
||||||
|
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
|
||||||
|
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
|
||||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -152,8 +194,10 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4=
|
||||||
|
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -161,19 +205,25 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
|
||||||
|
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||||
|
google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
|
||||||
|
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc=
|
||||||
|
gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
12
src/main.go
12
src/main.go
@ -349,6 +349,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, pluginConfig := range pluginConfigs.Configs {
|
for _, pluginConfig := range pluginConfigs.Configs {
|
||||||
|
if pluginConfig.Version > 1 {
|
||||||
|
err = pluginHandler.InitPlugin(pluginConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Couldn't initialize plugin ", pluginConfig.Endpoint)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("Plugin ", pluginConfig.Endpoint, " still uses plugin version 1. Consider migrating to version 2! (see https://github.com/bbernhard/signal-cli-rest-api/plugins/migrate-v1-plugin-to-v2.md)")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Registering plugin ", pluginConfig.Endpoint)
|
||||||
|
|
||||||
if pluginConfig.Method == "GET" {
|
if pluginConfig.Method == "GET" {
|
||||||
plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
||||||
} else if pluginConfig.Method == "POST" {
|
} else if pluginConfig.Method == "POST" {
|
||||||
|
|||||||
@ -1,27 +1,30 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yuin/gopher-lua"
|
"errors"
|
||||||
"github.com/cjoudrey/gluahttp"
|
|
||||||
"layeh.com/gopher-luar"
|
|
||||||
luajson "layeh.com/gopher-json"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io"
|
"io"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/bbernhard/signal-cli-rest-api/utils"
|
|
||||||
"github.com/bbernhard/signal-cli-rest-api/api"
|
|
||||||
"strings"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gluasql "github.com/bbernhard/gluasql"
|
||||||
|
"github.com/bbernhard/signal-cli-rest-api/api"
|
||||||
|
"github.com/bbernhard/signal-cli-rest-api/utils"
|
||||||
|
"github.com/cjoudrey/gluahttp"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
luajson "layeh.com/gopher-json"
|
||||||
|
luar "layeh.com/gopher-luar"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginInputData struct {
|
type PluginInputData struct {
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
QueryParams map[string]string
|
QueryParams map[string]string
|
||||||
Payload string
|
Payload string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginOutputData struct {
|
type PluginOutputData struct {
|
||||||
payload string
|
payload string
|
||||||
httpStatusCode int
|
httpStatusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ func (p *PluginOutputData) HttpStatusCode() int {
|
|||||||
return p.httpStatusCode
|
return p.httpStatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
func execPluginV1(c *gin.Context, pluginConfig utils.PluginConfig) {
|
||||||
jsonData, err := io.ReadAll(c.Request.Body)
|
jsonData, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
|
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
|
||||||
@ -50,13 +53,13 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pluginInputData := &PluginInputData{
|
pluginInputData := &PluginInputData{
|
||||||
Params: make(map[string]string),
|
Params: make(map[string]string),
|
||||||
QueryParams: make(map[string]string),
|
QueryParams: make(map[string]string),
|
||||||
Payload: string(jsonData),
|
Payload: string(jsonData),
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginOutputData := &PluginOutputData{
|
pluginOutputData := &PluginOutputData{
|
||||||
payload: "",
|
payload: "",
|
||||||
httpStatusCode: 200,
|
httpStatusCode: 200,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +81,10 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
|||||||
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
|
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
|
||||||
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
||||||
luajson.Preload(l)
|
luajson.Preload(l)
|
||||||
|
gluasql.Preload(l)
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
|
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
|
||||||
|
log.Error("Error executing lua script: ", err)
|
||||||
c.JSON(400, api.Error{Msg: err.Error()})
|
c.JSON(400, api.Error{Msg: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -87,16 +92,138 @@ func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
|||||||
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload())
|
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execPluginV2(c *gin.Context, pluginConfig utils.PluginConfig) {
|
||||||
|
jsonData, err := io.ReadAll(c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
|
||||||
|
log.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginInputData := &PluginInputData{
|
||||||
|
Params: make(map[string]string),
|
||||||
|
QueryParams: make(map[string]string),
|
||||||
|
Payload: string(jsonData),
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginOutputData := &PluginOutputData{
|
||||||
|
payload: "",
|
||||||
|
httpStatusCode: 200,
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(pluginConfig.Endpoint, "/")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, ":") {
|
||||||
|
paramName := strings.TrimPrefix(part, ":")
|
||||||
|
pluginInputData.Params[paramName] = c.Param(paramName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := c.Request.URL.Query()
|
||||||
|
for key, values := range queryParams {
|
||||||
|
pluginInputData.QueryParams[key] = values[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
l := lua.NewState()
|
||||||
|
l.SetGlobal("pluginInputData", luar.New(l, pluginInputData))
|
||||||
|
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
|
||||||
|
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
||||||
|
luajson.Preload(l)
|
||||||
|
gluasql.Preload(l)
|
||||||
|
defer l.Close()
|
||||||
|
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
|
||||||
|
log.Error("Error executing lua script: ", err)
|
||||||
|
c.JSON(400, api.Error{Msg: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get global "exec"
|
||||||
|
lv := l.GetGlobal("exec")
|
||||||
|
|
||||||
|
// Check if it exists and is a function
|
||||||
|
if fn, ok := lv.(*lua.LFunction); ok {
|
||||||
|
err := l.CallByParam(lua.P{
|
||||||
|
Fn: fn,
|
||||||
|
NRet: 1, // exec function returns one value
|
||||||
|
Protect: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Couldn't execute plugin: ", err.Error())
|
||||||
|
c.JSON(400, "Couldn't execute plugin: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := l.Get(-1)
|
||||||
|
l.Pop(1)
|
||||||
|
|
||||||
|
if ret != lua.LNil {
|
||||||
|
log.Error("Couldn't execute plugin")
|
||||||
|
c.JSON(400, "Couldn't execute plugin")
|
||||||
|
}
|
||||||
|
c.Data(
|
||||||
|
pluginOutputData.HttpStatusCode(),
|
||||||
|
"application/json",
|
||||||
|
[]byte(pluginOutputData.Payload()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Error("Couldn't execute plugin. No exec function implemented!")
|
||||||
|
c.JSON(400, "Couldn't execute plugin. No exec function implemented!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type plugHandler struct {
|
type plugHandler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc {
|
func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc {
|
||||||
fn := func(c *gin.Context) {
|
fn := func(c *gin.Context) {
|
||||||
execPlugin(c, pluginConfig)
|
if pluginConfig.Version == 1 {
|
||||||
|
execPluginV1(c, pluginConfig)
|
||||||
|
} else {
|
||||||
|
execPluginV2(c, pluginConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gin.HandlerFunc(fn)
|
return gin.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
//exported
|
func (p plugHandler) InitPlugin(pluginConfig utils.PluginConfig) error {
|
||||||
|
l := lua.NewState()
|
||||||
|
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
||||||
|
luajson.Preload(l)
|
||||||
|
gluasql.Preload(l)
|
||||||
|
defer l.Close()
|
||||||
|
err := l.DoFile(pluginConfig.ScriptPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error executing lua script: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get global "init"
|
||||||
|
lv := l.GetGlobal("init")
|
||||||
|
|
||||||
|
// Check if it exists and is a function
|
||||||
|
if fn, ok := lv.(*lua.LFunction); ok {
|
||||||
|
err := l.CallByParam(lua.P{
|
||||||
|
Fn: fn,
|
||||||
|
NRet: 2, // init function returns two values
|
||||||
|
Protect: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = l.Get(-2)
|
||||||
|
errVal := l.Get(-1)
|
||||||
|
l.Pop(2)
|
||||||
|
|
||||||
|
if errVal != lua.LNil {
|
||||||
|
return errors.New("Couldn't initialize lua script: " + errVal.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// exported
|
||||||
var PluginHandler plugHandler
|
var PluginHandler plugHandler
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginConfig struct {
|
type PluginConfig struct {
|
||||||
Endpoint string `yaml:"endpoint"`
|
Endpoint string `yaml:"endpoint"`
|
||||||
Method string `yaml:"method"`
|
Method string `yaml:"method"`
|
||||||
|
Version int `yaml:"version,omitempty"`
|
||||||
ScriptPath string
|
ScriptPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +42,7 @@ func (c *PluginConfigs) Load(baseDirectory string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pluginConfig PluginConfig
|
var pluginConfig PluginConfig
|
||||||
|
pluginConfig.Version = 1
|
||||||
err = yaml.Unmarshal(data, &pluginConfig)
|
err = yaml.Unmarshal(data, &pluginConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -6,4 +6,5 @@ import (
|
|||||||
|
|
||||||
type PluginHandler interface {
|
type PluginHandler interface {
|
||||||
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
|
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
|
||||||
|
InitPlugin(pluginConfig PluginConfig) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const (
|
|||||||
MonoSpaceBegin = 6
|
MonoSpaceBegin = 6
|
||||||
StrikethroughBegin = 8
|
StrikethroughBegin = 8
|
||||||
SpoilerBegin = 9
|
SpoilerBegin = 9
|
||||||
|
BoldItalicBegin = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
const EscapeCharacter rune = '\\'
|
const EscapeCharacter rune = '\\'
|
||||||
@ -119,7 +120,14 @@ func (l *TextstyleParser) handleToken(tokenType int, signalCliStylingType string
|
|||||||
} else {
|
} else {
|
||||||
if l.tokens.Peek().Token == tokenType {
|
if l.tokens.Peek().Token == tokenType {
|
||||||
tokenBeginState := l.tokens.Pop()
|
tokenBeginState := l.tokens.Pop()
|
||||||
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(getUtf16StringLength(l.fullString)-tokenBeginState.BeginPos)+":"+signalCliStylingType)
|
length := getUtf16StringLength(l.fullString) - tokenBeginState.BeginPos
|
||||||
|
|
||||||
|
if tokenType == BoldItalicBegin {
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Bold)
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Italic)
|
||||||
|
} else {
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+signalCliStylingType)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType})
|
l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType})
|
||||||
}
|
}
|
||||||
@ -137,14 +145,27 @@ func (l *TextstyleParser) Parse() (string, []string) {
|
|||||||
nextRune := l.peek()
|
nextRune := l.peek()
|
||||||
|
|
||||||
if c == '*' {
|
if c == '*' {
|
||||||
if nextRune == '*' { //Bold
|
if nextRune == '*' {
|
||||||
l.next()
|
l.next()
|
||||||
if prevChar == EscapeCharacter {
|
|
||||||
prevChar = c
|
if l.peek() == '*' { // Check for *** before treating it as **
|
||||||
l.fullString += "**"
|
l.next()
|
||||||
continue
|
|
||||||
|
if prevChar == EscapeCharacter {
|
||||||
|
prevChar = c
|
||||||
|
l.fullString += "***"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
l.handleToken(BoldItalicBegin, "BOLD_ITALIC")
|
||||||
|
} else {
|
||||||
|
if prevChar == EscapeCharacter {
|
||||||
|
prevChar = c
|
||||||
|
l.fullString += "**"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l.handleToken(BoldBegin, Bold)
|
||||||
}
|
}
|
||||||
l.handleToken(BoldBegin, Bold)
|
|
||||||
} else { //Italic
|
} else { //Italic
|
||||||
if prevChar == EscapeCharacter {
|
if prevChar == EscapeCharacter {
|
||||||
prevChar = c
|
prevChar = c
|
||||||
|
|||||||
@ -155,5 +155,11 @@ func TestEscapeNew(t *testing.T) {
|
|||||||
message, signalCliFormatStrings := textstyleParser.Parse()
|
message, signalCliFormatStrings := textstyleParser.Parse()
|
||||||
expectMessageEqual(t, message, "Test ** * ~ Escape")
|
expectMessageEqual(t, message, "Test ** * ~ Escape")
|
||||||
expectFormatStringsEqual(t, signalCliFormatStrings, []string{})
|
expectFormatStringsEqual(t, signalCliFormatStrings, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoldItalic(t *testing.T) {
|
||||||
|
textstyleParser := NewTextstyleParser("***Bold Italic Text***")
|
||||||
|
message, signalCliFormatStrings := textstyleParser.Parse()
|
||||||
|
expectMessageEqual(t, message, "Bold Italic Text")
|
||||||
|
expectFormatStringsEqual(t, signalCliFormatStrings, []string{"0:16:BOLD", "0:16:ITALIC"})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user