]> nmode's Git Repositories - signal-cli/commitdiff
Make send behavior more deterministic if there are unregistered recipients
authorAsamK <asamk@gmx.de>
Sun, 14 Nov 2021 13:42:17 +0000 (14:42 +0100)
committerAsamK <asamk@gmx.de>
Sun, 14 Nov 2021 13:42:17 +0000 (14:42 +0100)
Fixes #803

lib/build.gradle.kts
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/UntrustedIdentityException.java
lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/commands/SendReceiptCommand.java
src/main/java/org/asamk/signal/commands/SendTypingCommand.java
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java

index ae1d88a09aacf218a0d18e33018da4bdb30067db..54c23a0da0c5115b909cecd0e7e735c1f358de86 100644 (file)
@@ -14,7 +14,7 @@ repositories {
 }
 
 dependencies {
-    implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_32")
+    implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_33")
     api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
     implementation("com.google.protobuf:protobuf-javalite:3.11.4")
     implementation("org.bouncycastle:bcprov-jdk15on:1.69")
index 2ffb5337e8ae9b10d4c50109ab03deb6141f1fb0..61f6e1d6ca316c67d1547ef28dc69dd82f9fa0c0 100644 (file)
@@ -144,17 +144,17 @@ public interface Manager extends Closeable {
             GroupInviteLinkUrl inviteLinkUrl
     ) throws IOException, InactiveGroupLinkException;
 
-    void sendTypingMessage(
+    SendMessageResults sendTypingMessage(
             TypingAction action, Set<RecipientIdentifier> recipients
-    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
 
-    void sendReadReceipt(
+    SendMessageResults sendReadReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException;
+    ) throws IOException;
 
-    void sendViewedReceipt(
+    SendMessageResults sendViewedReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException;
+    ) throws IOException;
 
     SendMessageResults sendMessage(
             Message message, Set<RecipientIdentifier> recipients
index 81c18683acf8256e2e9dfc93c81c8f48ec429d5c..93296a1c4f98775e5797c622926ad4a2bdde1246 100644 (file)
@@ -620,50 +620,74 @@ public class ManagerImpl implements Manager {
         return new SendMessageResults(timestamp, results);
     }
 
-    private void sendTypingMessage(
+    private SendMessageResults sendTypingMessage(
             SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients
-    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
         final var timestamp = System.currentTimeMillis();
         for (var recipient : recipients) {
             if (recipient instanceof RecipientIdentifier.Single) {
                 final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent());
                 final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
-                sendHelper.sendTypingMessage(message, recipientId);
+                final var result = sendHelper.sendTypingMessage(message, recipientId);
+                results.put(recipient,
+                        List.of(SendMessageResult.from(result,
+                                account.getRecipientStore(),
+                                account.getRecipientStore()::resolveRecipientAddress)));
             } else if (recipient instanceof RecipientIdentifier.Group) {
                 final var groupId = ((RecipientIdentifier.Group) recipient).groupId();
                 final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize()));
-                sendHelper.sendGroupTypingMessage(message, groupId);
+                final var result = sendHelper.sendGroupTypingMessage(message, groupId);
+                results.put(recipient,
+                        result.stream()
+                                .map(r -> SendMessageResult.from(r,
+                                        account.getRecipientStore(),
+                                        account.getRecipientStore()::resolveRecipientAddress))
+                                .collect(Collectors.toList()));
             }
         }
+        return new SendMessageResults(timestamp, results);
     }
 
     @Override
-    public void sendTypingMessage(
+    public SendMessageResults sendTypingMessage(
             TypingAction action, Set<RecipientIdentifier> recipients
-    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
-        sendTypingMessage(action.toSignalService(), recipients);
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return sendTypingMessage(action.toSignalService(), recipients);
     }
 
     @Override
-    public void sendReadReceipt(
+    public SendMessageResults sendReadReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException {
+    ) throws IOException {
+        final var timestamp = System.currentTimeMillis();
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
                 messageIds,
-                System.currentTimeMillis());
+                timestamp);
 
-        sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
+        final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
+        return new SendMessageResults(timestamp,
+                Map.of(sender,
+                        List.of(SendMessageResult.from(result,
+                                account.getRecipientStore(),
+                                account.getRecipientStore()::resolveRecipientAddress))));
     }
 
     @Override
-    public void sendViewedReceipt(
+    public SendMessageResults sendViewedReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException {
+    ) throws IOException {
+        final var timestamp = System.currentTimeMillis();
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
                 messageIds,
-                System.currentTimeMillis());
+                timestamp);
 
-        sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
+        final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
+        return new SendMessageResults(timestamp,
+                Map.of(sender,
+                        List.of(SendMessageResult.from(result,
+                                account.getRecipientStore(),
+                                account.getRecipientStore()::resolveRecipientAddress))));
     }
 
     @Override
index e7bb5436b3181dcdcc6e00072cf8f2082d4b9769..318afd264395be20a533ed2ed0bba5e0c87ea22a 100644 (file)
@@ -5,13 +5,9 @@ import org.asamk.signal.manager.storage.recipients.RecipientAddress;
 public class UntrustedIdentityException extends Exception {
 
     private final RecipientAddress sender;
-    private final Integer senderDevice;
+    private final int senderDevice;
 
-    public UntrustedIdentityException(final RecipientAddress sender) {
-        this(sender, null);
-    }
-
-    public UntrustedIdentityException(final RecipientAddress sender, final Integer senderDevice) {
+    public UntrustedIdentityException(final RecipientAddress sender, final int senderDevice) {
         super("Untrusted identity: " + sender.getIdentifier());
         this.sender = sender;
         this.senderDevice = senderDevice;
@@ -21,7 +17,7 @@ public class UntrustedIdentityException extends Exception {
         return sender;
     }
 
-    public Integer getSenderDevice() {
+    public int getSenderDevice() {
         return senderDevice;
     }
 }
index dc912634b11530ed91134ad123aa20966e30f553..237f813d36f9e5d04a477bfbcb5852d62ab59178 100644 (file)
@@ -1,7 +1,6 @@
 package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.SignalDependencies;
-import org.asamk.signal.manager.UntrustedIdentityException;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
@@ -17,12 +16,14 @@ import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
 import org.whispersystems.libsignal.util.guava.Optional;
 import org.whispersystems.signalservice.api.SignalServiceMessageSender;
 import org.whispersystems.signalservice.api.crypto.ContentHint;
+import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
 import org.whispersystems.signalservice.api.messages.SendMessageResult;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 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.multidevice.SignalServiceSyncMessage;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
 import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@@ -68,8 +69,8 @@ public class SendHelper {
     }
 
     /**
-     * Send a single message to one or multiple recipients.
-     * The message is extended with the current expiration timer for each recipient.
+     * Send a single message to one recipient.
+     * The message is extended with the current expiration timer.
      */
     public SendMessageResult sendMessage(
             final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
@@ -135,67 +136,46 @@ public class SendHelper {
         return result;
     }
 
-    public void sendDeliveryReceipt(
+    public SendMessageResult sendDeliveryReceipt(
             RecipientId recipientId, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException {
+    ) {
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
                 messageIds,
                 System.currentTimeMillis());
 
-        sendReceiptMessage(receiptMessage, recipientId);
+        return sendReceiptMessage(receiptMessage, recipientId);
     }
 
-    public void sendReceiptMessage(
+    public SendMessageResult sendReceiptMessage(
             final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
-    ) throws IOException, UntrustedIdentityException {
-        final var messageSender = dependencies.getMessageSender();
-        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
-        try {
-            messageSender.sendReceipt(address, unidentifiedAccessHelper.getAccessFor(recipientId), receiptMessage);
-        } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
-            throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
-        }
+    ) {
+        return handleSendMessage(recipientId,
+                (messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address,
+                        unidentifiedAccess,
+                        receiptMessage));
     }
 
-    public void sendRetryReceipt(
+    public SendMessageResult sendRetryReceipt(
             DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
-    ) throws IOException, UntrustedIdentityException {
-        var messageSender = dependencies.getMessageSender();
-        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
+    ) {
         logger.debug("Sending retry receipt for {} to {}, device: {}",
                 errorMessage.getTimestamp(),
                 recipientId,
                 errorMessage.getDeviceId());
-        try {
-            messageSender.sendRetryReceipt(address,
-                    unidentifiedAccessHelper.getAccessFor(recipientId),
-                    groupId.transform(GroupId::serialize),
-                    errorMessage);
-        } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
-            throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
-        }
+        return handleSendMessage(recipientId,
+                (messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address,
+                        unidentifiedAccess,
+                        groupId.transform(GroupId::serialize),
+                        errorMessage));
     }
 
-    public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
-        var messageSender = dependencies.getMessageSender();
-
-        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
-        try {
-            try {
-                return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
-            } catch (UnregisteredUserException e) {
-                final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
-                final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
-                return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
-            }
-        } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
-            return SendMessageResult.identityFailure(address, e.getIdentityKey());
-        }
+    public SendMessageResult sendNullMessage(RecipientId recipientId) {
+        return handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage);
     }
 
     public SendMessageResult sendSelfMessage(
             SignalServiceDataMessage.Builder messageBuilder
-    ) throws IOException {
+    ) {
         final var recipientId = account.getSelfRecipientId();
         final var contact = account.getContactStore().getContact(recipientId);
         final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
@@ -205,35 +185,40 @@ public class SendHelper {
         return sendSelfMessage(message);
     }
 
-    public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
+    public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
         var messageSender = dependencies.getMessageSender();
         try {
             return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
+        } catch (UnregisteredUserException e) {
+            var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
+            return SendMessageResult.unregisteredFailure(address);
+        } catch (ProofRequiredException e) {
+            var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
+            return SendMessageResult.proofRequiredFailure(address, e);
+        } catch (RateLimitException e) {
+            var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
+            logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
+            return SendMessageResult.networkFailure(address);
         } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
             var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
             return SendMessageResult.identityFailure(address, e.getIdentityKey());
+        } catch (IOException e) {
+            var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
+            logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
+            return SendMessageResult.networkFailure(address);
         }
     }
 
-    public void sendTypingMessage(
+    public SendMessageResult sendTypingMessage(
             SignalServiceTypingMessage message, RecipientId recipientId
-    ) throws IOException, UntrustedIdentityException {
-        var messageSender = dependencies.getMessageSender();
-        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
-        try {
-            try {
-                messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
-            } catch (UnregisteredUserException e) {
-                final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
-                final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
-                messageSender.sendTyping(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId), message);
-            }
-        } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
-            throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
-        }
+    ) {
+        return handleSendMessage(recipientId,
+                (messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(address,
+                        unidentifiedAccess,
+                        message));
     }
 
-    public void sendGroupTypingMessage(
+    public List<SendMessageResult> sendGroupTypingMessage(
             SignalServiceTypingMessage message, GroupId groupId
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         final var g = getGroupForSending(groupId);
@@ -245,7 +230,10 @@ public class SendHelper {
         final var addresses = recipientIdList.stream()
                 .map(addressResolver::resolveSignalServiceAddress)
                 .collect(Collectors.toList());
-        messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
+        return messageSender.sendTyping(addresses,
+                unidentifiedAccessHelper.getAccessFor(recipientIdList),
+                message,
+                null);
     }
 
     private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
@@ -285,25 +273,29 @@ public class SendHelper {
 
     private SendMessageResult sendMessage(
             SignalServiceDataMessage message, RecipientId recipientId
-    ) throws IOException {
+    ) {
+        return handleSendMessage(recipientId,
+                (messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
+                        unidentifiedAccess,
+                        ContentHint.DEFAULT,
+                        message,
+                        SignalServiceMessageSender.IndividualSendEvents.EMPTY));
+    }
+
+    private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) {
         var messageSender = dependencies.getMessageSender();
 
-        final var address = addressResolver.resolveSignalServiceAddress(recipientId);
+        var address = addressResolver.resolveSignalServiceAddress(recipientId);
         try {
             try {
-                return messageSender.sendDataMessage(address,
-                        unidentifiedAccessHelper.getAccessFor(recipientId),
-                        ContentHint.DEFAULT,
-                        message,
-                        SignalServiceMessageSender.IndividualSendEvents.EMPTY);
+                return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(recipientId));
             } catch (UnregisteredUserException e) {
                 final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
-                return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId),
-                        unidentifiedAccessHelper.getAccessFor(newRecipientId),
-                        ContentHint.DEFAULT,
-                        message,
-                        SignalServiceMessageSender.IndividualSendEvents.EMPTY);
+                address = addressResolver.resolveSignalServiceAddress(newRecipientId);
+                return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(newRecipientId));
             }
+        } catch (UnregisteredUserException e) {
+            return SendMessageResult.unregisteredFailure(address);
         } catch (ProofRequiredException e) {
             return SendMessageResult.proofRequiredFailure(address, e);
         } catch (RateLimitException e) {
@@ -311,10 +303,13 @@ public class SendHelper {
             return SendMessageResult.networkFailure(address);
         } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
             return SendMessageResult.identityFailure(address, e.getIdentityKey());
+        } catch (IOException e) {
+            logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
+            return SendMessageResult.networkFailure(address);
         }
     }
 
-    private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
+    private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) {
         var address = account.getSelfAddress();
         var transcript = new SentTranscriptMessage(Optional.of(address),
                 message.getTimestamp(),
@@ -333,4 +328,13 @@ public class SendHelper {
             identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
         }
     }
+
+    interface SenderHandler {
+
+        SendMessageResult send(
+                SignalServiceMessageSender messageSender,
+                SignalServiceAddress address,
+                Optional<UnidentifiedAccessPair> unidentifiedAccess
+        ) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
+    }
 }
