From 6a6bebd503c02578aeac8a7899f735a522470012 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 28 Feb 2026 09:37:29 +0100 Subject: [PATCH] Add support for receiving pin/unpin messages Related #1923 --- .../signal/manager/api/MessageEnvelope.java | 42 +++++++++- .../asamk/signal/ReceiveMessageHandler.java | 28 +++++++ .../asamk/signal/dbus/DbusManagerImpl.java | 12 ++- .../asamk/signal/json/JsonDataMessage.java | 10 ++- .../org/asamk/signal/json/JsonPinMessage.java | 30 +++++++ .../asamk/signal/json/JsonUnpinMessage.java | 21 +++++ .../signal-cli/reachability-metadata.json | 79 +++++++++++++++++++ 7 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/asamk/signal/json/JsonPinMessage.java create mode 100644 src/main/java/org/asamk/signal/json/JsonUnpinMessage.java 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..c984cd62 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 @@ -120,7 +120,9 @@ public record MessageEnvelope( Optional pollTerminate, List mentions, List previews, - List textStyles + List textStyles, + Optional pinMessage, + Optional unpinMessage ) { static Data from( @@ -169,7 +171,10 @@ public record MessageEnvelope( .orElse(List.of()), dataMessage.getBodyRanges() .map(a -> a.stream().filter(r -> r.style != null).map(TextStyle::from).toList()) - .orElse(List.of())); + .orElse(List.of()), + dataMessage.getPinnedMessage().map(p -> PinMessage.from(p, recipientResolver, addressResolver)), + dataMessage.getUnpinnedMessage() + .map(p -> UnpinMessage.from(p, recipientResolver, addressResolver))); } public record GroupContext(GroupId groupId, boolean isGroupUpdate, int revision) { @@ -564,6 +569,39 @@ public record MessageEnvelope( } } + public record PinMessage( + RecipientAddress targetAuthor, long targetSentTimestamp, long pinDurationSeconds + ) { + + static PinMessage from( + SignalServiceDataMessage.PinnedMessage pinnedMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new PinMessage(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient( + pinnedMessage.getTargetAuthor())).toApiRecipientAddress(), + pinnedMessage.getTargetSentTimestamp(), + Boolean.TRUE.equals(pinnedMessage.getForever()) + ? -1 + : pinnedMessage.getPinDurationInSeconds() == null + ? 0 + : pinnedMessage.getPinDurationInSeconds()); + } + } + + public record UnpinMessage(RecipientAddress targetAuthor, long targetSentTimestamp) { + + static UnpinMessage from( + SignalServiceDataMessage.UnpinnedMessage unpinnedMessage, + RecipientResolver recipientResolver, + RecipientAddressResolver addressResolver + ) { + return new UnpinMessage(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient( + unpinnedMessage.getTargetAuthor())).toApiRecipientAddress(), + unpinnedMessage.getTargetSentTimestamp()); + } + } + } public record Edit(long targetSentTimestamp, Data dataMessage) { diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index c282cce8..ad278f03 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -214,6 +214,16 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final var pollTerminate = message.pollTerminate().get(); writer.println("Poll Terminate: {}", DateUtils.formatTimestamp(pollTerminate.targetSentTimestamp())); } + if (message.pinMessage().isPresent()) { + writer.println("Pin Message:"); + final var pinMessage = message.pinMessage().get(); + printPinMessage(writer.indentedWriter(), pinMessage); + } + if (message.unpinMessage().isPresent()) { + writer.println("Unpin Message:"); + final var unpinMessage = message.unpinMessage().get(); + printUnpinMessage(writer.indentedWriter(), unpinMessage); + } } private void printEditMessage(PlainTextWriter writer, MessageEnvelope.Edit message) { @@ -620,6 +630,24 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } + private void printPinMessage(final PlainTextWriter writer, final MessageEnvelope.Data.PinMessage pinMessage) { + writer.println("Target author: {}", formatContact(pinMessage.targetAuthor())); + writer.println("Target timestamp: {}", DateUtils.formatTimestamp(pinMessage.targetSentTimestamp())); + final var duration = pinMessage.pinDurationSeconds(); + if (duration == -1) { + writer.println("Duration: forever"); + } else if (duration == 0) { + writer.println("Duration: unspecified"); + } else { + writer.println("Duration: {} seconds", duration); + } + } + + private void printUnpinMessage(final PlainTextWriter writer, final MessageEnvelope.Data.UnpinMessage unpinMessage) { + writer.println("Target author: {}", formatContact(unpinMessage.targetAuthor())); + writer.println("Target timestamp: {}", DateUtils.formatTimestamp(unpinMessage.targetSentTimestamp())); + } + private String formatContact(RecipientAddress address) { final var number = address.getLegacyIdentifier(); final var name = m.getContactOrProfileName(RecipientIdentifier.Single.fromAddress(address)); diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 6428ee2e..b9e90817 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -977,7 +977,9 @@ public class DbusManagerImpl implements Manager { Optional.empty(), getMentions(extras), List.of(), - List.of())), + List.of(), + Optional.empty(), + Optional.empty())), Optional.empty(), Optional.empty(), Optional.empty(), @@ -1023,7 +1025,9 @@ public class DbusManagerImpl implements Manager { Optional.empty(), getMentions(extras), List.of(), - List.of()))), + List.of(), + Optional.empty(), + Optional.empty()))), Optional.empty(), Optional.empty(), Optional.empty()); @@ -1101,7 +1105,9 @@ public class DbusManagerImpl implements Manager { Optional.empty(), getMentions(extras), List.of(), - List.of())), + List.of(), + Optional.empty(), + Optional.empty())), Optional.empty(), Optional.empty())), Optional.empty(), diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index b141265e..38699059 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -27,7 +27,9 @@ record JsonDataMessage( @JsonInclude(JsonInclude.Include.NON_NULL) JsonPollTerminate pollTerminate, @JsonInclude(JsonInclude.Include.NON_NULL) List textStyles, @JsonInclude(JsonInclude.Include.NON_NULL) JsonGroupInfo groupInfo, - @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryContext storyContext + @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryContext storyContext, + @JsonInclude(JsonInclude.Include.NON_NULL) JsonPinMessage pinMessage, + @JsonInclude(JsonInclude.Include.NON_NULL) JsonUnpinMessage unpinMessage ) { static JsonDataMessage from(MessageEnvelope.Data dataMessage, Manager m) { @@ -71,6 +73,8 @@ record JsonDataMessage( .stream() .map(JsonTextStyle::from) .toList() : null; + final var pinMessage = dataMessage.pinMessage().map(JsonPinMessage::from).orElse(null); + final var unpinMessage = dataMessage.unpinMessage().map(JsonUnpinMessage::from).orElse(null); return new JsonDataMessage(timestamp, message, @@ -91,6 +95,8 @@ record JsonDataMessage( pollTerminate, textStyles, groupInfo, - storyContext); + storyContext, + pinMessage, + unpinMessage); } } diff --git a/src/main/java/org/asamk/signal/json/JsonPinMessage.java b/src/main/java/org/asamk/signal/json/JsonPinMessage.java new file mode 100644 index 00000000..137805af --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonPinMessage.java @@ -0,0 +1,30 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.api.MessageEnvelope; + +import java.util.UUID; + +public record JsonPinMessage( + @Deprecated String targetAuthor, + String targetAuthorNumber, + String targetAuthorUuid, + long targetSentTimestamp, + long pinDurationSeconds +) { + + static JsonPinMessage from(MessageEnvelope.Data.PinMessage pinMessage) { + final var address = pinMessage.targetAuthor(); + final var targetAuthor = address.getLegacyIdentifier(); + final var targetAuthorNumber = address.number().orElse(null); + final var targetAuthorUuid = address.uuid().map(UUID::toString).orElse(null); + final var targetSentTimestamp = pinMessage.targetSentTimestamp(); + final var pinDurationSeconds = pinMessage.pinDurationSeconds(); + + return new JsonPinMessage(targetAuthor, + targetAuthorNumber, + targetAuthorUuid, + targetSentTimestamp, + pinDurationSeconds); + } +} + diff --git a/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java b/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java new file mode 100644 index 00000000..dad137c5 --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java @@ -0,0 +1,21 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.api.MessageEnvelope; + +import java.util.UUID; + +public record JsonUnpinMessage( + @Deprecated String targetAuthor, String targetAuthorNumber, String targetAuthorUuid, long targetSentTimestamp +) { + + static JsonUnpinMessage from(MessageEnvelope.Data.UnpinMessage unpinMessage) { + final var address = unpinMessage.targetAuthor(); + final var targetAuthor = address.getLegacyIdentifier(); + final var targetAuthorNumber = address.number().orElse(null); + final var targetAuthorUuid = address.uuid().map(UUID::toString).orElse(null); + final var targetSentTimestamp = unpinMessage.targetSentTimestamp(); + + return new JsonUnpinMessage(targetAuthor, targetAuthorNumber, targetAuthorUuid, targetSentTimestamp); + } +} + diff --git a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json index 5024add2..a75dd35a 100644 --- a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json @@ -1299,6 +1299,18 @@ "allDeclaredConstructors": true, "jniAccessible": true }, + { + "type": "java.util.concurrent.CancellationException", + "jniAccessible": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, { "type": "java.util.concurrent.ForkJoinTask", "fields": [ @@ -2565,6 +2577,31 @@ } ] }, + { + "type": "org.asamk.signal.json.JsonPinMessage", + "methods": [ + { + "name": "pinDurationSeconds", + "parameterTypes": [] + }, + { + "name": "targetAuthor", + "parameterTypes": [] + }, + { + "name": "targetAuthorNumber", + "parameterTypes": [] + }, + { + "name": "targetAuthorUuid", + "parameterTypes": [] + }, + { + "name": "targetSentTimestamp", + "parameterTypes": [] + } + ] + }, { "type": "org.asamk.signal.json.JsonPollCreate", "allDeclaredFields": true, @@ -2915,6 +2952,27 @@ "allDeclaredMethods": true, "allDeclaredConstructors": true }, + { + "type": "org.asamk.signal.json.JsonUnpinMessage", + "methods": [ + { + "name": "targetAuthor", + "parameterTypes": [] + }, + { + "name": "targetAuthorNumber", + "parameterTypes": [] + }, + { + "name": "targetAuthorUuid", + "parameterTypes": [] + }, + { + "name": "targetSentTimestamp", + "parameterTypes": [] + } + ] + }, { "type": "org.asamk.signal.jsonrpc.JsonRpcBatchMessage", "allDeclaredFields": true, @@ -9449,6 +9507,18 @@ { "type": "sun.text.resources.CollationData" }, + { + "type": "sun.text.resources.FormatData" + }, + { + "type": "sun.text.resources.FormatData_en" + }, + { + "type": "sun.text.resources.FormatData_en_US" + }, + { + "type": "sun.text.resources.JavaTimeSupplementary" + }, { "type": "sun.text.resources.cldr.FormatData" }, @@ -9461,6 +9531,15 @@ { "type": "sun.util.resources.cldr.CalendarData" }, + { + "type": "sun.util.resources.cldr.TimeZoneNames" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en_US" + }, { "type": { "proxy": [