]> nmode's Git Repositories - signal-cli/commitdiff
Add sendMessageRequestResponse command
authorAsamK <asamk@gmx.de>
Sun, 18 Feb 2024 19:37:20 +0000 (20:37 +0100)
committerAsamK <asamk@gmx.de>
Sun, 18 Feb 2024 19:48:16 +0000 (20:48 +0100)
client/src/cli.rs
client/src/jsonrpc.rs
client/src/main.rs
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java
lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/commands/Commands.java
src/main/java/org/asamk/signal/commands/MessageRequestResponseType.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java

index e5a5a8782156242311e18be1afd2e3a79cfc4fe8..a64ab51173661d9fe73eff8f40d2b0d19268587c 100644 (file)
@@ -277,6 +277,14 @@ pub enum CliCommands {
         #[arg(short = 's', long)]
         stop: bool,
     },
+    SendMessageRequestResponse {
+        recipient: Vec<String>,
+
+        #[arg(short = 'g', long = "group-id")]
+        group_id: Vec<String>,
+
+        r#type: MessageRequestResponseType,
+    },
     SetPin {
         pin: String,
     },
@@ -447,3 +455,10 @@ pub enum GroupPermission {
     EveryMember,
     OnlyAdmins,
 }
+
+#[derive(ValueEnum, Clone, Debug)]
+#[value(rename_all = "kebab-case")]
+pub enum MessageRequestResponseType {
+    Accept,
+    Delete,
+}
index 266047c2d8972a357b5e6cdfb3a42cec04972694..b085cde5009515f1edeea20a7c87671247abd13b 100644 (file)
@@ -247,6 +247,15 @@ pub trait Rpc {
         stop: bool,
     ) -> Result<Value, ErrorObjectOwned>;
 
+    #[method(name = "sendMessageRequestResponse", param_kind = map)]
+    fn send_message_request_response(
+        &self,
+        account: Option<String>,
+        recipients: Vec<String>,
+        #[allow(non_snake_case)] groupIds: Vec<String>,
+        r#type: String,
+    ) -> Result<Value, ErrorObjectOwned>;
+
     #[method(name = "setPin", param_kind = map)]
     fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value, ErrorObjectOwned>;
 
index d934f800f376256be42cb820156119d912e46055..71903e1d7633b0c4fb0eda55b8b3ddaefab34d78 100644 (file)
@@ -437,6 +437,23 @@ async fn handle_command(
                 .start_change_number(cli.account, number, voice, captcha)
                 .await
         }
+        CliCommands::SendMessageRequestResponse {
+            recipient,
+            group_id,
+            r#type,
+        } => {
+            client
+                .send_message_request_response(
+                    cli.account,
+                    recipient,
+                    group_id,
+                    match r#type {
+                        cli::MessageRequestResponseType::Accept => "accept".to_owned(),
+                        cli::MessageRequestResponseType::Delete => "delete".to_owned(),
+                    },
+                )
+                .await
+        }
     }
 }
 
index 52d0aaffb6f08178fcc0e8c164803a80b49123d2..f4d330a4cf13afa9be4aeead4683470af1dfe218 100644 (file)
@@ -204,6 +204,10 @@ public interface Manager extends Closeable {
 
     SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
 
+    SendMessageResults sendMessageRequestResponse(
+            MessageEnvelope.Sync.MessageRequestResponse.Type type, Set<RecipientIdentifier> recipientIdentifiers
+    );
+
     void hideRecipient(RecipientIdentifier.Single recipient);
 
     void deleteRecipient(RecipientIdentifier.Single recipient);
index 9fd5076def5dac80a667f8a6216836470482fa91..4d218003c4c0e48d2a4450b9bd4d8e337543915b 100644 (file)
@@ -2,6 +2,7 @@ package org.asamk.signal.manager.helper;
 
 import org.asamk.signal.manager.api.Contact;
 import org.asamk.signal.manager.api.GroupId;
+import org.asamk.signal.manager.api.MessageEnvelope;
 import org.asamk.signal.manager.api.TrustLevel;
 import org.asamk.signal.manager.storage.SignalAccount;
 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
@@ -28,6 +29,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
 import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
@@ -365,6 +367,25 @@ public class SyncHelper {
         }
     }
 