index 8d863d5468c1857ff3a52e52fc347249401d514c..a7d29b17779130ff823de4082d8bfe0ad2ce5464 100644 (file)
@@ -83,7 +83,9 @@ public class SendCommand implements JsonRpcLocalCommand {
             }
 
             try {
-                m.sendEndSessionMessage(singleRecipients);
+                final var results = m.sendEndSessionMessage(singleRecipients);
+                outputResult(outputWriter, results.timestamp());
+                ErrorUtils.handleSendMessageResults(results.results());
                 return;
             } catch (IOException e) {
                 throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
index 5dd296827972f8ea9d694a04c342b45401670191..266e3b8f0e09ac90dac9f9dcc384960f4a7eda28 100644 (file)
@@ -3,14 +3,18 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
+import org.asamk.signal.PlainTextWriter;
 import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.UntrustedIdentityException;
+import org.asamk.signal.manager.api.SendMessageResults;
 import org.asamk.signal.util.CommandUtil;
+import org.asamk.signal.util.ErrorUtils;
 
 import java.io.IOException;
+import java.util.Map;
 
 public class SendReceiptCommand implements JsonRpcLocalCommand {
 
@@ -43,16 +47,28 @@ public class SendReceiptCommand implements JsonRpcLocalCommand {
         final var type = ns.getString("type");
 
         try {
+            final SendMessageResults results;
             if (type == null || "read".equals(type)) {
-                m.sendReadReceipt(recipient, targetTimestamps);
+                results = m.sendReadReceipt(recipient, targetTimestamps);
             } else if ("viewed".equals(type)) {
-                m.sendViewedReceipt(recipient, targetTimestamps);
+                results = m.sendViewedReceipt(recipient, targetTimestamps);
             } else {
                 throw new UserErrorException("Unknown receipt type: " + type);
             }
-        } catch (IOException | UntrustedIdentityException e) {
+            outputResult(outputWriter, results.timestamp());
+            ErrorUtils.handleSendMessageResults(results.results());
+        } catch (IOException e) {
             throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
                     .getSimpleName() + ")");
         }
     }
+
+    private void outputResult(final OutputWriter outputWriter, final long timestamp) {
+        if (outputWriter instanceof PlainTextWriter writer) {
+            writer.println("{}", timestamp);
+        } else {
+            final var writer = (JsonWriter) outputWriter;
+            writer.write(Map.of("timestamp", timestamp));
+        }
+    }
 }
index ba062b70ed8b470c1bbf630292133553c9efd31c..9ca55c79bf8ff0585e02dfd6d4781ab21a10e951 100644 (file)
@@ -4,20 +4,23 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.JsonWriter;
 import org.asamk.signal.OutputWriter;
+import org.asamk.signal.PlainTextWriter;
 import org.asamk.signal.commands.exceptions.CommandException;
 import org.asamk.signal.commands.exceptions.UserErrorException;
 import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.UntrustedIdentityException;
 import org.asamk.signal.manager.api.RecipientIdentifier;
 import org.asamk.signal.manager.api.TypingAction;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.util.CommandUtil;
+import org.asamk.signal.util.ErrorUtils;
 
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Map;
 
 public class SendTypingCommand implements JsonRpcLocalCommand {
 
@@ -57,12 +60,23 @@ public class SendTypingCommand implements JsonRpcLocalCommand {
         }
 
         try {
-            m.sendTypingMessage(action, recipientIdentifiers);
-        } catch (IOException | UntrustedIdentityException e) {
+            final var results = m.sendTypingMessage(action, recipientIdentifiers);
+            outputResult(outputWriter, results.timestamp());
+            ErrorUtils.handleSendMessageResults(results.results());
+        } catch (IOException e) {
             throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
                     .getSimpleName() + ")");
         } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
             throw new UserErrorException("Failed to send to group: " + e.getMessage());
         }
     }
+
+    private void outputResult(final OutputWriter outputWriter, final long timestamp) {
+        if (outputWriter instanceof PlainTextWriter writer) {
+            writer.println("{}", timestamp);
+        } else {
+            final var writer = (JsonWriter) outputWriter;
+            writer.write(Map.of("timestamp", timestamp));
+        }
+    }
 }
index b61ca1358f93de03ada08e9b2d54fa6982c49a8e..bde5113a09fa12b8b34a7079e5f41997978ce607 100644 (file)
@@ -6,7 +6,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotMasterDeviceException;
 import org.asamk.signal.manager.StickerPackInvalidException;
-import org.asamk.signal.manager.UntrustedIdentityException;
 import org.asamk.signal.manager.api.Configuration;
 import org.asamk.signal.manager.api.Device;
 import org.asamk.signal.manager.api.Group;
@@ -298,31 +297,34 @@ public class DbusManagerImpl implements Manager {
     }
 
     @Override
-    public void sendTypingMessage(
+    public SendMessageResults sendTypingMessage(
             final TypingAction action, final Set<RecipientIdentifier> recipients
-    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
-        for (final var recipient : recipients) {
-            if (recipient instanceof RecipientIdentifier.Single) {
-                signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(),
-                        action == TypingAction.STOP);
-            } else if (recipient instanceof RecipientIdentifier.Group) {
-                throw new UnsupportedOperationException();
-            }
-        }
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
+        return handleMessage(recipients, numbers -> {
+            numbers.forEach(n -> signal.sendTyping(n, action == TypingAction.STOP));
+            return 0L;
+        }, () -> {
+            signal.sendTyping(signal.getSelfNumber(), action == TypingAction.STOP);
+            return 0L;
+        }, groupId -> {
+            throw new UnsupportedOperationException();
+        });
     }
 
     @Override
