From d84362eb0f022d8bd22321afb5f082b3881f316c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 18 Feb 2024 20:37:20 +0100 Subject: [PATCH] Add sendMessageRequestResponse command --- client/src/cli.rs | 15 ++++++ client/src/jsonrpc.rs | 9 ++++ client/src/main.rs | 17 +++++++ .../org/asamk/signal/manager/Manager.java | 4 ++ .../signal/manager/helper/SyncHelper.java | 34 +++++++++++++ .../signal/manager/internal/ManagerImpl.java | 44 +++++++++++++++++ man/signal-cli.1.adoc | 13 +++++ .../org/asamk/signal/commands/Commands.java | 1 + .../commands/MessageRequestResponseType.java | 16 ++++++ .../SendMessageRequestResponseCommand.java | 49 +++++++++++++++++++ .../asamk/signal/dbus/DbusManagerImpl.java | 8 +++ 11 files changed, 210 insertions(+) create mode 100644 src/main/java/org/asamk/signal/commands/MessageRequestResponseType.java create mode 100644 src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java diff --git a/client/src/cli.rs b/client/src/cli.rs index e5a5a8782..a64ab5117 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -277,6 +277,14 @@ pub enum CliCommands { #[arg(short = 's', long)] stop: bool, }, + SendMessageRequestResponse { + recipient: Vec, + + #[arg(short = 'g', long = "group-id")] + group_id: Vec, + + 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, +} diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index 266047c2d..b085cde50 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -247,6 +247,15 @@ pub trait Rpc { stop: bool, ) -> Result; + #[method(name = "sendMessageRequestResponse", param_kind = map)] + fn send_message_request_response( + &self, + account: Option, + recipients: Vec, + #[allow(non_snake_case)] groupIds: Vec, + r#type: String, + ) -> Result; + #[method(name = "setPin", param_kind = map)] fn set_pin(&self, account: Option, pin: String) -> Result; diff --git a/client/src/main.rs b/client/src/main.rs index d934f800f..71903e1d7 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -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 + } } } diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 52d0aaffb..f4d330a4c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -204,6 +204,10 @@ SendMessageResults sendPaymentNotificationMessage( SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; + SendMessageResults sendMessageRequestResponse( + MessageEnvelope.Sync.MessageRequestResponse.Type type, Set recipientIdentifiers + ); + void hideRecipient(RecipientIdentifier.Single recipient); void deleteRecipient(RecipientIdentifier.Single recipient); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 9fd5076de..4d218003c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -2,6 +2,7 @@ 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.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 void handleSyncDeviceContacts(final InputStream input) throws IOException } } + 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 @@ private void downloadContactAvatar(SignalServiceAttachment avatar, RecipientAddr 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; + }; + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index bd484bb79..a25c92c03 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -38,6 +38,7 @@ 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 SendMessageResults sendEndSessionMessage(Set } } + @Override + public SendMessageResults sendMessageRequestResponse( + final MessageRequestResponse.Type type, final Set recipients + ) { + var results = new HashMap>(); + 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 void setContactsBlocked( 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 void setGroupsBlocked( continue; } context.getGroupHelper().setGroupBlocked(groupId, blocked); + context.getSyncHelper() + .sendMessageRequestResponse(blocked + ? MessageRequestResponse.Type.BLOCK + : MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT, groupId); shouldRotateProfileKey = blocked; } if (shouldRotateProfileKey) { diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index d718571c9..712d49763 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -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. diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index a963ce4ea..51620a7f6 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -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 index 000000000..280a17672 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/MessageRequestResponseType.java @@ -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 index 000000000..146ebaf29 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java @@ -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.getList("recipient"); + final var groupIdStrings = ns.getList("group-id"); + final var usernameStrings = ns.getList("username"); + final var type = ns.get("type"); + + final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m, + false, + recipientStrings, + groupIdStrings, + usernameStrings); + m.sendMessageRequestResponse(type == MessageRequestResponseType.ACCEPT ? Type.ACCEPT : Type.DELETE, + recipientIdentifiers); + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index f684871ab..522bb3f6b 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -472,6 +472,14 @@ public SendMessageResults sendEndSessionMessage(final Set recipientIdentifiers + ) { + throw new UnsupportedOperationException(); + } + public void hideRecipient(final RecipientIdentifier.Single recipient) { throw new UnsupportedOperationException(); }