]> nmode's Git Repositories - signal-cli/commitdiff
Implement editing of previous messages
authorAsamK <asamk@gmx.de>
Thu, 11 May 2023 17:10:29 +0000 (19:10 +0200)
committerAsamK <asamk@gmx.de>
Thu, 11 May 2023 17:10:29 +0000 (19:10 +0200)
13 files changed:
graalvm-config-dir/reflect-config.json
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/ReceiveMessageHandler.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
src/main/java/org/asamk/signal/json/JsonEditMessage.java [new file with mode: 0644]
src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java
src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java

index 70b26757b4b75dad833d3958babd5b49eae8a451..3bab701f3a1761951c58f2a36c14ee69eb2649de 100644 (file)
   "name":"java.lang.Long",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
   "name":"java.lang.Long",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
-  "allDeclaredConstructors":true
+  "allDeclaredConstructors":true,
+  "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
 },
 {
   "name":"java.lang.Number",
 },
 {
   "name":"java.lang.Number",
     {"name":"viewOnce","parameterTypes":[] }
   ]
 },
     {"name":"viewOnce","parameterTypes":[] }
   ]
 },
+{
+  "name":"org.asamk.signal.json.JsonEditMessage",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
+  "methods":[
+    {"name":"dataMessage","parameterTypes":[] }, 
+    {"name":"targetSentTimestamp","parameterTypes":[] }
+  ]
+},
 {
   "name":"org.asamk.signal.json.JsonError",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
 {
   "name":"org.asamk.signal.json.JsonError",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
-  "allDeclaredConstructors":true
+  "allDeclaredConstructors":true,
+  "methods":[
+    {"name":"message","parameterTypes":[] }, 
+    {"name":"type","parameterTypes":[] }
+  ]
 },
 {
   "name":"org.asamk.signal.json.JsonGroupInfo",
 },
 {
   "name":"org.asamk.signal.json.JsonGroupInfo",
   "methods":[
     {"name":"callMessage","parameterTypes":[] }, 
     {"name":"dataMessage","parameterTypes":[] }, 
   "methods":[
     {"name":"callMessage","parameterTypes":[] }, 
     {"name":"dataMessage","parameterTypes":[] }, 
+    {"name":"editMessage","parameterTypes":[] }, 
     {"name":"receiptMessage","parameterTypes":[] }, 
     {"name":"source","parameterTypes":[] }, 
     {"name":"sourceDevice","parameterTypes":[] }, 
     {"name":"receiptMessage","parameterTypes":[] }, 
     {"name":"source","parameterTypes":[] }, 
     {"name":"sourceDevice","parameterTypes":[] }, 
   "name":"org.asamk.signal.json.JsonSticker",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
   "name":"org.asamk.signal.json.JsonSticker",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
-  "allDeclaredConstructors":true
+  "allDeclaredConstructors":true,
+  "methods":[
+    {"name":"packId","parameterTypes":[] }, 
+    {"name":"stickerId","parameterTypes":[] }
+  ]
 },
 {
   "name":"org.asamk.signal.json.JsonStoryContext",
 },
 {
   "name":"org.asamk.signal.json.JsonStoryContext",
     {"name":"dataMessage","parameterTypes":[] }, 
     {"name":"destination","parameterTypes":[] }, 
     {"name":"destinationNumber","parameterTypes":[] }, 
     {"name":"dataMessage","parameterTypes":[] }, 
     {"name":"destination","parameterTypes":[] }, 
     {"name":"destinationNumber","parameterTypes":[] }, 
-    {"name":"destinationUuid","parameterTypes":[] }
+    {"name":"destinationUuid","parameterTypes":[] }, 
+    {"name":"editMessage","parameterTypes":[] }
   ]
 },
 {
   ]
 },
 {
   "name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
   "name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
-  "allDeclaredConstructors":true
+  "allDeclaredConstructors":true,
+  "methods":[{"name":"<init>","parameterTypes":[] }]
 },
 {
   "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
 },
 {
   "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential",
   "allDeclaredFields":true,
   "allDeclaredMethods":true,
-  "allDeclaredConstructors":true
+  "allDeclaredConstructors":true,
+  "methods":[{"name":"<init>","parameterTypes":[] }]
 },
 {
   "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"
 },
 {
   "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"
     {"name":"sentTimestamp_"}
   ]
 },
     {"name":"sentTimestamp_"}
   ]
 },
