Always download long text attachments and use them as message body

Fixes #1901
This commit is contained in:
AsamK 2026-03-01 09:23:04 +01:00
parent 775236efc3
commit 8fcd953ece
2 changed files with 104 additions and 22 deletions

View File

@ -3,6 +3,7 @@ package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupUtils; import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.helper.RecipientAddressResolver; import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientResolver; import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.util.MimeUtils;
import org.signal.core.models.ServiceId; import org.signal.core.models.ServiceId;
import org.signal.libsignal.metadata.ProtocolException; import org.signal.libsignal.metadata.ProtocolException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; 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.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -128,10 +130,22 @@ public record MessageEnvelope(
static Data from( static Data from(
final SignalServiceDataMessage dataMessage, final SignalServiceDataMessage dataMessage,
Map<String, String> longTexts,
RecipientResolver recipientResolver, RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver, RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider 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(), return new Data(dataMessage.getTimestamp(),
dataMessage.getGroupContext().map(GroupContext::from), dataMessage.getGroupContext().map(GroupContext::from),
dataMessage.getStoryContext() dataMessage.getStoryContext()
@ -139,7 +153,7 @@ public record MessageEnvelope(
recipientResolver, recipientResolver,
addressResolver)), addressResolver)),
dataMessage.getGroupCallUpdate().map(GroupCallUpdate::from), dataMessage.getGroupCallUpdate().map(GroupCallUpdate::from),
dataMessage.getBody(), body,
dataMessage.getExpiresInSeconds(), dataMessage.getExpiresInSeconds(),
dataMessage.isExpirationUpdate(), dataMessage.isExpirationUpdate(),
dataMessage.isViewOnce(), dataMessage.isViewOnce(),
@ -621,12 +635,17 @@ public record MessageEnvelope(
public static Edit from( public static Edit from(
final SignalServiceEditMessage editMessage, final SignalServiceEditMessage editMessage,
Map<String, String> longTexts,
RecipientResolver recipientResolver, RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver, RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider final AttachmentFileProvider fileProvider
) { ) {
return new Edit(editMessage.getTargetSentTimestamp(), 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( public static Sync from(
final SignalServiceSyncMessage syncMessage, final SignalServiceSyncMessage syncMessage,
Map<String, String> longTexts,
RecipientResolver recipientResolver, RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver, RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider final AttachmentFileProvider fileProvider
) { ) {
return new Sync(syncMessage.getSent() 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.getBlockedList().map(b -> Blocked.from(b, recipientResolver, addressResolver)),
syncMessage.getRead() syncMessage.getRead()
.map(r -> r.stream().map(rm -> Read.from(rm, recipientResolver, addressResolver)).toList()) .map(r -> r.stream().map(rm -> Read.from(rm, recipientResolver, addressResolver)).toList())
@ -677,6 +697,7 @@ public record MessageEnvelope(
static Sent from( static Sent from(
SentTranscriptMessage sentMessage, SentTranscriptMessage sentMessage,
Map<String, String> longTexts,
RecipientResolver recipientResolver, RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver, RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider final AttachmentFileProvider fileProvider
@ -692,9 +713,17 @@ public record MessageEnvelope(
.toApiRecipientAddress()) .toApiRecipientAddress())
.collect(Collectors.toSet()), .collect(Collectors.toSet()),
sentMessage.getDataMessage() sentMessage.getDataMessage()
.map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)), .map(message -> Data.from(message,
longTexts,
recipientResolver,
addressResolver,
fileProvider)),
sentMessage.getEditMessage() 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))); sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
} }
} }
@ -993,6 +1022,7 @@ public record MessageEnvelope(
public static MessageEnvelope from( public static MessageEnvelope from(
SignalServiceEnvelope envelope, SignalServiceEnvelope envelope,
SignalServiceContent content, SignalServiceContent content,
Map<String, String> longTexts,
RecipientResolver recipientResolver, RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver, RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider, final AttachmentFileProvider fileProvider,
@ -1023,9 +1053,15 @@ public record MessageEnvelope(
receipt = content.getReceiptMessage().map(Receipt::from); receipt = content.getReceiptMessage().map(Receipt::from);
typing = content.getTypingMessage().map(Typing::from); typing = content.getTypingMessage().map(Typing::from);
data = content.getDataMessage() data = content.getDataMessage()
.map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider)); .map(dataMessage -> Data.from(dataMessage,
edit = content.getEditMessage().map(s -> Edit.from(s, recipientResolver, addressResolver, fileProvider)); longTexts,
sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider)); 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); call = content.getCallMessage().map(Call::from);
story = content.getStoryMessage().map(s -> Story.from(s, fileProvider)); story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
} else { } else {

View File

@ -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.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.StickerPack; 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;
import org.signal.core.models.ServiceId.ACI; import org.signal.core.models.ServiceId.ACI;
import org.signal.libsignal.metadata.ProtocolInvalidKeyException; 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.Envelope;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException; 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.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -273,13 +279,18 @@ public final class IncomingMessageHandler {
return List.of(); return List.of();
} else { } else {
List<HandleAction> actions; List<HandleAction> actions;
Map<String, String> longTexts;
if (content != null) { if (content != null) {
actions = handleMessage(envelope, content, receiveConfig); final var results = handleMessage(envelope, content, receiveConfig);
actions = results.first();
longTexts = results.second();
} else { } else {
actions = List.of(); actions = List.of();
longTexts = Map.of();
} }
handler.handleMessage(MessageEnvelope.from(envelope, handler.handleMessage(MessageEnvelope.from(envelope,
content, content,
longTexts,
account.getRecipientResolver(), account.getRecipientResolver(),
account.getRecipientAddressResolver(), account.getRecipientAddressResolver(),
context.getAttachmentHelper()::getAttachmentFile, context.getAttachmentHelper()::getAttachmentFile,
@ -288,12 +299,13 @@ public final class IncomingMessageHandler {
} }
} }
public List<HandleAction> handleMessage( public Pair<List<HandleAction>, Map<String, String>> handleMessage(
SignalServiceEnvelope envelope, SignalServiceEnvelope envelope,
SignalServiceContent content, SignalServiceContent content,
ReceiveConfig receiveConfig ReceiveConfig receiveConfig
) { ) {
var actions = new ArrayList<HandleAction>(); final var actions = new ArrayList<HandleAction>();
final var longTexts = new HashMap<String, String>();
final var senderDeviceAddress = getSender(envelope, content); final var senderDeviceAddress = getSender(envelope, content);
final var sender = senderDeviceAddress.recipientId(); final var sender = senderDeviceAddress.recipientId();
final var senderServiceId = senderDeviceAddress.serviceId(); final var senderServiceId = senderDeviceAddress.serviceId();
@ -368,11 +380,13 @@ public final class IncomingMessageHandler {
message.getTimestamp())); message.getTimestamp()));
} }
actions.addAll(handleSignalServiceDataMessage(message, final var dataResults = handleSignalServiceDataMessage(message,
false, false,
senderDeviceAddress, senderDeviceAddress,
destination, destination,
receiveConfig)); receiveConfig);
actions.addAll(dataResults.first());
longTexts.putAll(dataResults.second());
} }
if (content.getStoryMessage().isPresent()) { if (content.getStoryMessage().isPresent()) {
@ -382,10 +396,12 @@ public final class IncomingMessageHandler {
if (content.getSyncMessage().isPresent()) { if (content.getSyncMessage().isPresent()) {
var syncMessage = content.getSyncMessage().get(); 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( private boolean handlePniSignatureMessage(
@ -471,19 +487,20 @@ public final class IncomingMessageHandler {
} }
} }
private List<HandleAction> handleSyncMessage( private Pair<List<HandleAction>, Map<String, String>> handleSyncMessage(
final SignalServiceEnvelope envelope, final SignalServiceEnvelope envelope,
final SignalServiceSyncMessage syncMessage, final SignalServiceSyncMessage syncMessage,
final DeviceAddress sender, final DeviceAddress sender,
final ReceiveConfig receiveConfig final ReceiveConfig receiveConfig
) { ) {
var actions = new ArrayList<HandleAction>(); final var actions = new ArrayList<HandleAction>();
final var longTexts = new HashMap<String, String>();
account.setMultiDevice(true); account.setMultiDevice(true);
if (syncMessage.getSent().isPresent()) { if (syncMessage.getSent().isPresent()) {
var message = syncMessage.getSent().get(); var message = syncMessage.getSent().get();
final var destination = message.getDestination().orElse(null); final var destination = message.getDestination().orElse(null);
if (message.getDataMessage().isPresent()) { if (message.getDataMessage().isPresent()) {
actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(), final var dataResults = handleSignalServiceDataMessage(message.getDataMessage().get(),
true, true,
sender, sender,
destination == null destination == null
@ -491,7 +508,9 @@ public final class IncomingMessageHandler {
: new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination), : new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
destination.getServiceId(), destination.getServiceId(),
0), 0),
receiveConfig)); receiveConfig);
actions.addAll(dataResults.first());
longTexts.putAll(dataResults.second());
} }
if (message.getStoryMessage().isPresent()) { if (message.getStoryMessage().isPresent()) {
actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(), actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
@ -643,7 +662,7 @@ public final class IncomingMessageHandler {
actions.add(RetrieveDeviceNameAction.create()); actions.add(RetrieveDeviceNameAction.create());
} }
} }
return actions; return new Pair<>(actions, longTexts);
} }
private SignalServiceGroupContext getGroupContext(SignalServiceContent content) { private SignalServiceGroupContext getGroupContext(SignalServiceContent content) {
@ -742,13 +761,14 @@ public final class IncomingMessageHandler {
return false; return false;
} }
private List<HandleAction> handleSignalServiceDataMessage( private Pair<List<HandleAction>, Map<String, String>> handleSignalServiceDataMessage(
SignalServiceDataMessage message, SignalServiceDataMessage message,
boolean isSync, boolean isSync,
DeviceAddress source, DeviceAddress source,
DeviceAddress destination, DeviceAddress destination,
ReceiveConfig receiveConfig ReceiveConfig receiveConfig
) { ) {
final var longTexts = new HashMap<String, String>();
var actions = new ArrayList<HandleAction>(); var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) { if (message.getGroupContext().isPresent()) {
final var groupContext = message.getGroupContext().get(); final var groupContext = message.getGroupContext().get();
@ -843,6 +863,17 @@ public final class IncomingMessageHandler {
if (message.getAttachments().isPresent()) { if (message.getAttachments().isPresent()) {
for (var attachment : message.getAttachments().get()) { for (var attachment : message.getAttachments().get()) {
context.getAttachmentHelper().downloadAttachment(attachment); 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()) { 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()) { if (message.getGiftBadge().isPresent()) {
handleIncomingGiftBadge(message.getGiftBadge().get()); handleIncomingGiftBadge(message.getGiftBadge().get());
@ -892,7 +938,7 @@ public final class IncomingMessageHandler {
.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey())); .enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
} }
} }
return actions; return new Pair<>(actions, longTexts);
} }
private void handleIncomingGiftBadge(final SignalServiceDataMessage.GiftBadge giftBadge) { private void handleIncomingGiftBadge(final SignalServiceDataMessage.GiftBadge giftBadge) {