Skip to content

Commit

Permalink
Add sendMessageRequestResponse command
Browse files Browse the repository at this point in the history
  • Loading branch information
AsamK committed Feb 18, 2024
1 parent 2c0ad7f commit d84362e
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 0 deletions.
15 changes: 15 additions & 0 deletions client/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -447,3 +455,10 @@ pub enum GroupPermission {
EveryMember,
OnlyAdmins,
}

#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum MessageRequestResponseType {
Accept,
Delete,
}
9 changes: 9 additions & 0 deletions client/src/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;

Expand Down
17 changes: 17 additions & 0 deletions client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/asamk/signal/manager/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ SendMessageResults sendPaymentNotificationMessage(

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);
Expand Down
34 changes: 34 additions & 0 deletions lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -874,6 +875,41 @@ public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single>
}
}

@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);
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down
13 changes: 13 additions & 0 deletions man/signal-cli.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/asamk/signal/commands/Commands.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
8 changes: 8 additions & 0 deletions src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,14 @@ public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Si
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();
}
Expand Down

0 comments on commit d84362e

Please sign in to comment.