+{
+  "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$EditMessage",
+  "fields":[
+    {"name":"bitField0_"}, 
+    {"name":"dataMessage_"}, 
+    {"name":"targetSentTimestamp_"}
+  ]
+},
 {
   "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope",
   "fields":[
 {
   "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope",
   "fields":[
index bcb8f3482c716a9b8fe0dc30d6e1e9460102bc59..b6602f576768ce94ec9d86991a1ea35a0f1530cf 100644 (file)
@@ -142,6 +142,10 @@ public interface Manager extends Closeable {
             Message message, Set<RecipientIdentifier> recipients
     ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
 
             Message message, Set<RecipientIdentifier> recipients
     ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
 
+    SendMessageResults sendEditMessage(
+            Message message, Set<RecipientIdentifier> recipients, long editTargetTimestamp
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
+
     SendMessageResults sendRemoteDeleteMessage(
             long targetSentTimestamp, Set<RecipientIdentifier> recipients
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
     SendMessageResults sendRemoteDeleteMessage(
             long targetSentTimestamp, Set<RecipientIdentifier> recipients
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
index 9696d956abe24b7dfb6df61b9c41bbebafcf4741..1cc0629be198b449e0d985eb88be806b6b6cc0be 100644 (file)
@@ -466,6 +466,14 @@ class ManagerImpl implements Manager {
 
     private SendMessageResults sendMessage(
             SignalServiceDataMessage.Builder messageBuilder, Set<RecipientIdentifier> recipients
 
     private SendMessageResults sendMessage(
             SignalServiceDataMessage.Builder messageBuilder, Set<RecipientIdentifier> recipients
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return sendMessage(messageBuilder, recipients, Optional.empty());
+    }
+
+    private SendMessageResults sendMessage(
+            SignalServiceDataMessage.Builder messageBuilder,
+            Set<RecipientIdentifier> recipients,
+            Optional<Long> editTargetTimestamp
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
         long timestamp = System.currentTimeMillis();
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
         long timestamp = System.currentTimeMillis();
@@ -474,17 +482,19 @@ class ManagerImpl implements Manager {
             if (recipient instanceof RecipientIdentifier.Single single) {
                 try {
                     final var recipientId = context.getRecipientHelper().resolveRecipient(single);
             if (recipient instanceof RecipientIdentifier.Single single) {
                 try {
                     final var recipientId = context.getRecipientHelper().resolveRecipient(single);
-                    final var result = context.getSendHelper().sendMessage(messageBuilder, recipientId);
+                    final var result = context.getSendHelper()
+                            .sendMessage(messageBuilder, recipientId, editTargetTimestamp);
                     results.put(recipient, List.of(toSendMessageResult(result)));
                 } catch (UnregisteredRecipientException e) {
                     results.put(recipient,
                             List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress())));
                 }
             } else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
                     results.put(recipient, List.of(toSendMessageResult(result)));
                 } catch (UnregisteredRecipientException e) {
                     results.put(recipient,
                             List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress())));
                 }
             } else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
-                final var result = context.getSendHelper().sendSelfMessage(messageBuilder);
+                final var result = context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp);
                 results.put(recipient, List.of(toSendMessageResult(result)));
             } else if (recipient instanceof RecipientIdentifier.Group group) {
                 results.put(recipient, List.of(toSendMessageResult(result)));
             } else if (recipient instanceof RecipientIdentifier.Group group) {
-                final var result = context.getSendHelper().sendAsGroupMessage(messageBuilder, group.groupId());
+                final var result = context.getSendHelper()
+                        .sendAsGroupMessage(messageBuilder, group.groupId(), editTargetTimestamp);
                 results.put(recipient, result.stream().map(this::toSendMessageResult).toList());
             }
         }
                 results.put(recipient, result.stream().map(this::toSendMessageResult).toList());
             }
         }
@@ -581,6 +591,15 @@ class ManagerImpl implements Manager {
         return sendMessage(messageBuilder, recipients);
     }
 
         return sendMessage(messageBuilder, recipients);
     }
 
+    @Override
+    public SendMessageResults sendEditMessage(
+            Message message, Set<RecipientIdentifier> recipients, long editTargetTimestamp
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
+        final var messageBuilder = SignalServiceDataMessage.newBuilder();
+        applyMessage(messageBuilder, message);
+        return sendMessage(messageBuilder, recipients, Optional.of(editTargetTimestamp));
+    }
+
     private void applyMessage(
             final SignalServiceDataMessage.Builder messageBuilder, final Message message
     ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {
     private void applyMessage(
             final SignalServiceDataMessage.Builder messageBuilder, final Message message
     ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {
index c1726c1af61cef67834c4e4d0947f1fa01a328e1..a8f008754c3daa9c82afba9cb82f8af79c0b85b0 100644 (file)
@@ -9,6 +9,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
 import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
@@ -51,6 +52,7 @@ public record MessageEnvelope(
         Optional<Receipt> receipt,
         Optional<Typing> typing,
         Optional<Data> data,
         Optional<Receipt> receipt,
         Optional<Typing> typing,
         Optional<Data> data,
+        Optional<Edit> edit,
         Optional<Sync> sync,
         Optional<Call> call,
         Optional<Story> story
         Optional<Sync> sync,
         Optional<Call> call,
         Optional<Story> story
@@ -541,6 +543,19 @@ public record MessageEnvelope(
         }
     }
 
         }
     }
 
+    public record Edit(long targetSentTimestamp, Data dataMessage) {
+
+        public static Edit from(
+                final SignalServiceEditMessage editMessage,
+                RecipientResolver recipientResolver,
+                RecipientAddressResolver addressResolver,
+                final AttachmentFileProvider fileProvider
+        ) {
+            return new Edit(editMessage.getTargetSentTimestamp(),
+                    Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider));
+        }
+    }
+
     public record Sync(
             Optional<Sent> sent,
             Optional<Blocked> blocked,
     public record Sync(
             Optional<Sent> sent,
             Optional<Blocked> blocked,
@@ -582,6 +597,7 @@ public record MessageEnvelope(
                 Optional<RecipientAddress> destination,
                 Set<RecipientAddress> recipients,
                 Optional<Data> message,
                 Optional<RecipientAddress> destination,
                 Set<RecipientAddress> recipients,
                 Optional<Data> message,
+                Optional<Edit> editMessage,
                 Optional<Story> story
         ) {
 
                 Optional<Story> story
         ) {
 
@@ -603,6 +619,8 @@ public record MessageEnvelope(
                                 .collect(Collectors.toSet()),
                         sentMessage.getDataMessage()
                                 .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
                                 .collect(Collectors.toSet()),
                         sentMessage.getDataMessage()
                                 .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
+                        sentMessage.getEditMessage()
+                                .map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)),
                         sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
             }
         }
                         sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
             }
         }
@@ -920,6 +938,7 @@ public record MessageEnvelope(
         Optional<Receipt> receipt;
         Optional<Typing> typing;
         Optional<Data> data;
         Optional<Receipt> receipt;
         Optional<Typing> typing;
         Optional<Data> data;
+        Optional<Edit> edit;
         Optional<Sync> sync;
         Optional<Call> call;
         Optional<Story> story;
         Optional<Sync> sync;
         Optional<Call> call;
         Optional<Story> story;
@@ -928,6 +947,7 @@ public record MessageEnvelope(
             typing = content.getTypingMessage().map(Typing::from);
             data = content.getDataMessage()
                     .map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider));
             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));
             call = content.getCallMessage().map(Call::from);
             story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
             sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider));
             call = content.getCallMessage().map(Call::from);
             story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