+    public SendMessageResult sendMessageRequestResponse(
+            final MessageEnvelope.Sync.MessageRequestResponse.Type type, final GroupId groupId
+    ) {
+        final var response = MessageRequestResponseMessage.forGroup(groupId.serialize(), localToRemoteType(type));
+        return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
+    }
+
+    public SendMessageResult sendMessageRequestResponse(
+            final MessageEnvelope.Sync.MessageRequestResponse.Type type, final RecipientId recipientId
+    ) {
+        final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
+        if (address.serviceId().isEmpty()) {
+            return null;
+        }
+        final var response = MessageRequestResponseMessage.forIndividual(address.serviceId().get(),
+                localToRemoteType(type));
+        return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
+    }
+
     private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) {
         var r = new SyncMessage.Request.Builder().type(type).build();
         var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
@@ -389,4 +410,17 @@ public class SyncHelper {
             logger.warn("Failed to download avatar for contact {}, ignoring: {}", address, e.getMessage());
         }
     }
+
+    private MessageRequestResponseMessage.Type localToRemoteType(final MessageEnvelope.Sync.MessageRequestResponse.Type type) {
+        return switch (type) {
+            case UNKNOWN -> MessageRequestResponseMessage.Type.UNKNOWN;
+            case ACCEPT -> MessageRequestResponseMessage.Type.ACCEPT;
+            case DELETE -> MessageRequestResponseMessage.Type.DELETE;
+            case BLOCK -> MessageRequestResponseMessage.Type.BLOCK;
+            case BLOCK_AND_DELETE -> MessageRequestResponseMessage.Type.BLOCK_AND_DELETE;
+            case UNBLOCK_AND_ACCEPT -> MessageRequestResponseMessage.Type.UNBLOCK_AND_ACCEPT;
+            case SPAM -> MessageRequestResponseMessage.Type.SPAM;
+            case BLOCK_AND_SPAM -> MessageRequestResponseMessage.Type.BLOCK_AND_SPAM;
+        };
+    }
 }
index bd484bb7978a1ce087a65e5acbaf6ce3c03b3561..a25c92c03a2e02971743e8aa13148d441deee4b2 100644 (file)
@@ -38,6 +38,7 @@ import org.asamk.signal.manager.api.InvalidUsernameException;
 import org.asamk.signal.manager.api.LastGroupAdminException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.MessageEnvelope.Sync.MessageRequestResponse;
 import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
 import org.asamk.signal.manager.api.NotAGroupMemberException;
 import org.asamk.signal.manager.api.NotPrimaryDeviceException;
@@ -874,6 +875,41 @@ public class ManagerImpl implements Manager {
         }
     }
 
+    @Override
+    public SendMessageResults sendMessageRequestResponse(
+            final MessageRequestResponse.Type type, final Set<RecipientIdentifier> recipients
+    ) {
+        var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
+        for (final var recipient : recipients) {
+            if (recipient instanceof RecipientIdentifier.NoteToSelf || (
+                    recipient instanceof RecipientIdentifier.Single single
+                            && new RecipientAddress(single.toPartialRecipientAddress()).matches(account.getSelfRecipientAddress())
+            )) {
+                final var result = context.getSyncHelper()
+                        .sendMessageRequestResponse(type, account.getSelfRecipientId());
+                if (result != null) {
+                    results.put(recipient, List.of(toSendMessageResult(result)));
+                }
+                results.put(recipient, List.of(toSendMessageResult(result)));
+            } else if (recipient instanceof RecipientIdentifier.Single single) {
+                try {
+                    final var recipientId = context.getRecipientHelper().resolveRecipient(single);
+                    final var result = context.getSyncHelper().sendMessageRequestResponse(type, recipientId);
+                    if (result != null) {
+                        results.put(recipient, List.of(toSendMessageResult(result)));
+                    }
+                } catch (UnregisteredRecipientException e) {
+                    results.put(recipient,
+                            List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress())));
+                }
+            } else if (recipient instanceof RecipientIdentifier.Group group) {
+                final var result = context.getSyncHelper().sendMessageRequestResponse(type, group.groupId());
+                results.put(recipient, List.of(toSendMessageResult(result)));
+            }
+        }
+        return new SendMessageResults(0, results);
+    }
+
     @Override
     public void hideRecipient(final RecipientIdentifier.Single recipient) {
         final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient);
@@ -929,6 +965,10 @@ public class ManagerImpl implements Manager {
                 continue;
             }
             context.getContactHelper().setContactBlocked(recipientId, blocked);
