mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Make send behavior more deterministic if there are unregistered recipients
Fixes #803
This commit is contained in:
parent
fa5c09d23b
commit
382d8d22d0
10 changed files with 192 additions and 136 deletions
|
@ -14,7 +14,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_32")
|
implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_33")
|
||||||
api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
|
api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
|
||||||
implementation("com.google.protobuf:protobuf-javalite:3.11.4")
|
implementation("com.google.protobuf:protobuf-javalite:3.11.4")
|
||||||
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
|
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
|
||||||
|
|
|
@ -144,17 +144,17 @@ public interface Manager extends Closeable {
|
||||||
GroupInviteLinkUrl inviteLinkUrl
|
GroupInviteLinkUrl inviteLinkUrl
|
||||||
) throws IOException, InactiveGroupLinkException;
|
) throws IOException, InactiveGroupLinkException;
|
||||||
|
|
||||||
void sendTypingMessage(
|
SendMessageResults sendTypingMessage(
|
||||||
TypingAction action, Set<RecipientIdentifier> recipients
|
TypingAction action, Set<RecipientIdentifier> recipients
|
||||||
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
|
||||||
|
|
||||||
void sendReadReceipt(
|
SendMessageResults sendReadReceipt(
|
||||||
RecipientIdentifier.Single sender, List<Long> messageIds
|
RecipientIdentifier.Single sender, List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException;
|
) throws IOException;
|
||||||
|
|
||||||
void sendViewedReceipt(
|
SendMessageResults sendViewedReceipt(
|
||||||
RecipientIdentifier.Single sender, List<Long> messageIds
|
RecipientIdentifier.Single sender, List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException;
|
) throws IOException;
|
||||||
|
|
||||||
SendMessageResults sendMessage(
|
SendMessageResults sendMessage(
|
||||||
Message message, Set<RecipientIdentifier> recipients
|
Message message, Set<RecipientIdentifier> recipients
|
||||||
|
|
|
@ -620,50 +620,74 @@ public class ManagerImpl implements Manager {
|
||||||
return new SendMessageResults(timestamp, results);
|
return new SendMessageResults(timestamp, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendTypingMessage(
|
private SendMessageResults sendTypingMessage(
|
||||||
SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients
|
SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients
|
||||||
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||||
|
var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
|
||||||
final var timestamp = System.currentTimeMillis();
|
final var timestamp = System.currentTimeMillis();
|
||||||
for (var recipient : recipients) {
|
for (var recipient : recipients) {
|
||||||
if (recipient instanceof RecipientIdentifier.Single) {
|
if (recipient instanceof RecipientIdentifier.Single) {
|
||||||
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent());
|
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent());
|
||||||
final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
|
final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
|
||||||
sendHelper.sendTypingMessage(message, recipientId);
|
final var result = sendHelper.sendTypingMessage(message, recipientId);
|
||||||
|
results.put(recipient,
|
||||||
|
List.of(SendMessageResult.from(result,
|
||||||
|
account.getRecipientStore(),
|
||||||
|
account.getRecipientStore()::resolveRecipientAddress)));
|
||||||
} else if (recipient instanceof RecipientIdentifier.Group) {
|
} else if (recipient instanceof RecipientIdentifier.Group) {
|
||||||
final var groupId = ((RecipientIdentifier.Group) recipient).groupId();
|
final var groupId = ((RecipientIdentifier.Group) recipient).groupId();
|
||||||
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize()));
|
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize()));
|
||||||
sendHelper.sendGroupTypingMessage(message, groupId);
|
final var result = sendHelper.sendGroupTypingMessage(message, groupId);
|
||||||
|
results.put(recipient,
|
||||||
|
result.stream()
|
||||||
|
.map(r -> SendMessageResult.from(r,
|
||||||
|
account.getRecipientStore(),
|
||||||
|
account.getRecipientStore()::resolveRecipientAddress))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return new SendMessageResults(timestamp, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendTypingMessage(
|
public SendMessageResults sendTypingMessage(
|
||||||
TypingAction action, Set<RecipientIdentifier> recipients
|
TypingAction action, Set<RecipientIdentifier> recipients
|
||||||
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||||
sendTypingMessage(action.toSignalService(), recipients);
|
return sendTypingMessage(action.toSignalService(), recipients);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendReadReceipt(
|
public SendMessageResults sendReadReceipt(
|
||||||
RecipientIdentifier.Single sender, List<Long> messageIds
|
RecipientIdentifier.Single sender, List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException {
|
) throws IOException {
|
||||||
|
final var timestamp = System.currentTimeMillis();
|
||||||
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
|
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
|
||||||
messageIds,
|
messageIds,
|
||||||
System.currentTimeMillis());
|
timestamp);
|
||||||
|
|
||||||
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
|
final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
|
||||||
|
return new SendMessageResults(timestamp,
|
||||||
|
Map.of(sender,
|
||||||
|
List.of(SendMessageResult.from(result,
|
||||||
|
account.getRecipientStore(),
|
||||||
|
account.getRecipientStore()::resolveRecipientAddress))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendViewedReceipt(
|
public SendMessageResults sendViewedReceipt(
|
||||||
RecipientIdentifier.Single sender, List<Long> messageIds
|
RecipientIdentifier.Single sender, List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException {
|
) throws IOException {
|
||||||
|
final var timestamp = System.currentTimeMillis();
|
||||||
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
|
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
|
||||||
messageIds,
|
messageIds,
|
||||||
System.currentTimeMillis());
|
timestamp);
|
||||||
|
|
||||||
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
|
final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
|
||||||
|
return new SendMessageResults(timestamp,
|
||||||
|
Map.of(sender,
|
||||||
|
List.of(SendMessageResult.from(result,
|
||||||
|
account.getRecipientStore(),
|
||||||
|
account.getRecipientStore()::resolveRecipientAddress))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,13 +5,9 @@ import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||||
public class UntrustedIdentityException extends Exception {
|
public class UntrustedIdentityException extends Exception {
|
||||||
|
|
||||||
private final RecipientAddress sender;
|
private final RecipientAddress sender;
|
||||||
private final Integer senderDevice;
|
private final int senderDevice;
|
||||||
|
|
||||||
public UntrustedIdentityException(final RecipientAddress sender) {
|
public UntrustedIdentityException(final RecipientAddress sender, final int senderDevice) {
|
||||||
this(sender, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UntrustedIdentityException(final RecipientAddress sender, final Integer senderDevice) {
|
|
||||||
super("Untrusted identity: " + sender.getIdentifier());
|
super("Untrusted identity: " + sender.getIdentifier());
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.senderDevice = senderDevice;
|
this.senderDevice = senderDevice;
|
||||||
|
@ -21,7 +17,7 @@ public class UntrustedIdentityException extends Exception {
|
||||||
return sender;
|
return sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSenderDevice() {
|
public int getSenderDevice() {
|
||||||
return senderDevice;
|
return senderDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.asamk.signal.manager.helper;
|
package org.asamk.signal.manager.helper;
|
||||||
|
|
||||||
import org.asamk.signal.manager.SignalDependencies;
|
import org.asamk.signal.manager.SignalDependencies;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
|
||||||
import org.asamk.signal.manager.groups.GroupId;
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
|
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
|
||||||
|
@ -17,12 +16,14 @@ import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
import org.whispersystems.signalservice.api.crypto.ContentHint;
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||||
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||||
|
@ -68,8 +69,8 @@ public class SendHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a single message to one or multiple recipients.
|
* Send a single message to one recipient.
|
||||||
* The message is extended with the current expiration timer for each recipient.
|
* The message is extended with the current expiration timer.
|
||||||
*/
|
*/
|
||||||
public SendMessageResult sendMessage(
|
public SendMessageResult sendMessage(
|
||||||
final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
|
final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
|
||||||
|
@ -135,67 +136,46 @@ public class SendHelper {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendDeliveryReceipt(
|
public SendMessageResult sendDeliveryReceipt(
|
||||||
RecipientId recipientId, List<Long> messageIds
|
RecipientId recipientId, List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
|
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
|
||||||
messageIds,
|
messageIds,
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis());
|
||||||
|
|
||||||
sendReceiptMessage(receiptMessage, recipientId);
|
return sendReceiptMessage(receiptMessage, recipientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendReceiptMessage(
|
public SendMessageResult sendReceiptMessage(
|
||||||
final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
|
final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
final var messageSender = dependencies.getMessageSender();
|
return handleSendMessage(recipientId,
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
(messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address,
|
||||||
try {
|
unidentifiedAccess,
|
||||||
messageSender.sendReceipt(address, unidentifiedAccessHelper.getAccessFor(recipientId), receiptMessage);
|
receiptMessage));
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
|
||||||
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendRetryReceipt(
|
public SendMessageResult sendRetryReceipt(
|
||||||
DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
|
DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
var messageSender = dependencies.getMessageSender();
|
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
|
||||||
logger.debug("Sending retry receipt for {} to {}, device: {}",
|
logger.debug("Sending retry receipt for {} to {}, device: {}",
|
||||||
errorMessage.getTimestamp(),
|
errorMessage.getTimestamp(),
|
||||||
recipientId,
|
recipientId,
|
||||||
errorMessage.getDeviceId());
|
errorMessage.getDeviceId());
|
||||||
try {
|
return handleSendMessage(recipientId,
|
||||||
messageSender.sendRetryReceipt(address,
|
(messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address,
|
||||||
unidentifiedAccessHelper.getAccessFor(recipientId),
|
unidentifiedAccess,
|
||||||
groupId.transform(GroupId::serialize),
|
groupId.transform(GroupId::serialize),
|
||||||
errorMessage);
|
errorMessage));
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
|
||||||
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
|
public SendMessageResult sendNullMessage(RecipientId recipientId) {
|
||||||
var messageSender = dependencies.getMessageSender();
|
return handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage);
|
||||||
|
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
|
|
||||||
} catch (UnregisteredUserException e) {
|
|
||||||
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
|
|
||||||
final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
|
|
||||||
return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
|
|
||||||
}
|
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
|
||||||
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendMessageResult sendSelfMessage(
|
public SendMessageResult sendSelfMessage(
|
||||||
SignalServiceDataMessage.Builder messageBuilder
|
SignalServiceDataMessage.Builder messageBuilder
|
||||||
) throws IOException {
|
) {
|
||||||
final var recipientId = account.getSelfRecipientId();
|
final var recipientId = account.getSelfRecipientId();
|
||||||
final var contact = account.getContactStore().getContact(recipientId);
|
final var contact = account.getContactStore().getContact(recipientId);
|
||||||
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
|
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
|
||||||
|
@ -205,35 +185,40 @@ public class SendHelper {
|
||||||
return sendSelfMessage(message);
|
return sendSelfMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
|
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
|
||||||
var messageSender = dependencies.getMessageSender();
|
var messageSender = dependencies.getMessageSender();
|
||||||
try {
|
try {
|
||||||
return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
|
return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
|
||||||
|
} catch (UnregisteredUserException e) {
|
||||||
|
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
||||||
|
return SendMessageResult.unregisteredFailure(address);
|
||||||
|
} catch (ProofRequiredException e) {
|
||||||
|
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
||||||
|
return SendMessageResult.proofRequiredFailure(address, e);
|
||||||
|
} catch (RateLimitException e) {
|
||||||
|
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
||||||
|
logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
|
||||||
|
return SendMessageResult.networkFailure(address);
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
||||||
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
||||||
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
||||||
|
} catch (IOException e) {
|
||||||
|
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
|
||||||
|
logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
|
||||||
|
return SendMessageResult.networkFailure(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendTypingMessage(
|
public SendMessageResult sendTypingMessage(
|
||||||
SignalServiceTypingMessage message, RecipientId recipientId
|
SignalServiceTypingMessage message, RecipientId recipientId
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
var messageSender = dependencies.getMessageSender();
|
return handleSendMessage(recipientId,
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
(messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(address,
|
||||||
try {
|
unidentifiedAccess,
|
||||||
try {
|
message));
|
||||||
messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
|
|
||||||
} catch (UnregisteredUserException e) {
|
|
||||||
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
|
|
||||||
final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
|
|
||||||
messageSender.sendTyping(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId), message);
|
|
||||||
}
|
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
|
||||||
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendGroupTypingMessage(
|
public List<SendMessageResult> sendGroupTypingMessage(
|
||||||
SignalServiceTypingMessage message, GroupId groupId
|
SignalServiceTypingMessage message, GroupId groupId
|
||||||
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||||
final var g = getGroupForSending(groupId);
|
final var g = getGroupForSending(groupId);
|
||||||
|
@ -245,7 +230,10 @@ public class SendHelper {
|
||||||
final var addresses = recipientIdList.stream()
|
final var addresses = recipientIdList.stream()
|
||||||
.map(addressResolver::resolveSignalServiceAddress)
|
.map(addressResolver::resolveSignalServiceAddress)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
|
return messageSender.sendTyping(addresses,
|
||||||
|
unidentifiedAccessHelper.getAccessFor(recipientIdList),
|
||||||
|
message,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
|
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
|
||||||
|
@ -285,25 +273,29 @@ public class SendHelper {
|
||||||
|
|
||||||
private SendMessageResult sendMessage(
|
private SendMessageResult sendMessage(
|
||||||
SignalServiceDataMessage message, RecipientId recipientId
|
SignalServiceDataMessage message, RecipientId recipientId
|
||||||
) throws IOException {
|
) {
|
||||||
|
return handleSendMessage(recipientId,
|
||||||
|
(messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
|
||||||
|
unidentifiedAccess,
|
||||||
|
ContentHint.DEFAULT,
|
||||||
|
message,
|
||||||
|
SignalServiceMessageSender.IndividualSendEvents.EMPTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) {
|
||||||
var messageSender = dependencies.getMessageSender();
|
var messageSender = dependencies.getMessageSender();
|
||||||
|
|
||||||
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
return messageSender.sendDataMessage(address,
|
return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(recipientId));
|
||||||
unidentifiedAccessHelper.getAccessFor(recipientId),
|
|
||||||
ContentHint.DEFAULT,
|
|
||||||
message,
|
|
||||||
SignalServiceMessageSender.IndividualSendEvents.EMPTY);
|
|
||||||
} catch (UnregisteredUserException e) {
|
} catch (UnregisteredUserException e) {
|
||||||
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
|
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
|
||||||
return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId),
|
address = addressResolver.resolveSignalServiceAddress(newRecipientId);
|
||||||
unidentifiedAccessHelper.getAccessFor(newRecipientId),
|
return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(newRecipientId));
|
||||||
ContentHint.DEFAULT,
|
|
||||||
message,
|
|
||||||
SignalServiceMessageSender.IndividualSendEvents.EMPTY);
|
|
||||||
}
|
}
|
||||||
|
} catch (UnregisteredUserException e) {
|
||||||
|
return SendMessageResult.unregisteredFailure(address);
|
||||||
} catch (ProofRequiredException e) {
|
} catch (ProofRequiredException e) {
|
||||||
return SendMessageResult.proofRequiredFailure(address, e);
|
return SendMessageResult.proofRequiredFailure(address, e);
|
||||||
} catch (RateLimitException e) {
|
} catch (RateLimitException e) {
|
||||||
|
@ -311,10 +303,13 @@ public class SendHelper {
|
||||||
return SendMessageResult.networkFailure(address);
|
return SendMessageResult.networkFailure(address);
|
||||||
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
|
||||||
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
return SendMessageResult.identityFailure(address, e.getIdentityKey());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
|
||||||
|
return SendMessageResult.networkFailure(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
|
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) {
|
||||||
var address = account.getSelfAddress();
|
var address = account.getSelfAddress();
|
||||||
var transcript = new SentTranscriptMessage(Optional.of(address),
|
var transcript = new SentTranscriptMessage(Optional.of(address),
|
||||||
message.getTimestamp(),
|
message.getTimestamp(),
|
||||||
|
@ -333,4 +328,13 @@ public class SendHelper {
|
||||||
identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
|
identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SenderHandler {
|
||||||
|
|
||||||
|
SendMessageResult send(
|
||||||
|
SignalServiceMessageSender messageSender,
|
||||||
|
SignalServiceAddress address,
|
||||||
|
Optional<UnidentifiedAccessPair> unidentifiedAccess
|
||||||
|
) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,9 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
m.sendEndSessionMessage(singleRecipients);
|
final var results = m.sendEndSessionMessage(singleRecipients);
|
||||||
|
outputResult(outputWriter, results.timestamp());
|
||||||
|
ErrorUtils.handleSendMessageResults(results.results());
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||||
|
|
|
@ -3,14 +3,18 @@ package org.asamk.signal.commands;
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
import org.asamk.signal.JsonWriter;
|
||||||
import org.asamk.signal.OutputWriter;
|
import org.asamk.signal.OutputWriter;
|
||||||
|
import org.asamk.signal.PlainTextWriter;
|
||||||
import org.asamk.signal.commands.exceptions.CommandException;
|
import org.asamk.signal.commands.exceptions.CommandException;
|
||||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
import org.asamk.signal.manager.api.SendMessageResults;
|
||||||
import org.asamk.signal.util.CommandUtil;
|
import org.asamk.signal.util.CommandUtil;
|
||||||
|
import org.asamk.signal.util.ErrorUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SendReceiptCommand implements JsonRpcLocalCommand {
|
public class SendReceiptCommand implements JsonRpcLocalCommand {
|
||||||
|
|
||||||
|
@ -43,16 +47,28 @@ public class SendReceiptCommand implements JsonRpcLocalCommand {
|
||||||
final var type = ns.getString("type");
|
final var type = ns.getString("type");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
final SendMessageResults results;
|
||||||
if (type == null || "read".equals(type)) {
|
if (type == null || "read".equals(type)) {
|
||||||
m.sendReadReceipt(recipient, targetTimestamps);
|
results = m.sendReadReceipt(recipient, targetTimestamps);
|
||||||
} else if ("viewed".equals(type)) {
|
} else if ("viewed".equals(type)) {
|
||||||
m.sendViewedReceipt(recipient, targetTimestamps);
|
results = m.sendViewedReceipt(recipient, targetTimestamps);
|
||||||
} else {
|
} else {
|
||||||
throw new UserErrorException("Unknown receipt type: " + type);
|
throw new UserErrorException("Unknown receipt type: " + type);
|
||||||
}
|
}
|
||||||
} catch (IOException | UntrustedIdentityException e) {
|
outputResult(outputWriter, results.timestamp());
|
||||||
|
ErrorUtils.handleSendMessageResults(results.results());
|
||||||
|
} catch (IOException e) {
|
||||||
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||||
.getSimpleName() + ")");
|
.getSimpleName() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
|
||||||
|
if (outputWriter instanceof PlainTextWriter writer) {
|
||||||
|
writer.println("{}", timestamp);
|
||||||
|
} else {
|
||||||
|
final var writer = (JsonWriter) outputWriter;
|
||||||
|
writer.write(Map.of("timestamp", timestamp));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,23 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
import org.asamk.signal.JsonWriter;
|
||||||
import org.asamk.signal.OutputWriter;
|
import org.asamk.signal.OutputWriter;
|
||||||
|
import org.asamk.signal.PlainTextWriter;
|
||||||
import org.asamk.signal.commands.exceptions.CommandException;
|
import org.asamk.signal.commands.exceptions.CommandException;
|
||||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
|
||||||
import org.asamk.signal.manager.api.RecipientIdentifier;
|
import org.asamk.signal.manager.api.RecipientIdentifier;
|
||||||
import org.asamk.signal.manager.api.TypingAction;
|
import org.asamk.signal.manager.api.TypingAction;
|
||||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
|
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
|
||||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||||
import org.asamk.signal.util.CommandUtil;
|
import org.asamk.signal.util.CommandUtil;
|
||||||
|
import org.asamk.signal.util.ErrorUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SendTypingCommand implements JsonRpcLocalCommand {
|
public class SendTypingCommand implements JsonRpcLocalCommand {
|
||||||
|
|
||||||
|
@ -57,12 +60,23 @@ public class SendTypingCommand implements JsonRpcLocalCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
m.sendTypingMessage(action, recipientIdentifiers);
|
final var results = m.sendTypingMessage(action, recipientIdentifiers);
|
||||||
} catch (IOException | UntrustedIdentityException e) {
|
outputResult(outputWriter, results.timestamp());
|
||||||
|
ErrorUtils.handleSendMessageResults(results.results());
|
||||||
|
} catch (IOException e) {
|
||||||
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
|
||||||
.getSimpleName() + ")");
|
.getSimpleName() + ")");
|
||||||
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
||||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
|
||||||
|
if (outputWriter instanceof PlainTextWriter writer) {
|
||||||
|
writer.println("{}", timestamp);
|
||||||
|
} else {
|
||||||
|
final var writer = (JsonWriter) outputWriter;
|
||||||
|
writer.write(Map.of("timestamp", timestamp));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.manager.NotMasterDeviceException;
|
import org.asamk.signal.manager.NotMasterDeviceException;
|
||||||
import org.asamk.signal.manager.StickerPackInvalidException;
|
import org.asamk.signal.manager.StickerPackInvalidException;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
|
||||||
import org.asamk.signal.manager.api.Configuration;
|
import org.asamk.signal.manager.api.Configuration;
|
||||||
import org.asamk.signal.manager.api.Device;
|
import org.asamk.signal.manager.api.Device;
|
||||||
import org.asamk.signal.manager.api.Group;
|
import org.asamk.signal.manager.api.Group;
|
||||||
|
@ -298,31 +297,34 @@ public class DbusManagerImpl implements Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendTypingMessage(
|
public SendMessageResults sendTypingMessage(
|
||||||
final TypingAction action, final Set<RecipientIdentifier> recipients
|
final TypingAction action, final Set<RecipientIdentifier> recipients
|
||||||
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
|
||||||
for (final var recipient : recipients) {
|
return handleMessage(recipients, numbers -> {
|
||||||
if (recipient instanceof RecipientIdentifier.Single) {
|
numbers.forEach(n -> signal.sendTyping(n, action == TypingAction.STOP));
|
||||||
signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(),
|
return 0L;
|
||||||
action == TypingAction.STOP);
|
}, () -> {
|
||||||
} else if (recipient instanceof RecipientIdentifier.Group) {
|
signal.sendTyping(signal.getSelfNumber(), action == TypingAction.STOP);
|
||||||
throw new UnsupportedOperationException();
|
return 0L;
|
||||||
}
|
}, groupId -> {
|
||||||
}
|
throw new UnsupportedOperationException();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendReadReceipt(
|
public SendMessageResults sendReadReceipt(
|
||||||
final RecipientIdentifier.Single sender, final List<Long> messageIds
|
final RecipientIdentifier.Single sender, final List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
signal.sendReadReceipt(sender.getIdentifier(), messageIds);
|
signal.sendReadReceipt(sender.getIdentifier(), messageIds);
|
||||||
|
return new SendMessageResults(0, Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendViewedReceipt(
|
public SendMessageResults sendViewedReceipt(
|
||||||
final RecipientIdentifier.Single sender, final List<Long> messageIds
|
final RecipientIdentifier.Single sender, final List<Long> messageIds
|
||||||
) throws IOException, UntrustedIdentityException {
|
) {
|
||||||
signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
|
signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
|
||||||
|
return new SendMessageResults(0, Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,7 +7,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.manager.NotMasterDeviceException;
|
import org.asamk.signal.manager.NotMasterDeviceException;
|
||||||
import org.asamk.signal.manager.StickerPackInvalidException;
|
import org.asamk.signal.manager.StickerPackInvalidException;
|
||||||
import org.asamk.signal.manager.UntrustedIdentityException;
|
|
||||||
import org.asamk.signal.manager.api.Identity;
|
import org.asamk.signal.manager.api.Identity;
|
||||||
import org.asamk.signal.manager.api.InactiveGroupLinkException;
|
import org.asamk.signal.manager.api.InactiveGroupLinkException;
|
||||||
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
|
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
|
||||||
|
@ -305,16 +304,15 @@ public class DbusSignalImpl implements Signal {
|
||||||
try {
|
try {
|
||||||
var recipients = new ArrayList<String>(1);
|
var recipients = new ArrayList<String>(1);
|
||||||
recipients.add(recipient);
|
recipients.add(recipient);
|
||||||
m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
|
final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
|
||||||
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
|
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
|
||||||
.map(RecipientIdentifier.class::cast)
|
.map(RecipientIdentifier.class::cast)
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()));
|
||||||
|
checkSendMessageResults(results.timestamp(), results.results());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new Error.Failure(e.getMessage());
|
throw new Error.Failure(e.getMessage());
|
||||||
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
|
||||||
throw new Error.GroupNotFound(e.getMessage());
|
throw new Error.GroupNotFound(e.getMessage());
|
||||||
} catch (UntrustedIdentityException e) {
|
|
||||||
throw new Error.UntrustedIdentity(e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,11 +321,11 @@ public class DbusSignalImpl implements Signal {
|
||||||
final String recipient, final List<Long> messageIds
|
final String recipient, final List<Long> messageIds
|
||||||
) throws Error.Failure, Error.UntrustedIdentity {
|
) throws Error.Failure, Error.UntrustedIdentity {
|
||||||
try {
|
try {
|
||||||
m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
|
final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
|
||||||
|
messageIds);
|
||||||
|
checkSendMessageResults(results.timestamp(), results.results());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new Error.Failure(e.getMessage());
|
throw new Error.Failure(e.getMessage());
|
||||||
} catch (UntrustedIdentityException e) {
|
|
||||||
throw new Error.UntrustedIdentity(e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,11 +334,11 @@ public class DbusSignalImpl implements Signal {
|
||||||
final String recipient, final List<Long> messageIds
|
final String recipient, final List<Long> messageIds
|
||||||
) throws Error.Failure, Error.UntrustedIdentity {
|
) throws Error.Failure, Error.UntrustedIdentity {
|
||||||
try {
|
try {
|
||||||
m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
|
final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
|
||||||
|
messageIds);
|
||||||
|
checkSendMessageResults(results.timestamp(), results.results());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new Error.Failure(e.getMessage());
|
throw new Error.Failure(e.getMessage());
|
||||||
} catch (UntrustedIdentityException e) {
|
|
||||||
throw new Error.UntrustedIdentity(e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue