Add sendMessageRequestResponse command

This commit is contained in:
AsamK 2024-02-18 20:37:20 +01:00
parent 2c0ad7feb7
commit d84362eb0f
11 changed files with 210 additions and 0 deletions

View file

@ -277,6 +277,14 @@ pub enum CliCommands {
#[arg(short = 's', long)] #[arg(short = 's', long)]
stop: bool, stop: bool,
}, },
SendMessageRequestResponse {
recipient: Vec<String>,
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
r#type: MessageRequestResponseType,
},
SetPin { SetPin {
pin: String, pin: String,
}, },
@ -447,3 +455,10 @@ pub enum GroupPermission {
EveryMember, EveryMember,
OnlyAdmins, OnlyAdmins,
} }
#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum MessageRequestResponseType {
Accept,
Delete,
}

View file

@ -247,6 +247,15 @@ pub trait Rpc {
stop: bool, stop: bool,
) -> Result<Value, ErrorObjectOwned>; ) -> 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)] #[method(name = "setPin", param_kind = map)]
fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value, ErrorObjectOwned>; fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value, ErrorObjectOwned>;

View file

@ -437,6 +437,23 @@ async fn handle_command(
.start_change_number(cli.account, number, voice, captcha) .start_change_number(cli.account, number, voice, captcha)
.await .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
}
} }
} }

View file

@ -204,6 +204,10 @@ public interface Manager extends Closeable {
SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException; SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
SendMessageResults sendMessageRequestResponse(
MessageEnvelope.Sync.MessageRequestResponse.Type type, Set<RecipientIdentifier> recipientIdentifiers
);
void hideRecipient(RecipientIdentifier.Single recipient); void hideRecipient(RecipientIdentifier.Single recipient);
void deleteRecipient(RecipientIdentifier.Single recipient); void deleteRecipient(RecipientIdentifier.Single recipient);

View file

@ -2,6 +2,7 @@ package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.api.Contact; import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.GroupId; 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.api.TrustLevel;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfoV1; 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.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage; 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.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; 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) { private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) {
var r = new SyncMessage.Request.Builder().type(type).build(); var r = new SyncMessage.Request.Builder().type(type).build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); 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()); 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;
};
}
} }

View 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.LastGroupAdminException;
import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope; 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.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.NotAGroupMemberException; import org.asamk.signal.manager.api.NotAGroupMemberException;
import org.asamk.signal.manager.api.NotPrimaryDeviceException; 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 @Override
public void hideRecipient(final RecipientIdentifier.Single recipient) { public void hideRecipient(final RecipientIdentifier.Single recipient) {
final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient); final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient);
@ -929,6 +965,10 @@ public class ManagerImpl implements Manager {
continue; continue;
} }
context.getContactHelper().setContactBlocked(recipientId, blocked); 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 // if we don't have a common group with the blocked contact we need to rotate the profile key
shouldRotateProfileKey = blocked && ( shouldRotateProfileKey = blocked && (
shouldRotateProfileKey || account.getGroupStore() shouldRotateProfileKey || account.getGroupStore()
@ -957,6 +997,10 @@ public class ManagerImpl implements Manager {
continue; continue;
} }
context.getGroupHelper().setGroupBlocked(groupId, blocked); context.getGroupHelper().setGroupBlocked(groupId, blocked);
context.getSyncHelper()
.sendMessageRequestResponse(blocked
? MessageRequestResponse.Type.BLOCK
: MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT, groupId);
shouldRotateProfileKey = blocked; shouldRotateProfileKey = blocked;
} }
if (shouldRotateProfileKey) { if (shouldRotateProfileKey) {

View file

@ -346,6 +346,19 @@ Clear session state and send end session message.
*--edit-timestamp*:: *--edit-timestamp*::
Specify the timestamp of a previous message with the recipient or group to send an edited message. 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 === sendPaymentNotification
Send a payment notification. Send a payment notification.

View file

@ -39,6 +39,7 @@ public class Commands {
addCommand(new RemoteDeleteCommand()); addCommand(new RemoteDeleteCommand());
addCommand(new SendCommand()); addCommand(new SendCommand());
addCommand(new SendContactsCommand()); addCommand(new SendContactsCommand());
addCommand(new SendMessageRequestResponseCommand());
addCommand(new SendPaymentNotificationCommand()); addCommand(new SendPaymentNotificationCommand());
addCommand(new SendReactionCommand()); addCommand(new SendReactionCommand());
addCommand(new SendReceiptCommand()); addCommand(new SendReceiptCommand());

View file

@ -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";
}
}
}

View file

@ -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);
}
}

View file

@ -472,6 +472,14 @@ public class DbusManagerImpl implements Manager {
return new SendMessageResults(0, Map.of()); 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) { public void hideRecipient(final RecipientIdentifier.Single recipient) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }