mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-05-18 13:24:15 +00:00
Compare commits
No commits in common. "cbefcb2ecdb3acc4722ba5b8f01485783e1647be" and "1129e5a7bec43c90dc0631b8090a7ea590bcf082" have entirely different histories.
cbefcb2ecd
...
1129e5a7be
88
Dockerfile
88
Dockerfile
@ -1,15 +1,14 @@
|
||||
ARG SIGNAL_CLI_VERSION=0.14.0
|
||||
ARG LIBSIGNAL_CLIENT_VERSION=0.87.4
|
||||
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.14.0+morph027+5
|
||||
ARG SIGNAL_CLI_VERSION=0.13.24
|
||||
ARG LIBSIGNAL_CLIENT_VERSION=0.87.0
|
||||
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.13.24+morph027+2
|
||||
|
||||
ARG SWAG_VERSION=1.16.4
|
||||
ARG GRAALVM_VERSION=25.0.2
|
||||
|
||||
ARG S6_OVERLAY_VERSION=v3.2.2.0
|
||||
ARG GRAALVM_VERSION=21.0.0
|
||||
#ARG GRAALVM_VERSION=25.0.2
|
||||
|
||||
ARG BUILD_VERSION_ARG=unset
|
||||
|
||||
FROM golang:1.26-trixie AS buildcontainer
|
||||
FROM golang:1.24-bookworm AS buildcontainer
|
||||
|
||||
ARG SIGNAL_CLI_VERSION
|
||||
ARG LIBSIGNAL_CLIENT_VERSION
|
||||
@ -33,8 +32,8 @@ RUN arch="$(uname -m)"; \
|
||||
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 \
|
||||
wget software-properties-common git locales zip unzip \
|
||||
file build-essential libz-dev zlib1g-dev \
|
||||
&& 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 && \
|
||||
@ -43,7 +42,7 @@ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
|
||||
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} \
|
||||
@ -92,8 +91,8 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \
|
||||
&& ./gradlew -q nativeCompile; \
|
||||
elif [ "$(uname -m)" = "aarch64" ] ; then \
|
||||
echo "Use native image from @morph027 (https://packaging.gitlab.io/signal-cli/) for arm64 - many thanks to @morph027" \
|
||||
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/signal-cli-native.pgp] https://packaging.gitlab.io/signal-cli signalcli main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
|
||||
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | apt-key add - \
|
||||
&& echo "deb https://packaging.gitlab.io/signal-cli focal main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
|
||||
&& mkdir -p /tmp/signal-cli-native \
|
||||
&& cd /tmp/signal-cli-native \
|
||||
#&& wget https://gitlab.com/packaging/signal-cli/-/jobs/3716873649/artifacts/download?file_type=archive -O /tmp/signal-cli-native/archive.zip \
|
||||
@ -155,63 +154,42 @@ RUN cd /tmp/signal-cli-rest-api-src/scripts && go build -o jsonrpc2-helper
|
||||
RUN cd /tmp/signal-cli-rest-api-src && go build -buildmode=plugin -o signal-cli-rest-api_plugin_loader.so plugin_loader.go
|
||||
|
||||
# Start a fresh container for release container
|
||||
FROM debian:trixie-slim
|
||||
|
||||
ARG TARGETARCH # set by buildx
|
||||
ARG SIGNAL_CLI_VERSION
|
||||
ARG BUILD_VERSION_ARG
|
||||
ARG S6_OVERLAY_VERSION
|
||||
# eclipse-temurin doesn't provide a OpenJDK 21 image for armv7 (see https://github.com/adoptium/containers/issues/502). Until this
|
||||
# is fixed we use the standard ubuntu image
|
||||
#FROM eclipse-temurin:21-jre-jammy
|
||||
|
||||
FROM ubuntu:jammy
|
||||
|
||||
ENV GIN_MODE=release
|
||||
|
||||
# Set environment variables to keep the image clean
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV PORT=8080
|
||||
|
||||
ARG SIGNAL_CLI_VERSION
|
||||
ARG BUILD_VERSION_ARG
|
||||
|
||||
ENV BUILD_VERSION=$BUILD_VERSION_ARG
|
||||
ENV SIGNAL_CLI_REST_API_PLUGIN_SHARED_OBJ_DIR=/usr/bin/
|
||||
|
||||
RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends util-linux openjdk-25-jre curl locales xz-utils \
|
||||
&& apt-get clean \
|
||||
&& apt-get install -y --no-install-recommends util-linux supervisor netcat openjdk-21-jre curl locales \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN if [ -z "$TARGETARCH" ]; then \
|
||||
# Fallback for older Docker versions not using BuildKit
|
||||
TARGETARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); \
|
||||
else \
|
||||
echo "Building for architecture: $TARGETARCH"; \
|
||||
fi;
|
||||
|
||||
# install s6-overlay as service control system
|
||||
RUN curl -fL -o /tmp/s6-overlay-noarch.tar.xz \
|
||||
"https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
|
||||
if [ "$TARGETARCH" = "amd64" ]; then S6_ARCH="x86_64"; \
|
||||
elif [ "$TARGETARCH" = "arm64" ]; then S6_ARCH="aarch64"; \
|
||||
elif [ "$TARGETARCH" = "arm" ]; then S6_ARCH="arm"; \
|
||||
else S6_ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); fi;\
|
||||
curl -fL -o /tmp/s6-overlay-bin.tar.xz \
|
||||
"https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-bin.tar.xz && \
|
||||
rm /tmp/s6-overlay-*.tar.xz
|
||||
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api
|
||||
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
|
||||
COPY --from=buildcontainer /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
RUN groupadd -g 1000 signal-api \
|
||||
&& useradd --no-log-init -M -d /home -s /bin/bash -u 1000 -g 1000 signal-api \
|
||||
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \
|
||||
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native \
|
||||
&& mkdir -p /home/.local/share/signal-cli \
|
||||
&& chown -R signal-api:signal-api /home
|
||||
|
||||
COPY --chmod=755 ./s6-services/ /etc/s6-overlay/s6-rc.d/
|
||||
&& mkdir -p /signal-cli-config/ \
|
||||
&& mkdir -p /home/.local/share/signal-cli
|
||||
|
||||
# remove the temporary created signal-cli-native on armv7, as GRAALVM doesn't support 32bit
|
||||
RUN arch="$(uname -m)"; \
|
||||
@ -223,24 +201,16 @@ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||
update-locale LANG=en_US.UTF-8
|
||||
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
EXPOSE ${PORT}
|
||||
|
||||
ENV SIGNAL_CLI_CONFIG_DIR=/home/.local/share/signal-cli
|
||||
ENV SIGNAL_CLI_UID=1000
|
||||
ENV SIGNAL_CLI_GID=1000
|
||||
ENV SIGNAL_CLI_CHOWN_ON_STARTUP=true
|
||||
|
||||
RUN mkdir -p /tmp/s6-runtime && chown -R signal-api:signal-api /tmp/s6-runtime /etc/s6-overlay
|
||||
|
||||
USER signal-api
|
||||
|
||||
# Mandatory ENV for non-root s6
|
||||
ENV S6_RUNTIME_PATH=/tmp/s6-runtime
|
||||
ENV S6_READ_ONLY_ROOT=1
|
||||
ENV S6_VERBOSITY=2
|
||||
|
||||
WORKDIR /home
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${PORT}/v1/health || exit 1
|
||||
|
||||
@ -156,6 +156,4 @@ There are a bunch of environmental variables that can be set inside the docker c
|
||||
|
||||
* `JSON_RPC_IGNORE_ATTACHMENTS`: When set to `true`, attachments are not automatically downloaded in json-rpc mode (default: `false`)
|
||||
* `JSON_RPC_IGNORE_STORIES`: When set to `true`, stories are not automatically downloaded in json-rpc mode (default: `false`)
|
||||
* `JSON_RPC_IGNORE_AVATARS`: When set to `true`, avatars are not automatically downloaded in json-rpc mode (default: `false`)
|
||||
* `JSON_RPC_IGNORE_STICKERS`: When set to `true`, sticker packs are not automatically downloaded in json-rpc mode (default: `false`)
|
||||
* `JSON_RPC_TRUST_NEW_IDENTITIES`: Choose how to trust new identities in json-rpc mode. Supported values: `on-first-use`, `always`, `never`. (default: `on-first-use`)
|
||||
|
||||
@ -4,8 +4,6 @@ This can be done by putting the docker container into debug mode with the follow
|
||||
|
||||
```curl -X POST -H "Content-Type: application/json" -d '{"logging": {"level": "debug"}}' 'http://127.0.0.1:8080/v1/configuration'```
|
||||
|
||||
Alternatively, you can set the `LOG_LEVEL` environment variable.
|
||||
|
||||
Once the docker container is in debug mode, execute the REST API command you want to debug.
|
||||
|
||||
e.g Let's assume we are experiencing some problems with sending messages. So, let's send a Signal message with
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
version: "3"
|
||||
services:
|
||||
signal-cli-rest-api:
|
||||
image: bbernhard/signal-cli-rest-api:latest
|
||||
@ -8,4 +9,3 @@ services:
|
||||
- "8080:8080" #map docker port 8080 to host port 8080.
|
||||
volumes:
|
||||
- "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
[ -z "${SIGNAL_CLI_CONFIG_DIR}" ] && echo "SIGNAL_CLI_CONFIG_DIR environmental variable needs to be set! Aborting!" && exit 1;
|
||||
|
||||
usermod -u ${SIGNAL_CLI_UID} signal-api
|
||||
groupmod -o -g ${SIGNAL_CLI_GID} signal-api
|
||||
|
||||
# Fix permissions to ensure backward compatibility if SIGNAL_CLI_CHOWN_ON_STARTUP is not set to "false"
|
||||
if [ "$SIGNAL_CLI_CHOWN_ON_STARTUP" != "false" ]; then
|
||||
echo "Changing ownership of ${SIGNAL_CLI_CONFIG_DIR} to ${SIGNAL_CLI_UID}:${SIGNAL_CLI_GID}"
|
||||
chown ${SIGNAL_CLI_UID}:${SIGNAL_CLI_GID} -R ${SIGNAL_CLI_CONFIG_DIR}
|
||||
else
|
||||
echo "Skipping chown on startup since SIGNAL_CLI_CHOWN_ON_STARTUP is set to 'false'"
|
||||
fi
|
||||
|
||||
# Show warning on docker exec
|
||||
cat <<EOF >> /root/.bashrc
|
||||
echo "WARNING: signal-cli-rest-api runs as signal-api (not as root!)"
|
||||
echo "Run 'su signal-api' before using signal-cli!"
|
||||
echo "If you want to use signal-cli directly, don't forget to specify the config directory. e.g: \"signal-cli --config ${SIGNAL_CLI_CONFIG_DIR}\""
|
||||
EOF
|
||||
|
||||
cap_prefix="-cap_"
|
||||
caps="$cap_prefix$(seq -s ",$cap_prefix" 0 $(cat /proc/sys/kernel/cap_last_cap))"
|
||||
|
||||
# TODO: check mode
|
||||
if [ "$MODE" = "json-rpc" ]
|
||||
then
|
||||
/usr/bin/jsonrpc2-helper
|
||||
if [ -n "$JAVA_OPTS" ] ; then
|
||||
echo "export JAVA_OPTS='$JAVA_OPTS'" >> /etc/default/supervisor
|
||||
fi
|
||||
service supervisor start
|
||||
supervisorctl start all
|
||||
fi
|
||||
|
||||
export HOST_IP=$(hostname -I | awk '{print $1}')
|
||||
|
||||
# Start API as signal-api user
|
||||
exec setpriv --reuid=${SIGNAL_CLI_UID} --regid=${SIGNAL_CLI_GID} --init-groups --inh-caps=$caps signal-cli-rest-api -signal-cli-config=${SIGNAL_CLI_CONFIG_DIR}
|
||||
@ -1,22 +1,23 @@
|
||||
diff --git a/build.gradle.kts b/build.gradle.kts
|
||||
index d8e36ea4..dba4dae3 100644
|
||||
index f51d9f1c..6357f590 100644
|
||||
--- a/build.gradle.kts
|
||||
+++ b/build.gradle.kts
|
||||
@@ -91,6 +91,7 @@ dependencies {
|
||||
@@ -55,6 +55,7 @@ dependencies {
|
||||
implementation(libs.slf4j.jul)
|
||||
implementation(libs.logback)
|
||||
implementation(libs.zxing)
|
||||
implementation(project(":libsignal-cli"))
|
||||
+ implementation(files("/tmp/libsignal-client.jar"))
|
||||
+ implementation(files("/tmp/libsignal-client.jar"))
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -99,6 +100,9 @@ configurations {
|
||||
@@ -63,6 +64,10 @@ configurations {
|
||||
}
|
||||
}
|
||||
|
||||
+configurations.all {
|
||||
+ exclude(group = "org.signal", module = "libsignal-client")
|
||||
+ exclude(group = "org.signal", module = "libsignal-client")
|
||||
+}
|
||||
+
|
||||
|
||||
tasks.withType<AbstractArchiveTask>().configureEach {
|
||||
isPreserveFileTimestamps = false
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
#!/command/with-contenv sh
|
||||
# Use with-contenv to import environment variables like SIGNAL_CLI_CONFIG_DIR
|
||||
exec signal-cli-rest-api -signal-cli-config="${SIGNAL_CLI_CONFIG_DIR}"
|
||||
@ -1 +0,0 @@
|
||||
longrun
|
||||
@ -1,10 +0,0 @@
|
||||
#!/command/with-contenv sh
|
||||
# File: /etc/s6-overlay/s6-rc.d/signal-json-rpc/run
|
||||
|
||||
if [ "$MODE" != "json-rpc" ]; then
|
||||
echo "Running as mode: $MODE - skipping json-rpc setup"
|
||||
sleep infinity # do nothing, but keep service running
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec jsonrpc2-helper
|
||||
@ -1 +0,0 @@
|
||||
longrun
|
||||
@ -1 +0,0 @@
|
||||
bundle
|
||||
@ -215,7 +215,7 @@ type RemoteDeleteRequest struct {
|
||||
}
|
||||
|
||||
type DeleteLocalAccountDataRequest struct {
|
||||
IgnoreRegistered bool `json:"ignore_registered" example:"false"`
|
||||
IgnoreRegistered bool `json:"ignore_registered" example:"false"`
|
||||
}
|
||||
|
||||
type DeviceLinkUriResponse struct {
|
||||
@ -364,30 +364,30 @@ func (a *Api) UnregisterNumber(c *gin.Context) {
|
||||
// @Failure 400 {object} Error
|
||||
// @Router /v1/devices/{number}/local-data [delete]
|
||||
func (a *Api) DeleteLocalAccountData(c *gin.Context) {
|
||||
number, err := url.PathUnescape(c.Param("number"))
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - malformed number"})
|
||||
return
|
||||
}
|
||||
if number == "" {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - number missing"})
|
||||
return
|
||||
}
|
||||
number, err := url.PathUnescape(c.Param("number"))
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - malformed number"})
|
||||
return
|
||||
}
|
||||
if number == "" {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - number missing"})
|
||||
return
|
||||
}
|
||||
|
||||
req := DeleteLocalAccountDataRequest{}
|
||||
if c.Request.Body != nil && c.Request.ContentLength != 0 {
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
|
||||
return
|
||||
}
|
||||
}
|
||||
req := DeleteLocalAccountDataRequest{}
|
||||
if c.Request.Body != nil && c.Request.ContentLength != 0 {
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - invalid request"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.signalClient.DeleteLocalAccountData(number, req.IgnoreRegistered); err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
if err := a.signalClient.DeleteLocalAccountData(number, req.IgnoreRegistered); err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary Verify a registered phone number.
|
||||
@ -670,8 +670,6 @@ func StringToBool(input string) bool {
|
||||
// @Param timeout query string false "Receive timeout in seconds (default: 1)"
|
||||
// @Param ignore_attachments query string false "Specify whether the attachments of the received message should be ignored" (default: false)"
|
||||
// @Param ignore_stories query string false "Specify whether stories should be ignored when receiving messages" (default: false)"
|
||||
// @Param ignore_avatars query string false "Specify whether avatar downloads should be ignored when receiving messages" (default: false)"
|
||||
// @Param ignore_stickers query string false "Specify whether sticker pack downloads should be ignored when receiving messages" (default: false)"
|
||||
// @Param max_messages query string false "Specify the maximum number of messages to receive (default: unlimited)". Not available in json-rpc mode.
|
||||
// @Param send_read_receipts query string false "Specify whether read receipts should be sent when receiving messages" (default: false)"
|
||||
// @Router /v1/receive/{number} [get]
|
||||
@ -720,25 +718,13 @@ func (a *Api) Receive(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ignoreAvatars := c.DefaultQuery("ignore_avatars", "false")
|
||||
if ignoreAvatars != "true" && ignoreAvatars != "false" {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - ignore_avatars parameter needs to be either 'true' or 'false'"})
|
||||
return
|
||||
}
|
||||
|
||||
ignoreStickers := c.DefaultQuery("ignore_stickers", "false")
|
||||
if ignoreStickers != "true" && ignoreStickers != "false" {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - ignore_stickers parameter needs to be either 'true' or 'false'"})
|
||||
return
|
||||
}
|
||||
|
||||
sendReadReceipts := c.DefaultQuery("send_read_receipts", "false")
|
||||
if sendReadReceipts != "true" && sendReadReceipts != "false" {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - send_read_receipts parameter needs to be either 'true' or 'false'"})
|
||||
return
|
||||
}
|
||||
|
||||
jsonStr, err := a.signalClient.Receive(number, timeoutInt, StringToBool(ignoreAttachments), StringToBool(ignoreStories), StringToBool(ignoreAvatars), StringToBool(ignoreStickers), maxMessagesInt, StringToBool(sendReadReceipts))
|
||||
jsonStr, err := a.signalClient.Receive(number, timeoutInt, StringToBool(ignoreAttachments), StringToBool(ignoreStories), maxMessagesInt, StringToBool(sendReadReceipts))
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
@ -1188,19 +1174,19 @@ func (a *Api) GetQrCodeLink(c *gin.Context) {
|
||||
// @Failure 400 {object} Error
|
||||
// @Router /v1/qrcodelink/raw [get]
|
||||
func (a *Api) GetQrCodeLinkUri(c *gin.Context) {
|
||||
deviceName := c.Query("device_name")
|
||||
if deviceName == "" {
|
||||
c.JSON(400, Error{Msg: "Please provide a name for the device"})
|
||||
return
|
||||
}
|
||||
deviceName := c.Query("device_name")
|
||||
if deviceName == "" {
|
||||
c.JSON(400, Error{Msg: "Please provide a name for the device"})
|
||||
return
|
||||
}
|
||||
|
||||
deviceLinkUri, err := a.signalClient.GetDeviceLinkUri(deviceName)
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
deviceLinkUri, err := a.signalClient.GetDeviceLinkUri(deviceName)
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, DeviceLinkUriResponse{DeviceLinkUri: deviceLinkUri})
|
||||
c.JSON(200, DeviceLinkUriResponse{DeviceLinkUri: deviceLinkUri})
|
||||
}
|
||||
|
||||
// @Summary List all accounts
|
||||
@ -2094,7 +2080,7 @@ func (a *Api) RemoveDevice(c *gin.Context) {
|
||||
}
|
||||
|
||||
deviceIdStr := c.Param("deviceId")
|
||||
deviceId, err := strconv.ParseInt(deviceIdStr, 10, 64)
|
||||
deviceId, err := strconv.ParseInt(deviceIdStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: "deviceId must be numeric"})
|
||||
return
|
||||
|
||||
@ -410,7 +410,7 @@ func (s *SignalClient) GetSignalCliMode() SignalCliMode {
|
||||
return s.signalCliMode
|
||||
}
|
||||
|
||||
func (s *SignalClient) Init(maxRetries int) error {
|
||||
func (s *SignalClient) Init() error {
|
||||
s.signalCliApiConfig = utils.NewSignalCliApiConfig()
|
||||
err := s.signalCliApiConfig.Load(s.signalCliApiConfigPath)
|
||||
if err != nil {
|
||||
@ -427,7 +427,7 @@ func (s *SignalClient) Init(maxRetries int) error {
|
||||
tcpPortsNumberMapping := s.jsonRpc2ClientConfig.GetTcpPortsForNumbers()
|
||||
for number, tcpPort := range tcpPortsNumberMapping {
|
||||
s.jsonRpc2Clients[number] = NewJsonRpc2Client(s.signalCliApiConfig, number)
|
||||
err := s.jsonRpc2Clients[number].Dial("127.0.0.1:"+strconv.FormatInt(tcpPort, 10), maxRetries)
|
||||
err := s.jsonRpc2Clients[number].Dial("127.0.0.1:" + strconv.FormatInt(tcpPort, 10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -993,7 +993,7 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
||||
return ×tamps, 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, maxMessages int64, sendReadReceipts bool) (string, error) {
|
||||
if s.signalCliMode == JsonRpc {
|
||||
return "", errors.New("Not implemented")
|
||||
} else {
|
||||
@ -1007,14 +1007,6 @@ func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments b
|
||||
command = append(command, "--ignore-stories")
|
||||
}
|
||||
|
||||
if ignoreAvatars {
|
||||
command = append(command, "--ignore-avatars")
|
||||
}
|
||||
|
||||
if ignoreStickers {
|
||||
command = append(command, "--ignore-stickers")
|
||||
}
|
||||
|
||||
if maxMessages > 0 {
|
||||
command = append(command, "--max-messages")
|
||||
command = append(command, strconv.FormatInt(maxMessages, 10))
|
||||
@ -1357,7 +1349,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
|
||||
}
|
||||
pendingMembers = append(pendingMembers, identifier)
|
||||
}
|
||||
groupEntry.PendingInvites = pendingMembers
|
||||
groupEntry.PendingRequests = pendingMembers
|
||||
|
||||
requestingMembers := []string{}
|
||||
for _, val := range signalCliGroupEntry.RequestingMembers {
|
||||
@ -1367,7 +1359,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
|
||||
}
|
||||
requestingMembers = append(requestingMembers, identifier)
|
||||
}
|
||||
groupEntry.PendingRequests = requestingMembers
|
||||
groupEntry.PendingInvites = requestingMembers
|
||||
|
||||
admins := []string{}
|
||||
for _, val := range signalCliGroupEntry.Admins {
|
||||
@ -2642,8 +2634,8 @@ func (s *SignalClient) ListContacts(number string, allRecipients bool, recipient
|
||||
|
||||
if s.signalCliMode == JsonRpc {
|
||||
type Request struct {
|
||||
AllRecipients bool `json:"allRecipients,omitempty"`
|
||||
Recipient string `json:"recipient,omitempty"`
|
||||
AllRecipients bool `json:"allRecipients,omitempty"`
|
||||
Recipient string `json:"recipient,omitempty"`
|
||||
}
|
||||
req := Request{}
|
||||
if allRecipients {
|
||||
@ -2713,6 +2705,7 @@ func (s *SignalClient) ListContacts(number string, allRecipients bool, recipient
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
func (s *SignalClient) SetPin(number string, registrationLockPin string) error {
|
||||
if s.signalCliMode == JsonRpc {
|
||||
type Request struct {
|
||||
|
||||
@ -2,14 +2,14 @@ package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"net/http"
|
||||
"bytes"
|
||||
"strconv"
|
||||
|
||||
"github.com/bbernhard/signal-cli-rest-api/utils"
|
||||
uuid "github.com/gofrs/uuid"
|
||||
@ -60,11 +60,11 @@ type JsonRpc2Client struct {
|
||||
conn net.Conn
|
||||
receivedResponsesById map[string]chan JsonRpc2MessageResponse
|
||||
receivedMessagesChannels map[string]chan JsonRpc2ReceivedMessage
|
||||
lastTimeErrorMessageSent time.Time
|
||||
signalCliApiConfig *utils.SignalCliApiConfig
|
||||
number string
|
||||
receivedMessagesMutex sync.Mutex
|
||||
receivedResponsesMutex sync.Mutex
|
||||
address string
|
||||
}
|
||||
|
||||
func NewJsonRpc2Client(signalCliApiConfig *utils.SignalCliApiConfig, number string) *JsonRpc2Client {
|
||||
@ -76,24 +76,10 @@ func NewJsonRpc2Client(signalCliApiConfig *utils.SignalCliApiConfig, number stri
|
||||
}
|
||||
}
|
||||
|
||||
func (r *JsonRpc2Client) Dial(address string, maxRetries int) error {
|
||||
func (r *JsonRpc2Client) Dial(address string) error {
|
||||
var err error
|
||||
r.address = address
|
||||
connected := false
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
r.conn, err = net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
log.Info("Waiting for signal-cli to start up in daemon mode...")
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
connected = true
|
||||
log.Info("Successfully connected to signal-cli in daemon mode")
|
||||
break
|
||||
}
|
||||
|
||||
if !connected {
|
||||
r.conn, err = net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -221,14 +207,11 @@ func (r *JsonRpc2Client) ReceiveData(number string, receiveWebhookUrl string) {
|
||||
for {
|
||||
str, err := connbuf.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Error("Lost connection to signal-cli...attempting to reconnect (", err.Error(), ")")
|
||||
r.conn.Close()
|
||||
err = r.Dial(r.address, 15)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to reconnect to signal-cli: ", err.Error(), "...aborting")
|
||||
elapsed := time.Since(r.lastTimeErrorMessageSent)
|
||||
if (elapsed) > time.Duration(5*time.Minute) { //avoid spamming the log file and only log the message at max every 5 minutes
|
||||
log.Error("Couldn't read data for number ", number, ": ", err.Error(), ". Is the number properly registered?")
|
||||
r.lastTimeErrorMessageSent = time.Now()
|
||||
}
|
||||
connbuf = bufio.NewReader(r.conn)
|
||||
log.Info("Successfully reconnected to signal-cli")
|
||||
continue
|
||||
}
|
||||
log.Debug("json-rpc received data: ", str)
|
||||
@ -265,7 +248,7 @@ func (r *JsonRpc2Client) ReceiveData(number string, receiveWebhookUrl string) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warn("Received unparsable message: ", str)
|
||||
log.Error("Received unparsable message: ", str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2043,18 +2043,6 @@ const docTemplate = `{
|
||||
"name": "ignore_stories",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify whether avatar downloads should be ignored when receiving messages",
|
||||
"name": "ignore_avatars",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify whether sticker pack downloads should be ignored when receiving messages",
|
||||
"name": "ignore_stickers",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify the maximum number of messages to receive (default: unlimited)",
|
||||
|
||||
@ -2040,18 +2040,6 @@
|
||||
"name": "ignore_stories",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify whether avatar downloads should be ignored when receiving messages",
|
||||
"name": "ignore_avatars",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify whether sticker pack downloads should be ignored when receiving messages",
|
||||
"name": "ignore_stickers",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Specify the maximum number of messages to receive (default: unlimited)",
|
||||
|
||||
@ -1916,16 +1916,6 @@ paths:
|
||||
in: query
|
||||
name: ignore_stories
|
||||
type: string
|
||||
- description: Specify whether avatar downloads should be ignored when receiving
|
||||
messages
|
||||
in: query
|
||||
name: ignore_avatars
|
||||
type: string
|
||||
- description: Specify whether sticker pack downloads should be ignored when
|
||||
receiving messages
|
||||
in: query
|
||||
name: ignore_stickers
|
||||
type: string
|
||||
- description: 'Specify the maximum number of messages to receive (default:
|
||||
unlimited)'
|
||||
in: query
|
||||
|
||||
@ -163,7 +163,7 @@ func main() {
|
||||
jsonRpc2ClientConfigPathPath := *signalCliConfig + "/jsonrpc2.yml"
|
||||
signalCliApiConfigPath := *signalCliConfig + "/api-config.yml"
|
||||
signalClient := client.NewSignalClient(*signalCliConfig, *attachmentTmpDir, *avatarTmpDir, signalCliMode, jsonRpc2ClientConfigPathPath, signalCliApiConfigPath, webhookUrl)
|
||||
err = signalClient.Init(15)
|
||||
err = signalClient.Init()
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't init Signal Client: ", err.Error())
|
||||
}
|
||||
@ -395,8 +395,6 @@ func main() {
|
||||
autoReceiveScheduleReceiveTimeout := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_RECEIVE_TIMEOUT", "10")
|
||||
autoReceiveScheduleIgnoreAttachments := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_IGNORE_ATTACHMENTS", "false")
|
||||
autoReceiveScheduleIgnoreStories := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_IGNORE_STORIES", "false")
|
||||
autoReceiveScheduleIgnoreAvatars := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_IGNORE_AVATARS", "false")
|
||||
autoReceiveScheduleIgnoreStickers := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_IGNORE_STICKERS", "false")
|
||||
autoReceiveScheduleSendReadReceipts := utils.GetEnv("AUTO_RECEIVE_SCHEDULE_SEND_READ_RECEIPTS", "false")
|
||||
|
||||
c := cron.New()
|
||||
@ -426,8 +424,6 @@ func main() {
|
||||
q.Add("timeout", autoReceiveScheduleReceiveTimeout)
|
||||
q.Add("ignore_attachments", autoReceiveScheduleIgnoreAttachments)
|
||||
q.Add("ignore_stories", autoReceiveScheduleIgnoreStories)
|
||||
q.Add("ignore_avatars", autoReceiveScheduleIgnoreAvatars)
|
||||
q.Add("ignore_stickers", autoReceiveScheduleIgnoreStickers)
|
||||
q.Add("send_read_receipts", autoReceiveScheduleSendReadReceipts)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
|
||||
@ -1,15 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/bbernhard/signal-cli-rest-api/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const supervisorctlConfigTemplate = `
|
||||
[program:%s]
|
||||
process_name=%s
|
||||
command=bash -c "nc -l -p %d <%s | signal-cli --output=json --config %s%s jsonRpc%s%s >%s"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=10
|
||||
user=signal-api
|
||||
directory=/usr/bin/
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/%s/out.log
|
||||
stderr_logfile=/var/log/%s/err.log
|
||||
stdout_logfile_maxbytes=50MB
|
||||
stdout_logfile_backups=10
|
||||
numprocs=1
|
||||
`
|
||||
|
||||
func main() {
|
||||
signalCliConfigDir := "/home/.local/share/signal-cli/"
|
||||
signalCliConfigDirEnv := utils.GetEnv("SIGNAL_CLI_CONFIG_DIR", "")
|
||||
@ -23,57 +41,72 @@ func main() {
|
||||
jsonRpc2ClientConfig := utils.NewJsonRpc2ClientConfig()
|
||||
|
||||
var tcpPort int64 = 6001
|
||||
jsonRpc2ClientConfig.AddEntry(utils.MULTI_ACCOUNT_NUMBER, utils.JsonRpc2ClientConfigEntry{TcpPort: tcpPort})
|
||||
fifoPathname := "/tmp/sigsocket1"
|
||||
|
||||
args := []string{"--output=json", "--config", signalCliConfigDir}
|
||||
jsonRpc2ClientConfig.AddEntry(utils.MULTI_ACCOUNT_NUMBER, utils.JsonRpc2ClientConfigEntry{TcpPort: tcpPort, FifoPathname: fifoPathname})
|
||||
|
||||
os.Remove(fifoPathname) //remove any existing named pipe
|
||||
|
||||
_, err := exec.Command("mkfifo", fifoPathname).Output()
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create fifo with name ", fifoPathname, ": ", err.Error())
|
||||
}
|
||||
|
||||
uid := utils.GetEnv("SIGNAL_CLI_UID", "1000")
|
||||
gid := utils.GetEnv("SIGNAL_CLI_GID", "1000")
|
||||
_, err = exec.Command("chown", uid+":"+gid, fifoPathname).Output()
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't change permissions of fifo with name ", fifoPathname, ": ", err.Error())
|
||||
}
|
||||
|
||||
signalCliIgnoreAttachments := ""
|
||||
ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "")
|
||||
if ignoreAttachments == "true" {
|
||||
signalCliIgnoreAttachments = " --ignore-attachments"
|
||||
}
|
||||
|
||||
signalCliIgnoreStories := ""
|
||||
ignoreStories := utils.GetEnv("JSON_RPC_IGNORE_STORIES", "")
|
||||
if ignoreStories == "true" {
|
||||
signalCliIgnoreStories = " --ignore-stories"
|
||||
}
|
||||
|
||||
supervisorctlProgramName := "signal-cli-json-rpc-1"
|
||||
supervisorctlLogFolder := "/var/log/" + supervisorctlProgramName
|
||||
_, err = exec.Command("mkdir", "-p", supervisorctlLogFolder).Output()
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create log folder ", supervisorctlLogFolder, ": ", err.Error())
|
||||
}
|
||||
|
||||
trustNewIdentities := ""
|
||||
trustNewIdentitiesEnv := utils.GetEnv("JSON_RPC_TRUST_NEW_IDENTITIES", "")
|
||||
if trustNewIdentitiesEnv == "on-first-use" {
|
||||
args = append(args, []string{"--trust-new-identities", "on-first-use"}...)
|
||||
trustNewIdentities = " --trust-new-identities on-first-use"
|
||||
} else if trustNewIdentitiesEnv == "always" {
|
||||
args = append(args, []string{"--trust-new-identities", "always"}...)
|
||||
trustNewIdentities = " --trust-new-identities always"
|
||||
} else if trustNewIdentitiesEnv == "never" {
|
||||
args = append(args, []string{"--trust-new-identities", "never"}...)
|
||||
trustNewIdentities = " --trust-new-identities never"
|
||||
} else if trustNewIdentitiesEnv != "" {
|
||||
log.Fatal("Invalid JSON_RPC_TRUST_NEW_IDENTITIES environment variable set!")
|
||||
}
|
||||
|
||||
args = append(args, "daemon")
|
||||
log.Info("Updated jsonrpc2.yml")
|
||||
|
||||
ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "")
|
||||
if ignoreAttachments == "true" {
|
||||
args = append(args, "--ignore-attachments")
|
||||
//write supervisorctl config
|
||||
supervisorctlConfigFilename := "/etc/supervisor/conf.d/" + "signal-cli-json-rpc-1.conf"
|
||||
|
||||
supervisorctlConfig := fmt.Sprintf(supervisorctlConfigTemplate, supervisorctlProgramName, supervisorctlProgramName,
|
||||
tcpPort, fifoPathname, signalCliConfigDir, trustNewIdentities, signalCliIgnoreAttachments, signalCliIgnoreStories, fifoPathname,
|
||||
supervisorctlProgramName, supervisorctlProgramName)
|
||||
|
||||
err = ioutil.WriteFile(supervisorctlConfigFilename, []byte(supervisorctlConfig), 0644)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't write ", supervisorctlConfigFilename, ": ", err.Error())
|
||||
}
|
||||
|
||||
ignoreStories := utils.GetEnv("JSON_RPC_IGNORE_STORIES", "")
|
||||
if ignoreStories == "true" {
|
||||
args = append(args, "--ignore-stories")
|
||||
}
|
||||
|
||||
ignoreAvatars := utils.GetEnv("JSON_RPC_IGNORE_AVATARS", "")
|
||||
if ignoreAvatars == "true" {
|
||||
args = append(args, "--ignore-avatars")
|
||||
}
|
||||
|
||||
ignoreStickers := utils.GetEnv("JSON_RPC_IGNORE_STICKERS", "")
|
||||
if ignoreStickers == "true" {
|
||||
args = append(args, "--ignore-stickers")
|
||||
}
|
||||
|
||||
args = append(args, []string{"--tcp", "127.0.0.1:" + strconv.FormatInt(tcpPort, 10)}...)
|
||||
|
||||
// write jsonrpc.yml config file
|
||||
err := jsonRpc2ClientConfig.Persist(signalCliConfigDir + "jsonrpc2.yml")
|
||||
err = jsonRpc2ClientConfig.Persist(signalCliConfigDir + "jsonrpc2.yml")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't persist jsonrpc2.yaml: ", err.Error())
|
||||
}
|
||||
|
||||
log.Info("Updated jsonrpc2.yml")
|
||||
|
||||
env := os.Environ()
|
||||
|
||||
err = syscall.Exec("/usr/bin/signal-cli", args, env)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't start signal-cli in json-rpc mode: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,15 +2,15 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const MULTI_ACCOUNT_NUMBER string = "<multi-account>"
|
||||
|
||||
type JsonRpc2ClientConfigEntry struct {
|
||||
TcpPort int64 `yaml:"tcp_port"`
|
||||
TcpPort int64 `yaml:"tcp_port"`
|
||||
FifoPathname string `yaml:"fifo_pathname"`
|
||||
}
|
||||
|
||||
type JsonRpc2ClientConfigEntries struct {
|
||||
@ -47,6 +47,14 @@ func (c *JsonRpc2ClientConfig) GetTcpPortForNumber(number string) (int64, error)
|
||||
return 0, errors.New("Number " + number + " not found in local map")
|
||||
}
|
||||
|
||||
func (c *JsonRpc2ClientConfig) GetFifoPathnameForNumber(number string) (string, error) {
|
||||
if val, ok := c.config.Entries[number]; ok {
|
||||
return val.FifoPathname, nil
|
||||
}
|
||||
|
||||
return "", errors.New("Number " + number + " not found in local map")
|
||||
}
|
||||
|
||||
func (c *JsonRpc2ClientConfig) GetTcpPortsForNumbers() map[string]int64 {
|
||||
mapping := make(map[string]int64)
|
||||
for number, val := range c.config.Entries {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user