Compare commits

...

7 Commits

Author SHA1 Message Date
Stephan Richter
25bd1c0d51
Merge 7e1b98f0c2163bceaa159d89816b2b6c2d0cb2d1 into ed3626fa77db5929d8234a94492046ca7d828cb5 2026-03-04 18:23:50 -08:00
Bernhard B
ed3626fa77 removed "apt-key add"
* "apt-key add" was removed from Debian, since it was insecure.
2026-03-04 21:36:34 +01:00
Bernhard B
6257fa754d updated buildcontainer to golang:1.26-trixie 2026-03-04 20:15:18 +01:00
Bernhard B
dd6d763618 switch back to old buildcontainer image 2026-03-04 17:55:31 +01:00
Bernhard B
1ea89705d5 updated signal-cli-native to v0.14.0 2026-03-04 16:33:45 +01:00
Stephan Richter
7e1b98f0c2 fix: use absolute path for libsignal-client jar copy
The BUILT_LIBSIGNAL_JAR variable captured a relative path that became
invalid after cd /tmp. Switch to capturing just the filename and using
the already-copied absolute path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:17:50 -05:00
Stephan Richter
40dd9a21b2 fix: Signal Desktop v8.0.0 binary ACI compatibility
Signal Desktop v8.0.0 switched from string ACI fields to binary ACI
encoding in protobuf messages. This breaks reactions, mentions, quotes,
and other message features when the library cannot parse the new format.

Two-part fix applied via patch to signal-cli v0.13.24 source build:

1. Bump signal-service-java from unofficial_137 to unofficial_138, which
   adds dual-format ACI parsing (string + binary fallback via
   ServiceId.parseOrNull).

2. Add defensive null guards in MessageEnvelope.java for cases where
   ServiceId resolution still fails (e.g. ACI.UNKNOWN). Preserves
   message content with UNKNOWN_UUID fallback rather than dropping
   entire message components (quotes, reactions, mentions, etc.).

The patch is applied during the x86_64 source build. The source-built
installDist output replaces the release tarball, so both the JVM and
native (GraalVM) paths get the fix.

Non-x86_64 architectures continue using the unpatched release tarball
until signal-cli cuts a new release with unofficial_138.

See: https://github.com/AsamK/signal-cli/pull/1944

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:24:30 -05:00
2 changed files with 120 additions and 18 deletions

View File

