From 8fcd953ece1ca66f98f2dc584b6e274ecf4ca140 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 1 Mar 2026 09:23:04 +0100 Subject: [PATCH] Always download long text attachments and use them as message body Fixes #1901 --- .../signal/manager/api/MessageEnvelope.java | 52 +++++++++++-- .../helper/IncomingMessageHandler.java | 74 +++++++++++++++---- 2 files changed, 104 insertions(+), 22 deletions(-) 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 83783e39..91f75033 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 @@ -3,6 +3,7 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.helper.RecipientAddressResolver; import org.asamk.signal.manager.storage.recipients.RecipientResolver; +import org.asamk.signal.manager.util.MimeUtils; import org.signal.core.models.ServiceId; import org.signal.libsignal.metadata.ProtocolException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -37,6 +38,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage; import java.io.File; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -128,10 +130,22 @@ public record MessageEnvelope( static Data from( final SignalServiceDataMessage dataMessage, + Map longTexts, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, final AttachmentFileProvider fileProvider ) { + var body = dataMessage.getBody(); + if (dataMessage.getAttachments().isPresent()) { + for (final var attachment : dataMessage.getAttachments().get()) { + if (MimeUtils.LONG_TEXT.equals(attachment.getContentType()) && attachment.isPointer()) { + final var longBody = longTexts.get(attachment.asPointer().getRemoteId().toString()); + if (longBody != null) { + body = Optional.of(longBody); + } + } + } + } return new Data(dataMessage.getTimestamp(), dataMessage.getGroupContext().map(GroupContext::from), dataMessage.getStoryContext() @@ -139,7 +153,7 @@ public record MessageEnvelope( recipientResolver, addressResolver)), dataMessage.getGroupCallUpdate().map(GroupCallUpdate::from), - dataMessage.getBody(), + body, dataMessage.getExpiresInSeconds(), dataMessage.isExpirationUpdate(), dataMessage.isViewOnce(), @@ -621,12 +635,17 @@ public record MessageEnvelope( public static Edit from( final SignalServiceEditMessage editMessage, + Map longTexts, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, final AttachmentFileProvider fileProvider ) { return new Edit(editMessage.getTargetSentTimestamp(), - Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider)); + Data.from(editMessage.getDataMessage(), + longTexts, + recipientResolver, + addressResolver, + fileProvider)); } } @@ -643,12 +662,13 @@ public record MessageEnvelope( public static Sync from( final SignalServiceSyncMessage syncMessage, + Map longTexts, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, final AttachmentFileProvider fileProvider ) { return new Sync(syncMessage.getSent() - .map(s -> Sent.from(s, recipientResolver, addressResolver, fileProvider)), + .map(s -> Sent.from(s, longTexts, recipientResolver, addressResolver, fileProvider)), syncMessage.getBlockedList().map(b -> Blocked.from(b, recipientResolver, addressResolver)), syncMessage.getRead() .map(r -> r.stream().map(rm -> Read.from(rm, recipientResolver, addressResolver)).toList()) @@ -677,6 +697,7 @@ public record MessageEnvelope( static Sent from( SentTranscriptMessage sentMessage, + Map longTexts, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, final AttachmentFileProvider fileProvider @@ -692,9 +713,17 @@ public record MessageEnvelope( .toApiRecipientAddress()) .collect(Collectors.toSet()), sentMessage.getDataMessage() - .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)), + .map(message -> Data.from(message, + longTexts, + recipientResolver, + addressResolver, + fileProvider)), sentMessage.getEditMessage() - .map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)), + .map(message -> Edit.from(message, + longTexts, + recipientResolver, + addressResolver, + fileProvider)), sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider))); } } @@ -993,6 +1022,7 @@ public record MessageEnvelope( public static MessageEnvelope from( SignalServiceEnvelope envelope, SignalServiceContent content, + Map longTexts, RecipientResolver recipientResolver, RecipientAddressResolver addressResolver, final AttachmentFileProvider fileProvider, @@ -1023,9 +1053,15 @@ public record MessageEnvelope( receipt = content.getReceiptMessage().map(Receipt::from); typing = content.getTypingMessage().map(Typing::from); data = content.getDataMessage() - .map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider)); - edit = content.getEditMessage().map(s -> Edit.from(s, recipientResolver, addressResolver, fileProvider)); - sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider)); + .map(dataMessage -> Data.from(dataMessage, + longTexts, + recipientResolver, + addressResolver, + fileProvider)); + edit = content.getEditMessage() + .map(s -> Edit.from(s, longTexts, recipientResolver, addressResolver, fileProvider)); + sync = content.getSyncMessage() + .map(s -> Sync.from(s, longTexts, recipientResolver, addressResolver, fileProvider)); call = content.getCallMessage().map(Call::from); story = content.getStoryMessage().map(s -> Story.from(s, fileProvider)); } else { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 10276ac9..dbc9f8b4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -35,6 +35,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV1; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.stickers.StickerPack; +import org.asamk.signal.manager.util.MimeUtils; import org.signal.core.models.ServiceId; import org.signal.core.models.ServiceId.ACI; import org.signal.libsignal.metadata.ProtocolInvalidKeyException; @@ -70,8 +71,13 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.Envelope; import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -273,13 +279,18 @@ public final class IncomingMessageHandler { return List.of(); } else { List actions; + Map longTexts; if (content != null) { - actions = handleMessage(envelope, content, receiveConfig); + final var results = handleMessage(envelope, content, receiveConfig); + actions = results.first(); + longTexts = results.second(); } else { actions = List.of(); + longTexts = Map.of(); } handler.handleMessage(MessageEnvelope.from(envelope, content, + longTexts, account.getRecipientResolver(), account.getRecipientAddressResolver(), context.getAttachmentHelper()::getAttachmentFile, @@ -288,12 +299,13 @@ public final class IncomingMessageHandler { } } - public List handleMessage( + public Pair, Map> handleMessage( SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig ) { - var actions = new ArrayList(); + final var actions = new ArrayList(); + final var longTexts = new HashMap(); final var senderDeviceAddress = getSender(envelope, content); final var sender = senderDeviceAddress.recipientId(); final var senderServiceId = senderDeviceAddress.serviceId(); @@ -368,11 +380,13 @@ public final class IncomingMessageHandler { message.getTimestamp())); } - actions.addAll(handleSignalServiceDataMessage(message, + final var dataResults = handleSignalServiceDataMessage(message, false, senderDeviceAddress, destination, - receiveConfig)); + receiveConfig); + actions.addAll(dataResults.first()); + longTexts.putAll(dataResults.second()); } if (content.getStoryMessage().isPresent()) { @@ -382,10 +396,12 @@ public final class IncomingMessageHandler { if (content.getSyncMessage().isPresent()) { var syncMessage = content.getSyncMessage().get(); - actions.addAll(handleSyncMessage(envelope, syncMessage, senderDeviceAddress, receiveConfig)); + final var syncResults = handleSyncMessage(envelope, syncMessage, senderDeviceAddress, receiveConfig); + actions.addAll(syncResults.first()); + longTexts.putAll(syncResults.second()); } - return actions; + return new Pair<>(actions, longTexts); } private boolean handlePniSignatureMessage( @@ -471,19 +487,20 @@ public final class IncomingMessageHandler { } } - private List handleSyncMessage( + private Pair, Map> handleSyncMessage( final SignalServiceEnvelope envelope, final SignalServiceSyncMessage syncMessage, final DeviceAddress sender, final ReceiveConfig receiveConfig ) { - var actions = new ArrayList(); + final var actions = new ArrayList(); + final var longTexts = new HashMap(); account.setMultiDevice(true); if (syncMessage.getSent().isPresent()) { var message = syncMessage.getSent().get(); final var destination = message.getDestination().orElse(null); if (message.getDataMessage().isPresent()) { - actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(), + final var dataResults = handleSignalServiceDataMessage(message.getDataMessage().get(), true, sender, destination == null @@ -491,7 +508,9 @@ public final class IncomingMessageHandler { : new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination), destination.getServiceId(), 0), - receiveConfig)); + receiveConfig); + actions.addAll(dataResults.first()); + longTexts.putAll(dataResults.second()); } if (message.getStoryMessage().isPresent()) { actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(), @@ -643,7 +662,7 @@ public final class IncomingMessageHandler { actions.add(RetrieveDeviceNameAction.create()); } } - return actions; + return new Pair<>(actions, longTexts); } private SignalServiceGroupContext getGroupContext(SignalServiceContent content) { @@ -742,13 +761,14 @@ public final class IncomingMessageHandler { return false; } - private List handleSignalServiceDataMessage( + private Pair, Map> handleSignalServiceDataMessage( SignalServiceDataMessage message, boolean isSync, DeviceAddress source, DeviceAddress destination, ReceiveConfig receiveConfig ) { + final var longTexts = new HashMap(); var actions = new ArrayList(); if (message.getGroupContext().isPresent()) { final var groupContext = message.getGroupContext().get(); @@ -843,6 +863,17 @@ public final class IncomingMessageHandler { if (message.getAttachments().isPresent()) { for (var attachment : message.getAttachments().get()) { context.getAttachmentHelper().downloadAttachment(attachment); + if (attachment.isPointer()) { + final var file = context.getAttachmentHelper().getAttachmentFile(attachment.asPointer()); + if (MimeUtils.LONG_TEXT.equals(attachment.getContentType()) && attachment.isPointer()) { + try { + final var longText = Files.readString(file.toPath()); + longTexts.put(attachment.asPointer().getRemoteId().toString(), longText); + } catch (IOException e) { + logger.warn("Failed to read long text attachment, ignoring", e); + } + } + } } } if (message.getSharedContacts().isPresent()) { @@ -872,6 +903,21 @@ public final class IncomingMessageHandler { } } } + } else { + if (message.getAttachments().isPresent()) { + for (var attachment : message.getAttachments().get()) { + if (MimeUtils.LONG_TEXT.equals(attachment.getContentType()) && attachment.isPointer()) { + try { + context.getAttachmentHelper().retrieveAttachment(attachment, in -> { + final var longText = new String(in.readAllBytes(), StandardCharsets.UTF_8); + longTexts.put(attachment.asPointer().getRemoteId().toString(), longText); + }); + } catch (IOException e) { + logger.warn("Failed to download long text attachment, ignoring", e); + } + } + } + } } if (message.getGiftBadge().isPresent()) { handleIncomingGiftBadge(message.getGiftBadge().get()); @@ -892,7 +938,7 @@ public final class IncomingMessageHandler { .enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey())); } } - return actions; + return new Pair<>(actions, longTexts); } private void handleIncomingGiftBadge(final SignalServiceDataMessage.GiftBadge giftBadge) {