@@ -937,6 +957,7 @@ public record MessageEnvelope(
                     List.of(envelope.getTimestamp()))) : Optional.empty();
             typing = Optional.empty();
             data = Optional.empty();
                     List.of(envelope.getTimestamp()))) : Optional.empty();
             typing = Optional.empty();
             data = Optional.empty();
+            edit = Optional.empty();
             sync = Optional.empty();
             call = Optional.empty();
             story = Optional.empty();
             sync = Optional.empty();
             call = Optional.empty();
             story = Optional.empty();
@@ -953,6 +974,7 @@ public record MessageEnvelope(
                 receipt,
                 typing,
                 data,
                 receipt,
                 typing,
                 data,
+                edit,
                 sync,
                 call,
                 story);
                 sync,
                 call,
                 story);
index bb9a3ea41d16fc596bcc4373dfc11c02ba532697..5658dc25e9b5041632dfb556d40725a8af577849 100644 (file)
@@ -563,7 +563,7 @@ public class GroupHelper {
 
     private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
 
     private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
-        context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId);
+        context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, Optional.empty());
     }
 
     private SendGroupMessageResults updateGroupV2(
     }
 
     private SendGroupMessageResults updateGroupV2(
index 3dd99d3a21ff79308d97a1838275d8a4ea50bb64..a410c2c9e7c29c4f9aef8d3cefb982e3c061889e 100644 (file)
@@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
@@ -71,8 +72,10 @@ public class SendHelper {
      * The message is extended with the current expiration timer.
      */
     public SendMessageResult sendMessage(
      * The message is extended with the current expiration timer.
      */
     public SendMessageResult sendMessage(
-            final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
-    ) throws IOException {
+            final SignalServiceDataMessage.Builder messageBuilder,
+            final RecipientId recipientId,
+            Optional<Long> editTargetTimestamp
+    ) {
         var contact = account.getContactStore().getContact(recipientId);
         if (contact == null || !contact.isProfileSharingEnabled()) {
             final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
         var contact = account.getContactStore().getContact(recipientId);
         if (contact == null || !contact.isProfileSharingEnabled()) {
             final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
@@ -89,7 +92,7 @@ public class SendHelper {
         }
 
         final var message = messageBuilder.build();
         }
 
         final var message = messageBuilder.build();
-        return sendMessage(message, recipientId);
+        return sendMessage(message, recipientId, editTargetTimestamp);
     }
 
     /**
     }
 
     /**
@@ -97,10 +100,10 @@ public class SendHelper {
      * The message is extended with the current expiration timer for the group and the group context.
      */
     public List<SendMessageResult> sendAsGroupMessage(
      * The message is extended with the current expiration timer for the group and the group context.
      */
     public List<SendMessageResult> sendAsGroupMessage(
-            SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
+            SignalServiceDataMessage.Builder messageBuilder, GroupId groupId, Optional<Long> editTargetTimestamp
     ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
         final var g = getGroupForSending(groupId);
     ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException {
         final var g = getGroupForSending(groupId);
-        return sendAsGroupMessage(messageBuilder, g);
+        return sendAsGroupMessage(messageBuilder, g, editTargetTimestamp);
     }
 
     /**
     }
 
     /**
@@ -112,7 +115,7 @@ public class SendHelper {
             final Set<RecipientId> recipientIds,
             final DistributionId distributionId
     ) throws IOException {
             final Set<RecipientId> recipientIds,
             final DistributionId distributionId
     ) throws IOException {
-        return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT);
+        return sendGroupMessage(message, recipientIds, distributionId, ContentHint.IMPLICIT, Optional.empty());
     }
 
     public SendMessageResult sendReceiptMessage(
     }
 
     public SendMessageResult sendReceiptMessage(
@@ -169,7 +172,7 @@ public class SendHelper {
     }
 
     public SendMessageResult sendSelfMessage(
     }
 
     public SendMessageResult sendSelfMessage(
-            SignalServiceDataMessage.Builder messageBuilder
+            SignalServiceDataMessage.Builder messageBuilder, Optional<Long> editTargetTimestamp
     ) {
         final var recipientId = account.getSelfRecipientId();
         final var contact = account.getContactStore().getContact(recipientId);
     ) {
         final var recipientId = account.getSelfRecipientId();
         final var contact = account.getContactStore().getContact(recipientId);
@@ -177,7 +180,7 @@ public class SendHelper {
         messageBuilder.withExpiration(expirationTime);
 
         var message = messageBuilder.build();
         messageBuilder.withExpiration(expirationTime);
 
         var message = messageBuilder.build();
-        return sendSelfMessage(message);
+        return sendSelfMessage(message, editTargetTimestamp);
     }
 
     public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
     }
 
     public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
@@ -289,7 +292,7 @@ public class SendHelper {
     }
 
     private List<SendMessageResult> sendAsGroupMessage(
     }
 
     private List<SendMessageResult> sendAsGroupMessage(
-            final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g
+            final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g, Optional<Long> editTargetTimestamp
     ) throws IOException, GroupSendingNotAllowedException {
         GroupUtils.setGroupContext(messageBuilder, g);
         messageBuilder.withExpiration(g.getMessageExpirationTimer());
     ) throws IOException, GroupSendingNotAllowedException {
         GroupUtils.setGroupContext(messageBuilder, g);
         messageBuilder.withExpiration(g.getMessageExpirationTimer());
@@ -308,14 +311,19 @@ public class SendHelper {
             }
         }
 
             }
         }
 
-        return sendGroupMessage(message, recipients, g.getDistributionId(), ContentHint.RESENDABLE);
+        return sendGroupMessage(message,
+                recipients,
+                g.getDistributionId(),
+                ContentHint.RESENDABLE,
+                editTargetTimestamp);
     }
 
     private List<SendMessageResult> sendGroupMessage(
             final SignalServiceDataMessage message,
             final Set<RecipientId> recipientIds,
             final DistributionId distributionId,
     }
 
     private List<SendMessageResult> sendGroupMessage(
             final SignalServiceDataMessage message,
             final Set<RecipientId> recipientIds,
             final DistributionId distributionId,
-            final ContentHint contentHint
+            final ContentHint contentHint,
+            final Optional<Long> editTargetTimestamp
     ) throws IOException {
         final var messageSender = dependencies.getMessageSender();
         final var messageSendLogStore = account.getMessageSendLogStore();
     ) throws IOException {
         final var messageSender = dependencies.getMessageSender();
         final var messageSendLogStore = account.getMessageSendLogStore();
@@ -355,7 +363,7 @@ public class SendHelper {
                 SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY,
                 urgent,
                 false,
                 SignalServiceMessageSender.SenderKeyGroupEvents.EMPTY,
                 urgent,
                 false,
-                null,
+                editTargetTimestamp.map(timestamp -> new SignalServiceEditMessage(timestamp, message)).orElse(null),
                 sendResult -> {
                     logger.trace("Partial message send results: {}", sendResult.size());
                     synchronized (entryId) {
                 sendResult -> {
                     logger.trace("Partial message send results: {}", sendResult.size());
                     synchronized (entryId) {
@@ -613,18 +621,27 @@ public class SendHelper {
     }
 
     private SendMessageResult sendMessage(
     }
 
     private SendMessageResult sendMessage(
-            SignalServiceDataMessage message, RecipientId recipientId
+            SignalServiceDataMessage message, RecipientId recipientId, Optional<Long> editTargetTimestamp
     ) {
         final var messageSendLogStore = account.getMessageSendLogStore();
         final var urgent = true;
     ) {
         final var messageSendLogStore = account.getMessageSendLogStore();
         final var urgent = true;
+        final var includePniSignature = false;
         final var result = handleSendMessage(recipientId,
         final var result = handleSendMessage(recipientId,
-                (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
+                editTargetTimestamp.isEmpty()
+                        ? (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
                         unidentifiedAccess,
                         ContentHint.RESENDABLE,
                         message,
                         SignalServiceMessageSender.IndividualSendEvents.EMPTY,
                         urgent,
                         unidentifiedAccess,
                         ContentHint.RESENDABLE,
                         message,
                         SignalServiceMessageSender.IndividualSendEvents.EMPTY,
                         urgent,
-                        false));
+                        includePniSignature)
+                        : (messageSender, address, unidentifiedAccess) -> messageSender.sendEditMessage(address,
+                                unidentifiedAccess,
+                                ContentHint.RESENDABLE,
+                                message,
+                                SignalServiceMessageSender.IndividualSendEvents.EMPTY,
+                                urgent,
+                                editTargetTimestamp.get()));
         messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE, urgent);
         handleSendMessageResult(result);
         return result;
         messageSendLogStore.insertIfPossible(message.getTimestamp(), result, ContentHint.RESENDABLE, urgent);
         handleSendMessageResult(result);
         return result;
@@ -665,17 +682,17 @@ public class SendHelper {
         }
     }
 
         }
     }
 
-    private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) {
+    private SendMessageResult sendSelfMessage(SignalServiceDataMessage message, Optional<Long> editTargetTimestamp) {
         var address = account.getSelfAddress();
         var transcript = new SentTranscriptMessage(Optional.of(address),
                 message.getTimestamp(),
         var address = account.getSelfAddress();
         var transcript = new SentTranscriptMessage(Optional.of(address),
                 message.getTimestamp(),
-                Optional.of(message),
+                editTargetTimestamp.isEmpty() ? Optional.of(message) : Optional.empty(),
                 message.getExpiresInSeconds(),
                 Map.of(address.getServiceId(), true),
                 false,
                 Optional.empty(),
                 Set.of(),
                 message.getExpiresInSeconds(),
                 Map.of(address.getServiceId(), true),
                 false,
                 Optional.empty(),
                 Set.of(),
-                Optional.empty());
+                editTargetTimestamp.map((timestamp) -> new SignalServiceEditMessage(timestamp, message)));
         var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
 
         return sendSyncMessage(syncMessage);
         var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
 
         return sendSyncMessage(syncMessage);
index a9ccbad4550ce71cb78ff8393c9b6dc6673e93d8..57a10e5aa0d73a48295b6842da26638507887617 100644 (file)
@@ -285,6 +285,9 @@ Specify the number of the author of the story.
 *-e*, *--end-session*::
 Clear session state and send end session message.
 
 *-e*, *--end-session*::
 Clear session state and send end session message.
 
+*--edit-timestamp*::
+Specify the timestamp of a previous message with the recipient or group to send an edited message.
+
 === sendPaymentNotification
 
 Send a payment notification.
 === sendPaymentNotification
 
 Send a payment notification.
index c52700304250fdaaed5d3e5f00b71da5699daeb4..1f4253acd68304389f1ef0a8b57c40ab4cac470b 100644 (file)
@@ -69,6 +69,10 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
             var message = envelope.data().get();
             printDataMessage(writer, message);
         }
             var message = envelope.data().get();
             printDataMessage(writer, message);
         }
+        if (envelope.edit().isPresent()) {
+            var message = envelope.edit().get();
+            printEditMessage(writer, message);
+        }
         if (envelope.story().isPresent()) {
             var message = envelope.story().get();
             printStoryMessage(writer.indentedWriter(), message);
         if (envelope.story().isPresent()) {
             var message = envelope.story().get();
             printStoryMessage(writer.indentedWriter(), message);
@@ -192,6 +196,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
         }
     }
 
         }
     }
 
+    private void printEditMessage(
+            PlainTextWriter writer, MessageEnvelope.Edit message
+    ) {
+        writer.println("Edit: Target message timestamp: {}", DateUtils.formatTimestamp(message.targetSentTimestamp()));
+        printDataMessage(writer.indentedWriter(), message.dataMessage());
+    }
+
     private void printStoryMessage(
             PlainTextWriter writer, MessageEnvelope.Story message
     ) {
     private void printStoryMessage(
             PlainTextWriter writer, MessageEnvelope.Story message
     ) {
index 2a47ab8d0794d4d9e5212f56536290fdbad8f305..9534f182a2ccbc4a4865dd63ffbad090048170d9 100644 (file)
@@ -84,6 +84,9 @@ public class SendCommand implements JsonRpcLocalCommand {
                 .type(long.class)
                 .help("Specify the timestamp of a story to reply to.");
         subparser.addArgument("--story-author").help("Specify the number of the author of the story.");
                 .type(long.class)
                 .help("Specify the timestamp of a story to reply to.");
         subparser.addArgument("--story-author").help("Specify the number of the author of the story.");
+        subparser.addArgument("--edit-timestamp")
+                .type(long.class)
+                .help("Specify the timestamp of a previous message with the recipient or group to send an edited message.");
     }
 
     @Override
     }
 
     @Override
@@ -189,6 +192,8 @@ public class SendCommand implements JsonRpcLocalCommand {
                     "Sending empty message is not allowed, either a message, attachment or sticker must be given.");
         }
 
                     "Sending empty message is not allowed, either a message, attachment or sticker must be given.");
         }
 
+        final var editTimestamp = ns.getLong("edit-timestamp");
+
         try {
             final var message = new Message(messageText,
                     attachments,
         try {
             final var message = new Message(messageText,
                     attachments,
@@ -197,7 +202,9 @@ public class SendCommand implements JsonRpcLocalCommand {
                     Optional.ofNullable(sticker),
                     previews,
                     Optional.ofNullable((storyReply)));
                     Optional.ofNullable(sticker),
                     previews,
                     Optional.ofNullable((storyReply)));
-            var results = m.sendMessage(message, recipientIdentifiers);
+            var results = editTimestamp != null
+                    ? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
+                    : m.sendMessage(message, recipientIdentifiers);
             outputResult(outputWriter, results);
         } catch (AttachmentInvalidException | IOException e) {
             throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
             outputResult(outputWriter, results);
         } catch (AttachmentInvalidException | IOException e) {
             throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
index cefb7a820f68a63030fbd5ff9ffba873c91e18b2..7eca1f2008fed0810398a704b1ae51588c67dec3 100644 (file)
@@ -10,6 +10,7 @@ import org.asamk.signal.manager.api.Group;
 import org.asamk.signal.manager.api.Identity;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
 import org.asamk.signal.manager.api.Identity;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
+import org.asamk.signal.manager.api.InvalidStickerException;
 import org.asamk.signal.manager.api.InvalidUsernameException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.MessageEnvelope;
 import org.asamk.signal.manager.api.InvalidUsernameException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.MessageEnvelope;
@@ -25,6 +26,7 @@ import org.asamk.signal.manager.api.StickerPack;
 import org.asamk.signal.manager.api.StickerPackInvalidException;
 import org.asamk.signal.manager.api.StickerPackUrl;
 import org.asamk.signal.manager.api.TypingAction;
 import org.asamk.signal.manager.api.StickerPackInvalidException;
 import org.asamk.signal.manager.api.StickerPackUrl;
 import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.api.UpdateGroup;
 import org.asamk.signal.manager.api.UpdateProfile;
 import org.asamk.signal.manager.api.UserStatus;
 import org.asamk.signal.manager.api.UpdateGroup;
 import org.asamk.signal.manager.api.UpdateProfile;
 import org.asamk.signal.manager.api.UserStatus;
@@ -369,6 +371,13 @@ public class DbusManagerImpl implements Manager {
                 groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
     }
 
                 groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
     }
 
+    @Override
+    public SendMessageResults sendEditMessage(
+            final Message message, final Set<RecipientIdentifier> recipients, final long editTargetTimestamp
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public SendMessageResults sendRemoteDeleteMessage(
             final long targetSentTimestamp, final Set<RecipientIdentifier> recipients
     @Override
     public SendMessageResults sendRemoteDeleteMessage(
             final long targetSentTimestamp, final Set<RecipientIdentifier> recipients
@@ -801,6 +810,7 @@ public class DbusManagerImpl implements Manager {
                                 List.of())),
                         Optional.empty(),
                         Optional.empty(),
                                 List.of())),
                         Optional.empty(),
                         Optional.empty(),
+                        Optional.empty(),
                         Optional.empty());
                 notifyMessageHandlers(envelope);
             };
                         Optional.empty());
                 notifyMessageHandlers(envelope);
             };
@@ -827,6 +837,7 @@ public class DbusManagerImpl implements Manager {
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
+                        Optional.empty(),
                         Optional.empty());
                 notifyMessageHandlers(envelope);
             };
                         Optional.empty());
                 notifyMessageHandlers(envelope);
             };
@@ -844,6 +855,7 @@ public class DbusManagerImpl implements Manager {
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
+                        Optional.empty(),
                         Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(),
                                 syncReceived.getTimestamp(),
                                 syncReceived.getDestination().isEmpty()
                         Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(),
                                 syncReceived.getTimestamp(),
                                 syncReceived.getDestination().isEmpty()
@@ -874,6 +886,7 @@ public class DbusManagerImpl implements Manager {
                                         List.of(),
                                         List.of(),
                                         List.of())),
                                         List.of(),
                                         List.of(),
                                         List.of())),
+                                Optional.empty(),
                                 Optional.empty())),
                                 Optional.empty(),
                                 List.of(),
                                 Optional.empty())),
                                 Optional.empty(),
                                 List.of(),
diff --git a/src/main/java/org/asamk/signal/json/JsonEditMessage.java b/src/main/java/org/asamk/signal/json/JsonEditMessage.java
new file mode 100644 (file)
index 0000000..193bd16
--- /dev/null
@@ -0,0 +1,10 @@
+package org.asamk.signal.json;
+
+import org.asamk.signal.manager.api.MessageEnvelope;
+
+record JsonEditMessage(long targetSentTimestamp, JsonDataMessage dataMessage) {
+
+    static JsonEditMessage from(MessageEnvelope.Edit editMessage) {
+        return new JsonEditMessage(editMessage.targetSentTimestamp(), JsonDataMessage.from(editMessage.dataMessage()));
+    }
+}
index c32de61a96c3b452100d61009dc53f4bafef66c7..bafa97e7ab2f3d4d897f98b16e451fa7d71458c3 100644 (file)
@@ -18,6 +18,7 @@ public record JsonMessageEnvelope(
         Integer sourceDevice,
         long timestamp,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage,
         Integer sourceDevice,
         long timestamp,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage,
+        @JsonInclude(JsonInclude.Include.NON_NULL) JsonEditMessage editMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryMessage storyMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryMessage storyMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage,
@@ -61,6 +62,7 @@ public record JsonMessageEnvelope(
         final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null);
 
         final var dataMessage = envelope.data().map(JsonDataMessage::from).orElse(null);
         final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null);
 
         final var dataMessage = envelope.data().map(JsonDataMessage::from).orElse(null);
+        final var editMessage = envelope.edit().map(JsonEditMessage::from).orElse(null);
         final var storyMessage = envelope.story().map(JsonStoryMessage::from).orElse(null);
         final var syncMessage = envelope.sync().map(JsonSyncMessage::from).orElse(null);
         final var callMessage = envelope.call().map(JsonCallMessage::from).orElse(null);
         final var storyMessage = envelope.story().map(JsonStoryMessage::from).orElse(null);
         final var syncMessage = envelope.sync().map(JsonSyncMessage::from).orElse(null);
         final var callMessage = envelope.call().map(JsonCallMessage::from).orElse(null);
@@ -72,6 +74,7 @@ public record JsonMessageEnvelope(
                 sourceDevice,
                 timestamp,
                 dataMessage,
                 sourceDevice,
                 timestamp,
                 dataMessage,
+                editMessage,
                 storyMessage,
                 syncMessage,
                 callMessage,
                 storyMessage,
                 syncMessage,
                 callMessage,
index fc84b0887ef75bf9e82393a94f8a8144b44facc5..fe427ef132afa7bebf90bf997a3bab8508ce7b0b 100644 (file)
@@ -1,8 +1,10 @@
 package org.asamk.signal.json;
 
 package org.asamk.signal.json;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonUnwrapped;
 
 import org.asamk.signal.manager.api.MessageEnvelope;
 import com.fasterxml.jackson.annotation.JsonUnwrapped;
 
 import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.RecipientAddress;
 
 import java.util.UUID;
 
 
 import java.util.UUID;
 
@@ -10,22 +12,17 @@ record JsonSyncDataMessage(
         @Deprecated String destination,
         String destinationNumber,
         String destinationUuid,
         @Deprecated String destination,
         String destinationNumber,
         String destinationUuid,
+        @JsonInclude(JsonInclude.Include.NON_NULL) JsonEditMessage editMessage,
         @JsonUnwrapped JsonDataMessage dataMessage
 ) {
 
     static JsonSyncDataMessage from(MessageEnvelope.Sync.Sent transcriptMessage) {
         @JsonUnwrapped JsonDataMessage dataMessage
 ) {
 
     static JsonSyncDataMessage from(MessageEnvelope.Sync.Sent transcriptMessage) {
-        if (transcriptMessage.destination().isPresent()) {
-            final var address = transcriptMessage.destination().get();
-            return new JsonSyncDataMessage(address.getLegacyIdentifier(),
-                    address.number().orElse(null),
-                    address.uuid().map(UUID::toString).orElse(null),
-                    transcriptMessage.message().map(JsonDataMessage::from).orElse(null));
-
-        } else {
-            return new JsonSyncDataMessage(null,
-                    null,
-                    null,
-                    transcriptMessage.message().map(JsonDataMessage::from).orElse(null));
-        }
+        return new JsonSyncDataMessage(transcriptMessage.destination()
+                .map(RecipientAddress::getLegacyIdentifier)
+                .orElse(null),
+                transcriptMessage.destination().flatMap(RecipientAddress::number).orElse(null),
+                transcriptMessage.destination().flatMap(address -> address.uuid().map(UUID::toString)).orElse(null),
+                transcriptMessage.editMessage().map(JsonEditMessage::from).orElse(null),
+                transcriptMessage.message().map(JsonDataMessage::from).orElse(null));
     }
 }
     }
 }