@ -1,13 +1,13 @@
ARG SIGNAL_CLI_VERSION=0.14.0
ARG LIBSIGNAL_CLIENT_VERSION=0.87.4
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.13.24+morph027+2
ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION=0.14.0+morph027+3
ARG SWAG_VERSION=1.16.4
ARG GRAALVM_VERSION=25.0.2
ARG BUILD_VERSION_ARG=unset
FROM golang:1.24 AS buildcontainer
FROM golang:1.26-trixie AS buildcontainer
ARG SIGNAL_CLI_VERSION
ARG LIBSIGNAL_CLIENT_VERSION
@ -18,6 +18,7 @@ ARG SIGNAL_CLI_NATIVE_PACKAGE_VERSION
COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
COPY ext/libraries/libsignal-client/signal-cli-native.patch /tmp/signal-cli-native.patch
COPY ext/patches/fix-binary-aci.patch /tmp/fix-binary-aci.patch
# use architecture specific libsignal_jni.so
RUN arch="$(uname -m)"; \
@ -32,7 +33,7 @@ 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 \
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 && \
@ -74,6 +75,7 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \
&& git clone https://github.com/AsamK/signal-cli.git signal-cli-${SIGNAL_CLI_VERSION}-source \
&& cd signal-cli-${SIGNAL_CLI_VERSION}-source \
&& git checkout -q v${SIGNAL_CLI_VERSION} \
&& git apply /tmp/fix-binary-aci.patch \
&& cd /tmp && mkdir -p /tmp/graalvm && tar xf gvm.tar.gz -C /tmp/graalvm --strip-components=1 \
&& export GRAALVM_HOME=/tmp/graalvm \
&& export PATH=/tmp/graalvm/bin:$PATH \
@ -81,17 +83,21 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \
&& 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) \
&& BUILT_LIBSIGNAL_JAR_NAME=$(ls build/install/signal-cli/lib/ | grep 'libsignal-client-.*\.jar' | head -1) \
&& echo "Built libsignal-client jar: ${BUILT_LIBSIGNAL_JAR_NAME}" \
&& rm -rf /tmp/signal-cli-${SIGNAL_CLI_VERSION} \
&& cp -a build/install/signal-cli /tmp/signal-cli-${SIGNAL_CLI_VERSION} \
&& cd /tmp \
&& cp signal-cli-${SIGNAL_CLI_VERSION}-source/build/install/signal-cli/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal-client.jar \
&& cp /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/${BUILT_LIBSIGNAL_JAR_NAME} libsignal-client.jar \
&& zip -qu libsignal-client.jar libsignal_jni.so \
&& cp libsignal-client.jar /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/${BUILT_LIBSIGNAL_JAR_NAME} \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source \
&& git apply /tmp/signal-cli-native.patch \
&& ./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 | apt-key add - \
&& echo "deb https://packaging.gitlab.io/signal-cli focal main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
&& curl -fsSL https://packaging.gitlab.io/signal-cli/gpg.key | gpg -o /usr/share/keyrings/signal-cli-native.pgp --dearmor \
&& echo "deb [signed-by=/usr/share/keyrings/signal-cli-native.pgp] https://packaging.gitlab.io/signal-cli signalcli main" > /etc/apt/sources.list.d/morph027-signal-cli.list \
&& mkdir -p /tmp/signal-cli-native \
&& cd /tmp/signal-cli-native \
#&& wget https://gitlab.com/packaging/signal-cli/-/jobs/3716873649/artifacts/download?file_type=archive -O /tmp/signal-cli-native/archive.zip \
@ -112,20 +118,27 @@ RUN if [ "$(uname -m)" = "x86_64" ]; then \
echo "Unknown architecture"; \
fi;
# replace libsignal-client
# Post-processing: inject native libsignal_jni.so and apply BaseConfig workaround.
# On x86_64 the source build (above) already produced a patched installDist with
# the native lib injected, so we only need to package it. On other architectures
# the release tarball is still unpatched — apply the BaseConfig sed workaround and
# inject the native lib there.
RUN ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/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 /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-* && echo "\n\n" && exit 1)
# workaround until upstream is fixed
RUN cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib \
&& unzip signal-cli-${SIGNAL_CLI_VERSION}.jar \
&& sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' org/asamk/signal/BaseConfig.class \
&& zip -r signal-cli-${SIGNAL_CLI_VERSION}.jar org/ META-INF/ \
&& rm -rf META-INF \
&& rm -rf org
RUN if [ "$(uname -m)" != "x86_64" ]; then \
ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar \
|| (echo "\n\nsignal-client jar file with version ${LIBSIGNAL_CLIENT_VERSION} not found.\n\n" \
&& ls /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-* && exit 1) \
&& cd /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib \
&& unzip signal-cli-${SIGNAL_CLI_VERSION}.jar \
&& sed -i 's/Signal-Android\/5.22.3/Signal-Android\/5.51.7/g' org/asamk/signal/BaseConfig.class \
&& zip -r signal-cli-${SIGNAL_CLI_VERSION}.jar org/ META-INF/ \
&& rm -rf META-INF \
&& rm -rf org \
&& cd /tmp/ \
&& zip -qu /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal_jni.so; \
fi
RUN cd /tmp/ \
&& zip -qu /tmp/signal-cli-${SIGNAL_CLI_VERSION}/lib/libsignal-client-${LIBSIGNAL_CLIENT_VERSION}.jar libsignal_jni.so \
&& zip -qr signal-cli-${SIGNAL_CLI_VERSION}.zip signal-cli-${SIGNAL_CLI_VERSION}/* \
&& unzip -q /tmp/signal-cli-${SIGNAL_CLI_VERSION}.zip -d /opt \
&& rm -f /tmp/signal-cli-${SIGNAL_CLI_VERSION}.zip

View File

@ -0,0 +1,89 @@
Fix Signal Desktop v8.0.0 binary ACI encoding compatibility.
Signal Desktop v8.0.0 switched from string ACI fields to binary ACI encoding
in protobuf messages. This causes null ServiceId values when the library
cannot parse the new format, breaking reactions, mentions, quotes, and other
message features.
Two-part fix:
1. Bump signal-service-java from unofficial_137 to unofficial_138 which adds
dual-format ACI parsing (string + binary fallback).
2. Add defensive null guards in MessageEnvelope.java for cases where ServiceId
resolution still fails (e.g. ACI.UNKNOWN), preserving message content with
UNKNOWN_UUID fallback rather than dropping entire message components.
See: https://github.com/AsamK/signal-cli/pull/1944
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9b1bd5f4..0000001 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
logback = "ch.qos.logback:logback-classic:1.5.25"
-signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_137"
+signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_138"
sqlite = "org.xerial:sqlite-jdbc:3.51.1.0"
hikari = "com.zaxxer:HikariCP:7.0.2"
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
index 37946057..57a5a0f4 100644
--- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
+++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
@@ -132,6 +132,7 @@ public record MessageEnvelope(
return new Data(dataMessage.getTimestamp(),
dataMessage.getGroupContext().map(GroupContext::from),
dataMessage.getStoryContext()
+ .filter(s -> s.getAuthorServiceId() != null)
.map((SignalServiceDataMessage.StoryContext storyContext) -> StoryContext.from(storyContext,
recipientResolver,
addressResolver)),
@@ -143,9 +144,10 @@ public record MessageEnvelope(
dataMessage.isEndSession(),
dataMessage.isProfileKeyUpdate(),
dataMessage.getProfileKey().isPresent(),
- dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
+ dataMessage.getReaction()
+ .filter(r -> r.getTargetAuthor() != null)
+ .map(r -> Reaction.from(r, recipientResolver, addressResolver)),
dataMessage.getQuote()
- .filter(q -> q.getAuthor() != null && q.getAuthor().isValid())
.map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)),
dataMessage.getPayment().map(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null),
dataMessage.getAttachments()
@@ -159,10 +161,15 @@ public record MessageEnvelope(
.toList())
.orElse(List.of()),
dataMessage.getPollCreate().map(PollCreate::from),
- dataMessage.getPollVote().map(p -> PollVote.from(p, recipientResolver, addressResolver)),
+ dataMessage.getPollVote()
+ .filter(p -> p.getTargetAuthor() != null)
+ .map(p -> PollVote.from(p, recipientResolver, addressResolver)),
dataMessage.getPollTerminate().map(PollTerminate::from),
dataMessage.getMentions()
- .map(a -> a.stream().map(m -> Mention.from(m, recipientResolver, addressResolver)).toList())
+ .map(a -> a.stream()
+ .filter(m -> m.getServiceId() != null)
+ .map(m -> Mention.from(m, recipientResolver, addressResolver))
+ .toList())
.orElse(List.of()),
dataMessage.getPreviews()
.map(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList())
@@ -241,10 +248,13 @@ public record MessageEnvelope(
RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider
) {
+ final var author = quote.getAuthor() != null && quote.getAuthor().isValid()
+ ? addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor()))
+ .toApiRecipientAddress()
+ : new RecipientAddress(RecipientAddress.UNKNOWN_UUID);
return new Quote(quote.getId(),
- addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor()))
- .toApiRecipientAddress(),
- Optional.of(quote.getText()),
+ author,
+ Optional.ofNullable(quote.getText()),
quote.getMentions() == null
? List.of()
: quote.getMentions()