-    public void sendReadReceipt(
+    public SendMessageResults sendReadReceipt(
             final RecipientIdentifier.Single sender, final List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException {
+    ) {
         signal.sendReadReceipt(sender.getIdentifier(), messageIds);
+        return new SendMessageResults(0, Map.of());
     }
 
     @Override
-    public void sendViewedReceipt(
+    public SendMessageResults sendViewedReceipt(
             final RecipientIdentifier.Single sender, final List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException {
+    ) {
         signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
+        return new SendMessageResults(0, Map.of());
     }
 
     @Override
index 812110b31bdf2fb11ae656ce53e400990db3d21c..7f0c3b31cbd9ac6e269c3e5ddd958a936ef7439e 100644 (file)
@@ -7,7 +7,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotMasterDeviceException;
 import org.asamk.signal.manager.StickerPackInvalidException;
-import org.asamk.signal.manager.UntrustedIdentityException;
 import org.asamk.signal.manager.api.Identity;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
@@ -305,16 +304,15 @@ public class DbusSignalImpl implements Signal {
         try {
             var recipients = new ArrayList<String>(1);
             recipients.add(recipient);
-            m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
+            final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
                     getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
                             .map(RecipientIdentifier.class::cast)
                             .collect(Collectors.toSet()));
+            checkSendMessageResults(results.timestamp(), results.results());
         } catch (IOException e) {
             throw new Error.Failure(e.getMessage());
         } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
             throw new Error.GroupNotFound(e.getMessage());
-        } catch (UntrustedIdentityException e) {
-            throw new Error.UntrustedIdentity(e.getMessage());
         }
     }
 
@@ -323,11 +321,11 @@ public class DbusSignalImpl implements Signal {
             final String recipient, final List<Long> messageIds
     ) throws Error.Failure, Error.UntrustedIdentity {
         try {
-            m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
+            final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
+                    messageIds);
+            checkSendMessageResults(results.timestamp(), results.results());
         } catch (IOException e) {
             throw new Error.Failure(e.getMessage());
-        } catch (UntrustedIdentityException e) {
-            throw new Error.UntrustedIdentity(e.getMessage());
         }
     }
 
@@ -336,11 +334,11 @@ public class DbusSignalImpl implements Signal {
             final String recipient, final List<Long> messageIds
     ) throws Error.Failure, Error.UntrustedIdentity {
         try {
-            m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
+            final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
+                    messageIds);
+            checkSendMessageResults(results.timestamp(), results.results());
         } catch (IOException e) {
             throw new Error.Failure(e.getMessage());
-        } catch (UntrustedIdentityException e) {
-            throw new Error.UntrustedIdentity(e.getMessage());
         }
     }