+            context.getSyncHelper()
+                    .sendMessageRequestResponse(blocked
+                            ? MessageRequestResponse.Type.BLOCK
+                            : MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT, recipientId);
             // if we don't have a common group with the blocked contact we need to rotate the profile key
             shouldRotateProfileKey = blocked && (
                     shouldRotateProfileKey || account.getGroupStore()
@@ -957,6 +997,10 @@ public class ManagerImpl implements Manager {
                 continue;
             }
             context.getGroupHelper().setGroupBlocked(groupId, blocked);
+            context.getSyncHelper()
+                    .sendMessageRequestResponse(blocked
+                            ? MessageRequestResponse.Type.BLOCK
+                            : MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT, groupId);
             shouldRotateProfileKey = blocked;
         }
         if (shouldRotateProfileKey) {
index d718571c9b5b5c58a834939e5b386d78515b22d4..712d4976347a0e1ee6add41596eee7ad86cf4f5a 100644 (file)
@@ -346,6 +346,19 @@ 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.
 
+=== sendMessageRequestResponse
+
+Send response to a message request to linked devices.
+
+RECIPIENT::
+Specify the recipients’ phone number.
+
+*-g* GROUP, *--group-id* GROUP::
+Specify the recipient group ID in base64 encoding.
+
+*--type* TYPE::
+Type of message request response (accept, delete)
+
 === sendPaymentNotification
 
 Send a payment notification.
index a963ce4eab3575188b790b04bb88b8d286810cee..51620a7f6bb14f7319afa6741e8da6bd3accb4a4 100644 (file)
@@ -39,6 +39,7 @@ public class Commands {
         addCommand(new RemoteDeleteCommand());
         addCommand(new SendCommand());
         addCommand(new SendContactsCommand());
+        addCommand(new SendMessageRequestResponseCommand());
         addCommand(new SendPaymentNotificationCommand());
         addCommand(new SendReactionCommand());
         addCommand(new SendReceiptCommand());
diff --git a/src/main/java/org/asamk/signal/commands/MessageRequestResponseType.java b/src/main/java/org/asamk/signal/commands/MessageRequestResponseType.java
new file mode 100644 (file)
index 0000000..280a176
--- /dev/null
@@ -0,0 +1,16 @@
+package org.asamk.signal.commands;
+
+enum MessageRequestResponseType {
+    ACCEPT {
+        @Override
+        public String toString() {
+            return "accept";
+        }
+    },
+    DELETE {
+        @Override
+        public String toString() {
+            return "delete";
+        }
+    }
+}
diff --git a/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java
new file mode 100644 (file)
index 0000000..146ebaf
--- /dev/null
@@ -0,0 +1,49 @@
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.impl.Arguments;
+import net.sourceforge.argparse4j.inf.Namespace;
+import net.sourceforge.argparse4j.inf.Subparser;
+
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.MessageEnvelope.Sync.MessageRequestResponse.Type;
+import org.asamk.signal.output.OutputWriter;
+import org.asamk.signal.util.CommandUtil;
+
+public class SendMessageRequestResponseCommand implements JsonRpcLocalCommand {
+
+    @Override
+    public String getName() {
+        return "sendMessageRequestResponse";
+    }
+
+    @Override
+    public void attachToSubparser(final Subparser subparser) {
+        subparser.help("Send response to a message request to linked devices.");
+        subparser.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.").nargs("*");
+        subparser.addArgument("recipient").help("Specify the recipients' phone number.").nargs("*");
+        subparser.addArgument("-u", "--username").help("Specify the recipient username or username link.").nargs("*");
+        subparser.addArgument("--type")
+                .help("Type of message request response")
+                .type(Arguments.enumStringType(MessageRequestResponseType.class))
+                .required(true);
+    }
+
+    @Override
+    public void handleCommand(
+            final Namespace ns, final Manager m, final OutputWriter outputWriter
+    ) throws CommandException {
+        final var recipientStrings = ns.<String>getList("recipient");
+        final var groupIdStrings = ns.<String>getList("group-id");
+        final var usernameStrings = ns.<String>getList("username");
+        final var type = ns.<MessageRequestResponseType>get("type");
+
+        final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m,
+                false,
+                recipientStrings,
+                groupIdStrings,
+                usernameStrings);
+        m.sendMessageRequestResponse(type == MessageRequestResponseType.ACCEPT ? Type.ACCEPT : Type.DELETE,
+                recipientIdentifiers);
+    }
+}
index f684871abdc7bde96c230db95145b0045ff29f7b..522bb3f6bd9414c25263a1234160ba29e0774c47 100644 (file)
@@ -472,6 +472,14 @@ public class DbusManagerImpl implements Manager {
         return new SendMessageResults(0, Map.of());
     }
 
+    @Override
+    public SendMessageResults sendMessageRequestResponse(
+            final MessageEnvelope.Sync.MessageRequestResponse.Type type,
+            final Set<RecipientIdentifier> recipientIdentifiers
+    ) {
+        throw new UnsupportedOperationException();
+    }
+
     public void hideRecipient(final RecipientIdentifier.Single recipient) {
         throw new UnsupportedOperationException();
     }