#[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,
},
EveryMember,
OnlyAdmins,
}
+
+#[derive(ValueEnum, Clone, Debug)]
+#[value(rename_all = "kebab-case")]
+pub enum MessageRequestResponseType {
+ Accept,
+ Delete,
+}
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>;
.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
+ }
}
}
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);
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;
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;
}
}
+ 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));
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;
+ };
+ }
}
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;
}
}
+ @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);
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()
continue;
}
context.getGroupHelper().setGroupBlocked(groupId, blocked);
+ context.getSyncHelper()
+ .sendMessageRequestResponse(blocked
+ ? MessageRequestResponse.Type.BLOCK
+ : MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT, groupId);
shouldRotateProfileKey = blocked;
}
if (shouldRotateProfileKey) {
*--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.
addCommand(new RemoteDeleteCommand());
addCommand(new SendCommand());
addCommand(new SendContactsCommand());
+ addCommand(new SendMessageRequestResponseCommand());
addCommand(new SendPaymentNotificationCommand());
addCommand(new SendReactionCommand());
addCommand(new SendReceiptCommand());
--- /dev/null
+package org.asamk.signal.commands;
+
+enum MessageRequestResponseType {
+ ACCEPT {
+ @Override
+ public String toString() {
+ return "accept";
+ }
+ },
+ DELETE {
+ @Override
+ public String toString() {
+ return "delete";
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
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();
}