import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Message;
+import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.api.SendGroupMessageResults;
+import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
.filter(s -> !s.isEmpty())
.collect(Collectors.toSet()));
- // Store numbers as recipients so we have the number/uuid association
- contactDetails.forEach((number, uuid) -> resolveRecipientTrusted(new SignalServiceAddress(uuid, number)));
-
return numbers.stream().collect(Collectors.toMap(n -> n, n -> {
final var number = canonicalizedNumbers.get(n);
final var uuid = contactDetails.get(number);
return account.getGroupStore().getGroups();
}
- public Pair<Long, List<SendMessageResult>> sendGroupMessage(
- Message message, GroupId groupId
- ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
- final var messageBuilder = createMessageBuilder();
- applyMessage(messageBuilder, message);
-
- return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
- }
-
- public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction(
- String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId
- ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException {
- var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
- var reaction = new SignalServiceDataMessage.Reaction(emoji,
- remove,
- resolveSignalServiceAddress(targetAuthorRecipientId),
- targetSentTimestamp);
- final var messageBuilder = createMessageBuilder().withReaction(reaction);
-
- return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
- }
-
- public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
- GroupId groupId, Set<String> groupAdmins
- ) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
+ public SendGroupMessageResults sendQuitGroupMessage(
+ GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
+ ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException {
var group = getGroupForUpdating(groupId);
if (group instanceof GroupInfoV1) {
return quitGroupV1((GroupInfoV1) group);
}
}
- private Pair<Long, List<SendMessageResult>> quitGroupV1(final GroupInfoV1 groupInfoV1) throws IOException {
+ private SendGroupMessageResults quitGroupV1(final GroupInfoV1 groupInfoV1) throws IOException {
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
.withId(groupInfoV1.getGroupId().serialize())
.build();
- var messageBuilder = createMessageBuilder().asGroupMessage(group);
+ var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
groupInfoV1.removeMember(account.getSelfRecipientId());
account.getGroupStore().updateGroup(groupInfoV1);
- return sendHelper.sendGroupMessage(messageBuilder.build(),
+ return sendGroupMessage(messageBuilder,
groupInfoV1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
}
- private Pair<Long, List<SendMessageResult>> quitGroupV2(
+ private SendGroupMessageResults quitGroupV2(
final GroupInfoV2 groupInfoV2, final Set<RecipientId> newAdmins
) throws LastGroupAdminException, IOException {
final var currentAdmins = groupInfoV2.getAdminMembers();
}
final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
- var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
account.getGroupStore().updateGroup(groupInfoV2);
- return sendHelper.sendGroupMessage(messageBuilder.build(),
+
+ var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
+ return sendGroupMessage(messageBuilder,
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
}
avatarStore.deleteGroupAvatar(groupId);
}
- public Pair<GroupId, List<SendMessageResult>> createGroup(
- String name, List<String> members, File avatarFile
- ) throws IOException, AttachmentInvalidException, InvalidNumberException {
- return createGroup(name, members == null ? null : getRecipientIds(members), avatarFile);
+ public Pair<GroupId, SendGroupMessageResults> createGroup(
+ String name, Set<RecipientIdentifier.Single> members, File avatarFile
+ ) throws IOException, AttachmentInvalidException {
+ return createGroupInternal(name, members == null ? null : getRecipientIds(members), avatarFile);
}
- private Pair<GroupId, List<SendMessageResult>> createGroup(
+ private Pair<GroupId, SendGroupMessageResults> createGroupInternal(
String name, Set<RecipientId> members, File avatarFile
) throws IOException, AttachmentInvalidException {
final var selfRecipientId = account.getSelfRecipientId();
members == null ? Set.of() : members,
avatarFile);
- SignalServiceDataMessage.Builder messageBuilder;
if (gv2Pair == null) {
// Failed to create v2 group, creating v1 group instead
var gv1 = new GroupInfoV1(GroupIdV1.createRandom());
gv1.addMembers(List.of(selfRecipientId));
final var result = updateGroupV1(gv1, name, members, avatarFile);
- return new Pair<>(gv1.getGroupId(), result.second());
+ return new Pair<>(gv1.getGroupId(), result);
}
final var gv2 = gv2Pair.first();
avatarStore.storeGroupAvatar(gv2.getGroupId(),
outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
}
- messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
+
account.getGroupStore().updateGroup(gv2);
- final var result = sendHelper.sendGroupMessage(messageBuilder.build(),
- gv2.getMembersIncludingPendingWithout(selfRecipientId));
- return new Pair<>(gv2.getGroupId(), result.second());
+ final var messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
+
+ final var result = sendGroupMessage(messageBuilder, gv2.getMembersIncludingPendingWithout(selfRecipientId));
+ return new Pair<>(gv2.getGroupId(), result);
}
- public Pair<Long, List<SendMessageResult>> updateGroup(
+ public SendGroupMessageResults updateGroup(
GroupId groupId,
String name,
String description,
- List<String> members,
- List<String> removeMembers,
- List<String> admins,
- List<String> removeAdmins,
+ Set<RecipientIdentifier.Single> members,
+ Set<RecipientIdentifier.Single> removeMembers,
+ Set<RecipientIdentifier.Single> admins,
+ Set<RecipientIdentifier.Single> removeAdmins,
boolean resetGroupLink,
GroupLinkState groupLinkState,
GroupPermission addMemberPermission,
File avatarFile,
Integer expirationTimer,
Boolean isAnnouncementGroup
- ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
- return updateGroup(groupId,
+ ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
+ return updateGroupInternal(groupId,
name,
description,
members == null ? null : getRecipientIds(members),
isAnnouncementGroup);
}
- private Pair<Long, List<SendMessageResult>> updateGroup(
+ private SendGroupMessageResults updateGroupInternal(
final GroupId groupId,
final String name,
final String description,
return result;
}
- private Pair<Long, List<SendMessageResult>> updateGroupV1(
+ private SendGroupMessageResults updateGroupV1(
final GroupInfoV1 gv1, final String name, final Set<RecipientId> members, final File avatarFile
) throws IOException, AttachmentInvalidException {
updateGroupV1Details(gv1, name, members, avatarFile);
- var messageBuilder = getGroupUpdateMessageBuilder(gv1);
account.getGroupStore().updateGroup(gv1);
- return sendHelper.sendGroupMessage(messageBuilder.build(),
- gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
+ var messageBuilder = getGroupUpdateMessageBuilder(gv1);
+ return sendGroupMessage(messageBuilder, gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
}
private void updateGroupV1Details(
}
}
- private Pair<Long, List<SendMessageResult>> updateGroupV2(
+ private SendGroupMessageResults updateGroupV2(
final GroupInfoV2 group,
final String name,
final String description,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
) throws IOException {
- Pair<Long, List<SendMessageResult>> result = null;
+ SendGroupMessageResults result = null;
if (group.isPendingMember(account.getSelfRecipientId())) {
var groupGroupChangePair = groupV2Helper.acceptInvite(group);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
return result;
}
- public Pair<GroupId, List<SendMessageResult>> joinGroup(
+ public Pair<GroupId, SendGroupMessageResults> joinGroup(
GroupInviteLinkUrl inviteLinkUrl
) throws IOException, GroupLinkNotActiveException {
final var groupJoinInfo = groupV2Helper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
if (group.getGroup() == null) {
// Only requested member, can't send update to group members
- return new Pair<>(group.getGroupId(), List.of());
+ return new Pair<>(group.getGroupId(), new SendGroupMessageResults(0, List.of()));
}
final var result = sendUpdateGroupV2Message(group, group.getGroup(), groupChange);
- return new Pair<>(group.getGroupId(), result.second());
+ return new Pair<>(group.getGroupId(), result);
}
- private Pair<Long, List<SendMessageResult>> sendUpdateGroupV2Message(
+ private SendGroupMessageResults sendUpdateGroupV2Message(
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
) throws IOException {
final var selfRecipientId = account.getSelfRecipientId();
final var members = group.getMembersIncludingPendingWithout(selfRecipientId);
group.setGroup(newDecryptedGroup, this::resolveRecipient);
members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
+ account.getGroupStore().updateGroup(group);
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
- account.getGroupStore().updateGroup(group);
- return sendHelper.sendGroupMessage(messageBuilder.build(), members);
+ return sendGroupMessage(messageBuilder, members);
+ }
+
+ public SendMessageResults sendMessage(
+ SignalServiceDataMessage.Builder messageBuilder, Set<RecipientIdentifier> recipients
+ ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
+ var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
+ long timestamp = System.currentTimeMillis();
+ messageBuilder.withTimestamp(timestamp);
+ for (final var recipient : recipients) {
+ if (recipient instanceof RecipientIdentifier.Single) {
+ final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
+ final var result = sendHelper.sendMessage(messageBuilder, recipientId);
+ results.put(recipient, List.of(result));
+ } else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
+ final var result = sendHelper.sendSelfMessage(messageBuilder);
+ results.put(recipient, List.of(result));
+ } else if (recipient instanceof RecipientIdentifier.Group) {
+ final var groupId = ((RecipientIdentifier.Group) recipient).groupId;
+ final var result = sendHelper.sendAsGroupMessage(messageBuilder, groupId);
+ results.put(recipient, result);
+ }
+ }
+ return new SendMessageResults(timestamp, results);
+ }
+
+ public void sendTypingMessage(
+ SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients
+ ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException {
+ final var timestamp = System.currentTimeMillis();
+ for (var recipient : recipients) {
+ if (recipient instanceof RecipientIdentifier.Single) {
+ final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent());
+ final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
+ sendHelper.sendTypingMessage(message, recipientId);
+ } else if (recipient instanceof RecipientIdentifier.Group) {
+ final var groupId = ((RecipientIdentifier.Group) recipient).groupId;
+ final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize()));
+ sendHelper.sendGroupTypingMessage(message, groupId);
+ }
+ }
+ }
+
+ private SendGroupMessageResults sendGroupMessage(
+ final SignalServiceDataMessage.Builder messageBuilder, final Set<RecipientId> members
+ ) throws IOException {
+ final var timestamp = System.currentTimeMillis();
+ messageBuilder.withTimestamp(timestamp);
+ final var results = sendHelper.sendGroupMessage(messageBuilder.build(), members);
+ return new SendGroupMessageResults(timestamp, results);
}
private static int currentTimeDays() {
}
}
- Pair<Long, List<SendMessageResult>> sendGroupInfoMessage(
+ SendGroupMessageResults sendGroupInfoMessage(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
GroupInfoV1 g;
var messageBuilder = getGroupUpdateMessageBuilder(g);
// Send group message only to the recipient who requested it
- return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(recipientId));
+ return sendGroupMessage(messageBuilder, Set.of(recipientId));
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
throw new AttachmentInvalidException(g.getGroupId().toBase64(), e);
}
- return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime());
+ return SignalServiceDataMessage.newBuilder()
+ .asGroupMessage(group.build())
+ .withExpiration(g.getMessageExpirationTime());
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
var group = SignalServiceGroupV2.newBuilder(g.getMasterKey())
.withRevision(g.getGroup().getRevision())
.withSignedGroupChange(signedGroupChange);
- return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime());
+ return SignalServiceDataMessage.newBuilder()
+ .asGroupMessage(group.build())
+ .withExpiration(g.getMessageExpirationTime());
}
- Pair<Long, List<SendMessageResult>> sendGroupInfoRequest(
+ SendGroupMessageResults sendGroupInfoRequest(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException {
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize());
- var messageBuilder = createMessageBuilder().asGroupMessage(group.build());
+ var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group.build());
// Send group info request message to the recipient who sent us a message with this groupId
- return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(resolveRecipient(recipient)));
+ return sendGroupMessage(messageBuilder, Set.of(resolveRecipient(recipient)));
}
public void sendReadReceipt(
- String sender, List<Long> messageIds
- ) throws IOException, UntrustedIdentityException, InvalidNumberException {
+ RecipientIdentifier.Single sender, List<Long> messageIds
+ ) throws IOException, UntrustedIdentityException {
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
messageIds,
System.currentTimeMillis());
- sendHelper.sendReceiptMessage(receiptMessage, canonicalizeAndResolveRecipient(sender));
+ sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
}
public void sendViewedReceipt(
- String sender, List<Long> messageIds
- ) throws IOException, UntrustedIdentityException, InvalidNumberException {
+ RecipientIdentifier.Single sender, List<Long> messageIds
+ ) throws IOException, UntrustedIdentityException {
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
messageIds,
System.currentTimeMillis());
- sendHelper.sendReceiptMessage(receiptMessage, canonicalizeAndResolveRecipient(sender));
+ sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
}
void sendDeliveryReceipt(
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(remoteAddress));
}
- public Pair<Long, List<SendMessageResult>> sendMessage(
- Message message, List<String> recipients
- ) throws IOException, AttachmentInvalidException, InvalidNumberException {
- final var messageBuilder = createMessageBuilder();
+ public SendMessageResults sendMessage(
+ Message message, Set<RecipientIdentifier> recipients
+ ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException {
+ final var messageBuilder = SignalServiceDataMessage.newBuilder();
applyMessage(messageBuilder, message);
- return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
+ return sendMessage(messageBuilder, recipients);
}
private void applyMessage(
}
}
- public Pair<Long, SendMessageResult> sendSelfMessage(final Message message) throws IOException, AttachmentInvalidException {
- final var messageBuilder = createMessageBuilder();
- applyMessage(messageBuilder, message);
- return sendHelper.sendSelfMessage(messageBuilder);
- }
-
- public Pair<Long, List<SendMessageResult>> sendRemoteDeleteMessage(
- long targetSentTimestamp, List<String> recipients
- ) throws IOException, InvalidNumberException {
- var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
- final var messageBuilder = createMessageBuilder().withRemoteDelete(delete);
- return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
- }
-
- public Pair<Long, List<SendMessageResult>> sendGroupRemoteDeleteMessage(
- long targetSentTimestamp, GroupId groupId
+ public SendMessageResults sendRemoteDeleteMessage(
+ long targetSentTimestamp, Set<RecipientIdentifier> recipients
) throws IOException, NotAGroupMemberException, GroupNotFoundException {
var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
- final var messageBuilder = createMessageBuilder().withRemoteDelete(delete);
- return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete);
+ return sendMessage(messageBuilder, recipients);
}
- public Pair<Long, List<SendMessageResult>> sendMessageReaction(
- String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List<String> recipients
- ) throws IOException, InvalidNumberException {
- var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
+ public SendMessageResults sendMessageReaction(
+ String emoji,
+ boolean remove,
+ RecipientIdentifier.Single targetAuthor,
+ long targetSentTimestamp,
+ Set<RecipientIdentifier> recipients
+ ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
+ var targetAuthorRecipientId = resolveRecipient(targetAuthor);
var reaction = new SignalServiceDataMessage.Reaction(emoji,
remove,
resolveSignalServiceAddress(targetAuthorRecipientId),
targetSentTimestamp);
- final var messageBuilder = createMessageBuilder().withReaction(reaction);
- return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
+ return sendMessage(messageBuilder, recipients);
}
- public Pair<Long, List<SendMessageResult>> sendEndSessionMessage(List<String> recipients) throws IOException, InvalidNumberException {
- var messageBuilder = createMessageBuilder().asEndSessionMessage();
+ public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException {
+ var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage();
- final var recipientIds = getRecipientIds(recipients);
try {
- return sendHelper.sendMessage(messageBuilder, recipientIds);
+ return sendMessage(messageBuilder,
+ recipients.stream().map(RecipientIdentifier.class::cast).collect(Collectors.toSet()));
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new AssertionError(e);
} finally {
- for (var recipientId : recipientIds) {
+ for (var recipient : recipients) {
+ final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
handleEndSession(recipientId);
}
}
}
}
- public void setContactName(String number, String name) throws InvalidNumberException, NotMasterDeviceException {
+ public void setContactName(
+ RecipientIdentifier.Single recipient, String name
+ ) throws NotMasterDeviceException {
if (!account.isMasterDevice()) {
throw new NotMasterDeviceException();
}
- final var recipientId = canonicalizeAndResolveRecipient(number);
+ final var recipientId = resolveRecipient(recipient);
var contact = account.getContactStore().getContact(recipientId);
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore().storeContact(recipientId, builder.withName(name).build());
}
public void setContactBlocked(
- String number, boolean blocked
- ) throws InvalidNumberException, NotMasterDeviceException {
+ RecipientIdentifier.Single recipient, boolean blocked
+ ) throws NotMasterDeviceException {
if (!account.isMasterDevice()) {
throw new NotMasterDeviceException();
}
- setContactBlocked(canonicalizeAndResolveRecipient(number), blocked);
+ setContactBlocked(resolveRecipient(recipient), blocked);
}
private void setContactBlocked(RecipientId recipientId, boolean blocked) {
.storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build());
}
- private void sendExpirationTimerUpdate(RecipientId recipientId) throws IOException {
- final var messageBuilder = createMessageBuilder().asExpirationUpdate();
- sendHelper.sendMessage(messageBuilder, Set.of(recipientId));
- }
-
/**
* Change the expiration timer for a contact
*/
public void setExpirationTimer(
- String number, int messageExpirationTimer
- ) throws IOException, InvalidNumberException {
- var recipientId = canonicalizeAndResolveRecipient(number);
+ RecipientIdentifier.Single recipient, int messageExpirationTimer
+ ) throws IOException {
+ var recipientId = resolveRecipient(recipient);
setExpirationTimer(recipientId, messageExpirationTimer);
- sendExpirationTimerUpdate(recipientId);
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
+ try {
+ sendMessage(messageBuilder, Set.of(recipient));
+ } catch (NotAGroupMemberException | GroupNotFoundException e) {
+ throw new AssertionError(e);
+ }
}
/**
}
private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
- final var messageBuilder = createMessageBuilder().asExpirationUpdate();
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
sendHelper.sendAsGroupMessage(messageBuilder, groupId);
}
* @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
* @return if successful, returns the URL to install the sticker pack in the signal app
*/
- public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
+ public URI uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
var manifest = StickerUtils.getSignalServiceStickerManifestUpload(path);
var messageSender = dependencies.getMessageSender();
"pack_id="
+ URLEncoder.encode(Hex.toStringCondensed(packId.serialize()), StandardCharsets.UTF_8)
+ "&pack_key="
- + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)).toString();
+ + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8));
} catch (URISyntaxException e) {
throw new AssertionError(e);
}
return certificate;
}
- private Set<RecipientId> getRecipientIds(Collection<String> numbers) throws InvalidNumberException {
- final var signalServiceAddresses = new HashSet<SignalServiceAddress>(numbers.size());
+ private Set<RecipientId> getRecipientIds(Collection<RecipientIdentifier.Single> recipients) {
+ final var signalServiceAddresses = new HashSet<SignalServiceAddress>(recipients.size());
final var addressesMissingUuid = new HashSet<SignalServiceAddress>();
- for (var number : numbers) {
- final var resolvedAddress = resolveSignalServiceAddress(canonicalizeAndResolveRecipient(number));
+ for (var number : recipients) {
+ final var resolvedAddress = resolveSignalServiceAddress(resolveRecipient(number));
if (resolvedAddress.getUuid().isPresent()) {
signalServiceAddresses.add(resolvedAddress);
} else {
}
private Map<String, UUID> getRegisteredUsers(final Set<String> numbers) throws IOException {
+ final Map<String, UUID> registeredUsers;
try {
- return dependencies.getAccountManager()
+ registeredUsers = dependencies.getAccountManager()
.getRegisteredUsers(ServiceConfig.getIasKeyStore(),
numbers,
serviceEnvironmentConfig.getCdsMrenclave());
} catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) {
throw new IOException(e);
}
- }
- public void sendTypingMessage(
- TypingAction action, Set<String> recipients
- ) throws IOException, UntrustedIdentityException, InvalidNumberException {
- final var timestamp = System.currentTimeMillis();
- var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent());
- sendHelper.sendTypingMessage(message, getRecipientIds(recipients));
- }
+ // Store numbers as recipients so we have the number/uuid association
+ registeredUsers.forEach((number, uuid) -> resolveRecipientTrusted(new SignalServiceAddress(uuid, number)));
- public void sendGroupTypingMessage(
- TypingAction action, GroupId groupId
- ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
- final var timestamp = System.currentTimeMillis();
- final var message = new SignalServiceTypingMessage(action.toSignalService(),
- timestamp,
- Optional.of(groupId.serialize()));
- sendHelper.sendGroupTypingMessage(message, groupId);
+ return registeredUsers;
}
- private SignalServiceDataMessage.Builder createMessageBuilder() {
- final var timestamp = System.currentTimeMillis();
-
- var messageBuilder = SignalServiceDataMessage.newBuilder();
- messageBuilder.withTimestamp(timestamp);
- return messageBuilder;
+ public void sendTypingMessage(
+ TypingAction action, Set<RecipientIdentifier> recipients
+ ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException {
+ sendTypingMessage(action.toSignalService(), recipients);
}
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException, InvalidMessageStructureException {
return false;
}
- public boolean isContactBlocked(final String identifier) throws InvalidNumberException {
- final var recipientId = canonicalizeAndResolveRecipient(identifier);
+ public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
+ final var recipientId = resolveRecipient(recipient);
return isContactBlocked(recipientId);
}
return account.getContactStore().getContacts();
}
- public String getContactOrProfileName(String number) throws InvalidNumberException {
- final var recipientId = canonicalizeAndResolveRecipient(number);
+ public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
+ final var recipientId = resolveRecipient(recipientIdentifier);
final var recipient = account.getRecipientStore().getRecipient(recipientId);
if (recipient == null) {
return null;
return account.getIdentityKeyStore().getIdentities();
}
- public List<IdentityInfo> getIdentities(String number) throws InvalidNumberException {
- final var identity = account.getIdentityKeyStore().getIdentity(canonicalizeAndResolveRecipient(number));
+ public List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient) {
+ final var identity = account.getIdentityKeyStore().getIdentity(resolveRecipient(recipient));
return identity == null ? List.of() : List.of(identity);
}
/**
* Trust this the identity with this fingerprint
*
- * @param name username of the identity
+ * @param recipient username of the identity
* @param fingerprint Fingerprint
*/
- public boolean trustIdentityVerified(String name, byte[] fingerprint) throws InvalidNumberException {
- var recipientId = canonicalizeAndResolveRecipient(name);
+ public boolean trustIdentityVerified(RecipientIdentifier.Single recipient, byte[] fingerprint) {
+ var recipientId = resolveRecipient(recipient);
return trustIdentity(recipientId,
identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
TrustLevel.TRUSTED_VERIFIED);
/**
* Trust this the identity with this safety number
*
- * @param name username of the identity
+ * @param recipient username of the identity
* @param safetyNumber Safety number
*/
- public boolean trustIdentityVerifiedSafetyNumber(String name, String safetyNumber) throws InvalidNumberException {
- var recipientId = canonicalizeAndResolveRecipient(name);
+ public boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, String safetyNumber) {
+ var recipientId = resolveRecipient(recipient);
var address = account.getRecipientStore().resolveServiceAddress(recipientId);
return trustIdentity(recipientId,
identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
/**
* Trust this the identity with this scannable safety number
*
- * @param name username of the identity
+ * @param recipient username of the identity
* @param safetyNumber Scannable safety number
*/
- public boolean trustIdentityVerifiedSafetyNumber(String name, byte[] safetyNumber) throws InvalidNumberException {
- var recipientId = canonicalizeAndResolveRecipient(name);
+ public boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, byte[] safetyNumber) {
+ var recipientId = resolveRecipient(recipient);
var address = account.getRecipientStore().resolveServiceAddress(recipientId);
return trustIdentity(recipientId, identityKey -> {
final var fingerprint = computeSafetyNumberFingerprint(address, identityKey);
/**
* Trust all keys of this identity without verification
*
- * @param name username of the identity
+ * @param recipient username of the identity
*/
- public boolean trustIdentityAllKeys(String name) throws InvalidNumberException {
- var recipientId = canonicalizeAndResolveRecipient(name);
+ public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
+ var recipientId = resolveRecipient(recipient);
return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
}
return account.getRecipientStore().resolveServiceAddress(recipientId);
}
- private RecipientId canonicalizeAndResolveRecipient(String identifier) throws InvalidNumberException {
- var canonicalizedNumber = UuidUtil.isUuid(identifier) ? identifier : canonicalizePhoneNumber(identifier);
-
- return resolveRecipient(canonicalizedNumber);
- }
-
private String canonicalizePhoneNumber(final String number) throws InvalidNumberException {
return PhoneNumberFormatter.formatNumber(number, account.getUsername());
}
return resolveRecipient(address);
}
+ private RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) {
+ final SignalServiceAddress address;
+ if (recipient instanceof RecipientIdentifier.Uuid) {
+ address = new SignalServiceAddress(((RecipientIdentifier.Uuid) recipient).uuid, null);
+ } else {
+ address = new SignalServiceAddress(null, ((RecipientIdentifier.Number) recipient).number);
+ }
+
+ return resolveRecipient(address);
+ }
+
public RecipientId resolveRecipient(SignalServiceAddress address) {
return account.getRecipientStore().resolveRecipient(address);
}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.asamk.signal.manager.groups.GroupId;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+
+import java.util.UUID;
+
+public abstract class RecipientIdentifier {
+
+ public static class NoteToSelf extends RecipientIdentifier {
+
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof NoteToSelf;
+ }
+
+ @Override
+ public int hashCode() {
+ return 5;
+ }
+ }
+
+ public abstract static class Single extends RecipientIdentifier {
+
+ public static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
+ return UuidUtil.isUuid(identifier)
+ ? new Uuid(UUID.fromString(identifier))
+ : new Number(PhoneNumberFormatter.formatNumber(identifier, localNumber));
+ }
+
+ public static Single fromAddress(SignalServiceAddress address) {
+ return address.getUuid().isPresent()
+ ? new Uuid(address.getUuid().get())
+ : new Number(address.getNumber().get());
+ }
+ }
+
+ public static class Uuid extends Single {
+
+ public final UUID uuid;
+
+ public Uuid(final UUID uuid) {
+ this.uuid = uuid;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Uuid uuid1 = (Uuid) o;
+
+ return uuid.equals(uuid1.uuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid.hashCode();
+ }
+ }
+
+ public static class Number extends Single {
+
+ public final String number;
+
+ public Number(final String number) {
+ this.number = number;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Number number1 = (Number) o;
+
+ return number.equals(number1.number);
+ }
+
+ @Override
+ public int hashCode() {
+ return number.hashCode();
+ }
+ }
+
+ public static class Group extends RecipientIdentifier {
+
+ public final GroupId groupId;
+
+ public Group(final GroupId groupId) {
+ this.groupId = groupId;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final Group group = (Group) o;
+
+ return groupId.equals(group.groupId);
+ }
+
+ @Override
+ public int hashCode() {
+ return groupId.hashCode();
+ }
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.whispersystems.signalservice.api.messages.SendMessageResult;
+
+import java.util.List;
+
+public class SendGroupMessageResults {
+
+ private final long timestamp;
+ private final List<SendMessageResult> results;
+
+ public SendGroupMessageResults(
+ final long timestamp, final List<SendMessageResult> results
+ ) {
+ this.timestamp = timestamp;
+ this.results = results;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public List<SendMessageResult> getResults() {
+ return results;
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.whispersystems.signalservice.api.messages.SendMessageResult;
+
+import java.util.List;
+import java.util.Map;
+
+public class SendMessageResults {
+
+ private final long timestamp;
+ private final Map<RecipientIdentifier, List<SendMessageResult>> results;
+
+ public SendMessageResults(
+ final long timestamp, final Map<RecipientIdentifier, List<SendMessageResult>> results
+ ) {
+ this.timestamp = timestamp;
+ this.results = results;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public Map<RecipientIdentifier, List<SendMessageResult>> getResults() {
+ return results;
+ }
+}
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
* Send a single message to one or multiple recipients.
* The message is extended with the current expiration timer for each recipient.
*/
- public Pair<Long, List<SendMessageResult>> sendMessage(
- SignalServiceDataMessage.Builder messageBuilder, Set<RecipientId> recipientIds
+ public SendMessageResult sendMessage(
+ final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
) throws IOException {
- // Send to all individually, so sync messages are sent correctly
+ final var contact = account.getContactStore().getContact(recipientId);
+ final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
+ messageBuilder.withExpiration(expirationTime);
messageBuilder.withProfileKey(account.getProfileKey().serialize());
- var results = new ArrayList<SendMessageResult>(recipientIds.size());
- long timestamp = 0;
- for (var recipientId : recipientIds) {
- final var contact = account.getContactStore().getContact(recipientId);
- final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
- messageBuilder.withExpiration(expirationTime);
-
- final var singleMessage = messageBuilder.build();
- timestamp = singleMessage.getTimestamp();
- final var result = sendMessage(singleMessage, recipientId);
- handlePossibleIdentityFailure(result);
- results.add(result);
- }
- return new Pair<>(timestamp, results);
+ final var message = messageBuilder.build();
+ final var result = sendMessage(message, recipientId);
+ handlePossibleIdentityFailure(result);
+ return result;
}
/**
* Send a group message to the given group
* The message is extended with the current expiration timer for the group and the group context.
*/
- public Pair<Long, List<SendMessageResult>> sendAsGroupMessage(
+ public List<SendMessageResult> sendAsGroupMessage(
SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
) throws IOException, GroupNotFoundException, NotAGroupMemberException {
final var g = getGroupForSending(groupId);
+ return sendAsGroupMessage(messageBuilder, g);
+ }
+
+ private List<SendMessageResult> sendAsGroupMessage(
+ final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g
+ ) throws IOException {
GroupUtils.setGroupContext(messageBuilder, g);
messageBuilder.withExpiration(g.getMessageExpirationTime());
* Send a complete group message to the given recipients (should be current/old/new members)
* This method should only be used for create/update/quit group messages.
*/
- public Pair<Long, List<SendMessageResult>> sendGroupMessage(
+ public List<SendMessageResult> sendGroupMessage(
final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
) throws IOException {
List<SendMessageResult> result = sendGroupMessageInternal(message, recipientIds);
handlePossibleIdentityFailure(r);
}
- return new Pair<>(message.getTimestamp(), result);
+ return result;
}
public void sendReceiptMessage(
}
}
- public Pair<Long, SendMessageResult> sendSelfMessage(
+ public SendMessageResult sendSelfMessage(
SignalServiceDataMessage.Builder messageBuilder
) throws IOException {
final var recipientId = account.getSelfRecipientId();
messageBuilder.withExpiration(expirationTime);
var message = messageBuilder.build();
- final var result = sendSelfMessage(message);
- return new Pair<>(message.getTimestamp(), result);
+ return sendSelfMessage(message);
}
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
}
public void sendTypingMessage(
- SignalServiceTypingMessage message, Set<RecipientId> recipientIds
+ SignalServiceTypingMessage message, RecipientId recipientId
) throws IOException, UntrustedIdentityException {
var messageSender = dependencies.getMessageSender();
- for (var recipientId : recipientIds) {
- final var address = addressResolver.resolveSignalServiceAddress(recipientId);
- try {
- 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);
- }
+ final var address = addressResolver.resolveSignalServiceAddress(recipientId);
+ try {
+ 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);
}
}
long sendGroupRemoteDeleteMessage(
long targetSentTimestamp, byte[] groupId
- ) throws Error.Failure, Error.GroupNotFound;
+ ) throws Error.Failure, Error.GroupNotFound, Error.InvalidGroupId;
long sendMessageReaction(
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, String recipient
long sendGroupMessage(
String message, List<String> attachments, byte[] groupId
- ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid;
+ ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid, Error.InvalidGroupId;
long sendGroupMessageReaction(
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId
- ) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber;
+ ) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber, Error.InvalidGroupId;
String getContactName(String number) throws Error.InvalidNumber;
void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber;
- void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound;
+ void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId;
List<byte[]> getGroupIds();
- String getGroupName(byte[] groupId);
+ String getGroupName(byte[] groupId) throws Error.InvalidGroupId;
- List<String> getGroupMembers(byte[] groupId);
+ List<String> getGroupMembers(byte[] groupId) throws Error.InvalidGroupId;
byte[] updateGroup(
byte[] groupId, String name, List<String> members, String avatar
- ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound;
+ ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
boolean isRegistered();
List<String> getContactNumber(final String name) throws Error.Failure;
- void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure;
+ void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidGroupId;
boolean isContactBlocked(final String number) throws Error.InvalidNumber;
- boolean isGroupBlocked(final byte[] groupId);
+ boolean isGroupBlocked(final byte[] groupId) throws Error.InvalidGroupId;
- boolean isMember(final byte[] groupId);
+ boolean isMember(final byte[] groupId) throws Error.InvalidGroupId;
- void joinGroup(final String groupLink) throws Error.Failure;
+ byte[] joinGroup(final String groupLink) throws Error.Failure;
class MessageReceived extends DBusSignal {
}
}
+ class InvalidGroupId extends DBusExecutionException {
+
+ public InvalidGroupId(final String message) {
+ super(message);
+ }
+ }
+
class InvalidNumber extends DBusExecutionException {
public InvalidNumber(final String message) {
package org.asamk.signal;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.util.DateUtils;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.util.ArrayList;
import java.util.Base64;
final PlainTextWriter writer, final SignalServiceDataMessage.Reaction reaction
) {
writer.println("Emoji: {}", reaction.getEmoji());
- writer.println("Target author: {}", formatContact(m.resolveSignalServiceAddress(reaction.getTargetAuthor())));
+ writer.println("Target author: {}", formatContact(reaction.getTargetAuthor()));
writer.println("Target timestamp: {}", DateUtils.formatTimestamp(reaction.getTargetSentTimestamp()));
writer.println("Is remove: {}", reaction.isRemove());
}
final PlainTextWriter writer, final SignalServiceDataMessage.Quote quote
) {
writer.println("Id: {}", quote.getId());
- writer.println("Author: {}", getLegacyIdentifier(m.resolveSignalServiceAddress(quote.getAuthor())));
+ writer.println("Author: {}", formatContact(quote.getAuthor()));
writer.println("Text: {}", quote.getText());
if (quote.getMentions() != null && quote.getMentions().size() > 0) {
writer.println("Mentions:");
}
private String formatContact(SignalServiceAddress address) {
+ address = m.resolveSignalServiceAddress(address);
final var number = getLegacyIdentifier(address);
- String name = null;
- try {
- name = m.getContactOrProfileName(number);
- } catch (InvalidNumberException ignored) {
- }
+ final var name = m.getContactOrProfileName(RecipientIdentifier.Single.fromAddress(address));
if (name == null || name.isEmpty()) {
return number;
} else {
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.util.CommandUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
public class BlockCommand implements JsonRpcLocalCommand {
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- for (var contactNumber : ns.<String>getList("contact")) {
+ final var contacts = ns.<String>getList("contact");
+ for (var contact : CommandUtil.getSingleRecipientIdentifiers(contacts, m.getUsername())) {
try {
- m.setContactBlocked(contactNumber, true);
- } catch (InvalidNumberException e) {
- logger.warn("Invalid number {}: {}", contactNumber, e.getMessage());
+ m.setContactBlocked(contact, true);
} catch (NotMasterDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
}
}
- if (ns.<String>getList("group-id") != null) {
- for (var groupIdString : ns.<String>getList("group-id")) {
+ final var groupIdStrings = ns.<String>getList("group-id");
+ if (groupIdStrings != null) {
+ for (var groupId : CommandUtil.getGroupIds(groupIdStrings)) {
try {
- var groupId = Util.decodeGroupId(groupIdString);
m.setGroupBlocked(groupId, true);
- } catch (GroupIdFormatException | GroupNotFoundException e) {
- logger.warn("Invalid group id {}: {}", groupIdString, e.getMessage());
+ } catch (GroupNotFoundException e) {
+ logger.warn("Group not found {}: {}", groupId.toBase64(), e.getMessage());
}
}
}
writer.println("Joined group \"{}\"", newGroupId.toBase64());
}
}
- handleSendMessageResults(results.second());
+ handleSendMessageResults(results.second().getResults());
} catch (GroupPatchNotAcceptedException e) {
throw new UserErrorException("Failed to join group, maybe already a member");
} catch (IOException e) {
import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException;
-import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
+import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.Hex;
import org.asamk.signal.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.util.Base64;
import java.util.List;
if (number == null) {
identities = m.getIdentities();
} else {
- try {
- identities = m.getIdentities(number);
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Invalid number: " + e.getMessage());
- }
+ identities = m.getIdentities(CommandUtil.getSingleRecipientIdentifier(number, m.getUsername()));
}
if (outputWriter instanceof PlainTextWriter) {
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.util.CommandUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
import static org.asamk.signal.util.ErrorUtils.handleSendMessageResults;
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- final GroupId groupId;
- try {
- groupId = Util.decodeGroupId(ns.getString("group-id"));
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
+ final var groupId = CommandUtil.getGroupId(ns.getString("group-id"));
- var groupAdmins = ns.<String>getList("admin");
+ var groupAdmins = CommandUtil.getSingleRecipientIdentifiers(ns.getList("admin"), m.getUsername());
try {
try {
- final var results = m.sendQuitGroupMessage(groupId,
- groupAdmins == null ? Set.of() : new HashSet<>(groupAdmins));
- final var timestamp = results.first();
+ final var results = m.sendQuitGroupMessage(groupId, groupAdmins);
+ final var timestamp = results.getTimestamp();
outputResult(outputWriter, timestamp);
- handleSendMessageResults(results.second());
+ handleSendMessageResults(results.getResults());
} catch (NotAGroupMemberException e) {
logger.info("User is not a group member");
}
throw new IOErrorException("Failed to send message: " + e.getMessage());
} catch (GroupNotFoundException e) {
throw new UserErrorException("Failed to send to group: " + e.getMessage());
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse admin number: " + e.getMessage());
} catch (LastGroupAdminException e) {
throw new UserErrorException("You need to specify a new admin with --admin: " + e.getMessage());
}
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.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
-import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.util.CommandUtil;
+import org.asamk.signal.util.ErrorUtils;
import org.freedesktop.dbus.errors.UnknownObject;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
-import java.util.List;
+import java.io.IOException;
import java.util.Map;
public class RemoteDeleteCommand implements DbusCommand, JsonRpcLocalCommand {
.required(true)
.type(long.class)
.help("Specify the timestamp of the message to delete.");
- subparser.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.");
+ 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("--note-to-self").action(Arguments.storeTrue());
+ }
+
+ @Override
+ public void handleCommand(
+ final Namespace ns, final Manager m, final OutputWriter outputWriter
+ ) throws CommandException {
+ final var isNoteToSelf = ns.getBoolean("note-to-self");
+ final var recipientStrings = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
+
+ final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m,
+ isNoteToSelf,
+ recipientStrings,
+ groupIdStrings);
+
+ final long targetTimestamp = ns.getLong("target-timestamp");
+
+ try {
+ final var results = m.sendRemoteDeleteMessage(targetTimestamp, recipientIdentifiers);
+ outputResult(outputWriter, results.getTimestamp());
+ ErrorUtils.handleSendMessageResults(results.getResults());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new UserErrorException(e.getMessage());
+ } catch (IOException e) {
+ throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
+ }
}
@Override
public void handleCommand(
final Namespace ns, final Signal signal, final OutputWriter outputWriter
) throws CommandException {
- final List<String> recipients = ns.getList("recipient");
- final var groupIdString = ns.getString("group-id");
+ final var recipients = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
final var noRecipients = recipients == null || recipients.isEmpty();
- if (noRecipients && groupIdString == null) {
+ final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
+ if (noRecipients && noGroups) {
throw new UserErrorException("No recipients given");
}
- if (!noRecipients && groupIdString != null) {
+ if (!noRecipients && !noGroups) {
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
}
final long targetTimestamp = ns.getLong("target-timestamp");
- byte[] groupId = null;
- if (groupIdString != null) {
- try {
- groupId = Util.decodeGroupId(groupIdString).serialize();
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
- }
-
try {
- long timestamp;
- if (groupId != null) {
- timestamp = signal.sendGroupRemoteDeleteMessage(targetTimestamp, groupId);
+ long timestamp = 0;
+ if (!noGroups) {
+ final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
+ for (final var groupId : groupIds) {
+ timestamp = signal.sendGroupRemoteDeleteMessage(targetTimestamp, groupId.serialize());
+ }
} else {
timestamp = signal.sendRemoteDeleteMessage(targetTimestamp, recipients);
}
}
}
- @Override
- public void handleCommand(
- final Namespace ns, final Manager m, final OutputWriter outputWriter
- ) throws CommandException {
- handleCommand(ns, new DbusSignalImpl(m, null), outputWriter);
- }
-
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
if (outputWriter instanceof PlainTextWriter) {
final var writer = (PlainTextWriter) outputWriter;
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
-import org.asamk.signal.dbus.DbusSignalImpl;
+import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
+import org.asamk.signal.manager.api.Message;
+import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.util.CommandUtil;
+import org.asamk.signal.util.ErrorUtils;
import org.asamk.signal.util.IOUtils;
-import org.asamk.signal.util.Util;
import org.freedesktop.dbus.errors.UnknownObject;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.slf4j.Logger;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
public class SendCommand implements DbusCommand, JsonRpcLocalCommand {
public void attachToSubparser(final Subparser subparser) {
subparser.help("Send a message to another user or group.");
subparser.addArgument("recipient").help("Specify the recipients' phone number.").nargs("*");
- final var mutuallyExclusiveGroup = subparser.addMutuallyExclusiveGroup();
- mutuallyExclusiveGroup.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.");
- mutuallyExclusiveGroup.addArgument("--note-to-self")
+ subparser.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.").nargs("*");
+ subparser.addArgument("--note-to-self")
.help("Send the message to self without notification.")
.action(Arguments.storeTrue());
.action(Arguments.storeTrue());
}
+ @Override
+ public void handleCommand(
+ final Namespace ns, final Manager m, final OutputWriter outputWriter
+ ) throws CommandException {
+ final var isNoteToSelf = ns.getBoolean("note-to-self");
+ final var recipientStrings = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
+
+ final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m,
+ isNoteToSelf,
+ recipientStrings,
+ groupIdStrings);
+
+ final var isEndSession = ns.getBoolean("end-session");
+ if (isEndSession) {
+ final var singleRecipients = recipientIdentifiers.stream()
+ .filter(r -> r instanceof RecipientIdentifier.Single)
+ .map(RecipientIdentifier.Single.class::cast)
+ .collect(Collectors.toSet());
+ if (singleRecipients.isEmpty()) {
+ throw new UserErrorException("No recipients given");
+ }
+
+ try {
+ m.sendEndSessionMessage(singleRecipients);
+ return;
+ } catch (IOException e) {
+ throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
+ }
+ }
+
+ var messageText = ns.getString("message");
+ if (messageText == null) {
+ try {
+ messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
+ } catch (IOException e) {
+ throw new UserErrorException("Failed to read message from stdin: " + e.getMessage());
+ }
+ }
+
+ List<String> attachments = ns.getList("attachment");
+ if (attachments == null) {
+ attachments = List.of();
+ }
+
+ try {
+ var results = m.sendMessage(new Message(messageText, attachments), recipientIdentifiers);
+ outputResult(outputWriter, results.getTimestamp());
+ ErrorUtils.handleSendMessageResults(results.getResults());
+ } catch (AttachmentInvalidException | IOException e) {
+ throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new UserErrorException(e.getMessage());
+ }
+ }
+
@Override
public void handleCommand(
final Namespace ns, final Signal signal, final OutputWriter outputWriter
) throws CommandException {
- final List<String> recipients = ns.getList("recipient");
+ final var recipients = ns.<String>getList("recipient");
final var isEndSession = ns.getBoolean("end-session");
- final var groupIdString = ns.getString("group-id");
+ final var groupIdStrings = ns.<String>getList("group-id");
final var isNoteToSelf = ns.getBoolean("note-to-self");
final var noRecipients = recipients == null || recipients.isEmpty();
- if ((noRecipients && isEndSession) || (noRecipients && groupIdString == null && !isNoteToSelf)) {
+ final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
+ if ((noRecipients && isEndSession) || (noRecipients && noGroups && !isNoteToSelf)) {
throw new UserErrorException("No recipients given");
}
- if (!noRecipients && groupIdString != null) {
+ if (!noRecipients && !noGroups) {
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
}
if (!noRecipients && isNoteToSelf) {
attachments = List.of();
}
- if (groupIdString != null) {
- byte[] groupId;
- try {
- groupId = Util.decodeGroupId(groupIdString).serialize();
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
+ if (!noGroups) {
+ final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
try {
- var timestamp = signal.sendGroupMessage(messageText, attachments, groupId);
+ long timestamp = 0;
+ for (final var groupId : groupIds) {
+ timestamp = signal.sendGroupMessage(messageText, attachments, groupId.serialize());
+ }
outputResult(outputWriter, timestamp);
return;
} catch (DBusExecutionException e) {
writer.write(Map.of("timestamp", timestamp));
}
}
-
- @Override
- public void handleCommand(
- final Namespace ns, final Manager m, final OutputWriter outputWriter
- ) throws CommandException {
- handleCommand(ns, new DbusSignalImpl(m, null), outputWriter);
- }
}
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
-import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.util.CommandUtil;
+import org.asamk.signal.util.ErrorUtils;
import org.freedesktop.dbus.errors.UnknownObject;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
-import java.util.List;
+import java.io.IOException;
import java.util.Map;
public class SendReactionCommand implements DbusCommand, JsonRpcLocalCommand {
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.help("Send reaction to a previously received or sent message.");
- subparser.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.");
+ 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("--note-to-self")
+ .help("Send the reaction to self without notification.")
+ .action(Arguments.storeTrue());
subparser.addArgument("-e", "--emoji")
.required(true)
.help("Specify the emoji, should be a single unicode grapheme cluster.");
subparser.addArgument("-r", "--remove").help("Remove a reaction.").action(Arguments.storeTrue());
}
+ @Override
+ public void handleCommand(
+ final Namespace ns, final Manager m, final OutputWriter outputWriter
+ ) throws CommandException {
+ final var isNoteToSelf = ns.getBoolean("note-to-self");
+ final var recipientStrings = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
+
+ final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m,
+ isNoteToSelf,
+ recipientStrings,
+ groupIdStrings);
+
+ final var emoji = ns.getString("emoji");
+ final var isRemove = ns.getBoolean("remove");
+ final var targetAuthor = ns.getString("target-author");
+ final var targetTimestamp = ns.getLong("target-timestamp");
+
+ try {
+ final var results = m.sendMessageReaction(emoji,
+ isRemove,
+ CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
+ targetTimestamp,
+ recipientIdentifiers);
+ outputResult(outputWriter, results.getTimestamp());
+ ErrorUtils.handleSendMessageResults(results.getResults());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new UserErrorException(e.getMessage());
+ } catch (IOException e) {
+ throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
+ }
+ }
+
@Override
public void handleCommand(
final Namespace ns, final Signal signal, final OutputWriter outputWriter
) throws CommandException {
- final List<String> recipients = ns.getList("recipient");
- final var groupIdString = ns.getString("group-id");
+ final var recipients = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
final var noRecipients = recipients == null || recipients.isEmpty();
- if (noRecipients && groupIdString == null) {
+ final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty();
+ if (noRecipients && noGroups) {
throw new UserErrorException("No recipients given");
}
- if (!noRecipients && groupIdString != null) {
+ if (!noRecipients && !noGroups) {
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
}
final var emoji = ns.getString("emoji");
- final boolean isRemove = ns.getBoolean("remove");
+ final var isRemove = ns.getBoolean("remove");
final var targetAuthor = ns.getString("target-author");
- final long targetTimestamp = ns.getLong("target-timestamp");
-
- byte[] groupId = null;
- if (groupIdString != null) {
- try {
- groupId = Util.decodeGroupId(groupIdString).serialize();
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
- }
+ final var targetTimestamp = ns.getLong("target-timestamp");
try {
- long timestamp;
- if (groupId != null) {
- timestamp = signal.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId);
+ long timestamp = 0;
+ if (!noGroups) {
+ final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
+ for (final var groupId : groupIds) {
+ timestamp = signal.sendGroupMessageReaction(emoji,
+ isRemove,
+ targetAuthor,
+ targetTimestamp,
+ groupId.serialize());
+ }
} else {
timestamp = signal.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, recipients);
}
}
}
- @Override
- public void handleCommand(
- final Namespace ns, final Manager m, final OutputWriter outputWriter
- ) throws CommandException {
- handleCommand(ns, new DbusSignalImpl(m, null), outputWriter);
- }
-
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
if (outputWriter instanceof PlainTextWriter) {
final var writer = (PlainTextWriter) outputWriter;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.util.CommandUtil;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- final var recipient = ns.getString("recipient");
+ final var recipientString = ns.getString("recipient");
+ final var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getUsername());
final var targetTimestamps = ns.<Long>getList("target-timestamp");
final var type = ns.getString("type");
}
} catch (IOException | UntrustedIdentityException e) {
throw new UserErrorException("Failed to send message: " + e.getMessage());
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Invalid number: " + e.getMessage());
}
}
}
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.TypingAction;
-import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.util.CommandUtil;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.IOException;
import java.util.HashSet;
public void attachToSubparser(final Subparser subparser) {
subparser.help(
"Send typing message to trigger a typing indicator for the recipient. Indicator will be shown for 15seconds unless a typing STOP message is sent first.");
- subparser.addArgument("-g", "--group-id", "--group").help("Specify the recipient group ID.");
+ 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("-s", "--stop").help("Send a typing STOP message.").action(Arguments.storeTrue());
}
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- final var recipients = ns.<String>getList("recipient");
- final var groupIdString = ns.getString("group-id");
+ final var recipientStrings = ns.<String>getList("recipient");
+ final var groupIdStrings = ns.<String>getList("group-id");
+ final var action = ns.getBoolean("stop") ? TypingAction.STOP : TypingAction.START;
- final var noRecipients = recipients == null || recipients.isEmpty();
- if (noRecipients && groupIdString == null) {
- throw new UserErrorException("No recipients given");
+ final var recipientIdentifiers = new HashSet<RecipientIdentifier>();
+ if (recipientStrings != null) {
+ final var localNumber = m.getUsername();
+ recipientIdentifiers.addAll(CommandUtil.getSingleRecipientIdentifiers(recipientStrings, localNumber));
}
- if (!noRecipients && groupIdString != null) {
- throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
+ if (groupIdStrings != null) {
+ recipientIdentifiers.addAll(CommandUtil.getGroupIdentifiers(groupIdStrings));
}
- final var action = ns.getBoolean("stop") ? TypingAction.STOP : TypingAction.START;
-
- GroupId groupId = null;
- if (groupIdString != null) {
- try {
- groupId = Util.decodeGroupId(groupIdString);
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
+ if (recipientIdentifiers.isEmpty()) {
+ throw new UserErrorException("No recipients given");
}
try {
- if (groupId != null) {
- m.sendGroupTypingMessage(action, groupId);
- } else {
- m.sendTypingMessage(action, new HashSet<>(recipients));
- }
+ m.sendTypingMessage(action, recipientIdentifiers);
} catch (IOException | UntrustedIdentityException e) {
throw new UserErrorException("Failed to send message: " + e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new UserErrorException("Failed to send to group: " + e.getMessage());
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Invalid number: " + e.getMessage());
}
}
}
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.Hex;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.util.Base64;
import java.util.Locale;
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- var number = ns.getString("number");
+ var recipentString = ns.getString("number");
+ var recipient = CommandUtil.getSingleRecipientIdentifier(recipentString, m.getUsername());
if (ns.getBoolean("trust-all-known-keys")) {
- boolean res;
- try {
- res = m.trustIdentityAllKeys(number);
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
- }
+ boolean res = m.trustIdentityAllKeys(recipient);
if (!res) {
throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct.");
}
throw new UserErrorException(
"Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
}
- boolean res;
- try {
- res = m.trustIdentityVerified(number, fingerprintBytes);
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
- }
+ boolean res = m.trustIdentityVerified(recipient, fingerprintBytes);
if (!res) {
throw new UserErrorException(
"Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
}
} else if (safetyNumber.length() == 60) {
- boolean res;
- try {
- res = m.trustIdentityVerifiedSafetyNumber(number, safetyNumber);
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
- }
+ boolean res = m.trustIdentityVerifiedSafetyNumber(recipient, safetyNumber);
if (!res) {
throw new UserErrorException(
"Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
throw new UserErrorException(
"Safety number has invalid format, either specify the old hex fingerprint or the new safety number");
}
- boolean res;
- try {
- res = m.trustIdentityVerifiedSafetyNumber(number, scannableSafetyNumber);
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
- }
+ boolean res = m.trustIdentityVerifiedSafetyNumber(recipient, scannableSafetyNumber);
if (!res) {
throw new UserErrorException(
"Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.util.CommandUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
public class UnblockCommand implements JsonRpcLocalCommand {
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- for (var contactNumber : ns.<String>getList("contact")) {
+ for (var contactNumber : CommandUtil.getSingleRecipientIdentifiers(ns.getList("contact"), m.getUsername())) {
try {
m.setContactBlocked(contactNumber, false);
- } catch (InvalidNumberException e) {
- logger.warn("Invalid number: {}", contactNumber);
} catch (NotMasterDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
}
}
- if (ns.<String>getList("group-id") != null) {
- for (var groupIdString : ns.<String>getList("group-id")) {
- try {
- var groupId = Util.decodeGroupId(groupIdString);
- m.setGroupBlocked(groupId, false);
- } catch (GroupIdFormatException e) {
- logger.warn("Invalid group id: {}", groupIdString);
- } catch (GroupNotFoundException e) {
- logger.warn("Unknown group id: {}", groupIdString);
- }
+ final var groupIdStrings = ns.<String>getList("group-id");
+ for (var groupId : CommandUtil.getGroupIds(groupIdStrings)) {
+ try {
+ m.setGroupBlocked(groupId, false);
+ } catch (GroupNotFoundException e) {
+ logger.warn("Unknown group id: {}", groupId);
}
}
}
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.asamk.signal.util.CommandUtil;
import java.io.IOException;
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- var number = ns.getString("number");
+ var recipientString = ns.getString("number");
+ var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getUsername());
try {
var expiration = ns.getInt("expiration");
if (expiration != null) {
- m.setExpirationTimer(number, expiration);
+ m.setExpirationTimer(recipient, expiration);
}
var name = ns.getString("name");
if (name != null) {
- m.setContactName(number, name);
+ m.setContactName(recipient, name);
}
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Invalid contact number: " + e.getMessage());
} catch (IOException e) {
throw new IOErrorException("Update contact error: " + e.getMessage());
} catch (NotMasterDeviceException e) {
import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
+import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.ErrorUtils;
-import org.asamk.signal.util.Util;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.File;
import java.io.IOException;
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- GroupId groupId = null;
final var groupIdString = ns.getString("group-id");
- if (groupIdString != null) {
- try {
- groupId = Util.decodeGroupId(groupIdString);
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
- }
+ var groupId = CommandUtil.getGroupId(groupIdString);
+
+ final var localNumber = m.getUsername();
var groupName = ns.getString("name");
var groupDescription = ns.getString("description");
- var groupMembers = ns.<String>getList("member");
- var groupRemoveMembers = ns.<String>getList("remove-member");
- var groupAdmins = ns.<String>getList("admin");
- var groupRemoveAdmins = ns.<String>getList("remove-admin");
+ var groupMembers = CommandUtil.getSingleRecipientIdentifiers(ns.getList("member"), localNumber);
+ var groupRemoveMembers = CommandUtil.getSingleRecipientIdentifiers(ns.getList("remove-member"), localNumber);
+ var groupAdmins = CommandUtil.getSingleRecipientIdentifiers(ns.getList("admin"), localNumber);
+ var groupRemoveAdmins = CommandUtil.getSingleRecipientIdentifiers(ns.getList("remove-admin"), localNumber);
var groupAvatar = ns.getString("avatar");
var groupResetLink = ns.getBoolean("reset-link");
var groupLinkState = getGroupLinkState(ns.getString("link"));
try {
boolean isNewGroup = false;
+ Long timestamp = null;
if (groupId == null) {
isNewGroup = true;
var results = m.createGroup(groupName,
groupMembers,
groupAvatar == null ? null : new File(groupAvatar));
- ErrorUtils.handleSendMessageResults(results.second());
+ timestamp = results.second().getTimestamp();
+ ErrorUtils.handleSendMessageResults(results.second().getResults());
groupId = results.first();
groupName = null;
groupMembers = null;
groupSendMessagesPermission == null
? null
: groupSendMessagesPermission == GroupPermission.ONLY_ADMINS);
- Long timestamp = null;
if (results != null) {
- timestamp = results.first();
- ErrorUtils.handleSendMessageResults(results.second());
+ timestamp = results.getTimestamp();
+ ErrorUtils.handleSendMessageResults(results.getResults());
}
outputResult(outputWriter, timestamp, isNewGroup ? groupId : null);
} catch (AttachmentInvalidException e) {
throw new UserErrorException("Failed to add avatar attachment for group\": " + e.getMessage());
- } catch (GroupNotFoundException e) {
- logger.warn("Unknown group id: {}", groupIdString);
- } catch (NotAGroupMemberException e) {
- logger.warn("You're not a group member");
- } catch (InvalidNumberException e) {
- throw new UserErrorException("Failed to parse member number: " + e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new UserErrorException(e.getMessage());
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
}
public void handleCommand(
final Namespace ns, final Signal signal, final OutputWriter outputWriter
) throws CommandException {
- byte[] groupId = null;
- if (ns.getString("group-id") != null) {
- try {
- groupId = Util.decodeGroupId(ns.getString("group-id")).serialize();
- } catch (GroupIdFormatException e) {
- throw new UserErrorException("Invalid group id: " + e.getMessage());
- }
- }
- if (groupId == null) {
- groupId = new byte[0];
- }
+ var groupId = CommandUtil.getGroupId(ns.getString("group-id"));
var groupName = ns.getString("name");
if (groupName == null) {
}
try {
- var newGroupId = signal.updateGroup(groupId, groupName, groupMembers, groupAvatar);
- if (groupId.length != newGroupId.length) {
+ var newGroupId = signal.updateGroup(groupId == null ? new byte[0] : groupId.serialize(),
+ groupName,
+ groupMembers,
+ groupAvatar);
+ if (groupId == null) {
outputResult(outputWriter, null, GroupId.unknownVersion(newGroupId));
}
} catch (Signal.Error.AttachmentInvalid e) {
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.api.Message;
+import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
return sendMessage(message, attachments, recipients);
}
- private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
- var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
-
- if (error == null) {
- return;
- }
-
- final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
-
- if (result.getIdentityFailure() != null) {
- throw new Error.UntrustedIdentity(message);
- } else {
- throw new Error.Failure(message);
- }
- }
-
- private static void checkSendMessageResults(
- long timestamp, List<SendMessageResult> results
- ) throws DBusExecutionException {
- if (results.size() == 1) {
- checkSendMessageResult(timestamp, results.get(0));
- return;
- }
-
- var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
- if (errors.size() == 0) {
- return;
- }
-
- var message = new StringBuilder();
- message.append(timestamp).append('\n');
- message.append("Failed to send (some) messages:\n");
- for (var error : errors) {
- message.append(error).append('\n');
- }
-
- throw new Error.Failure(message.toString());
- }
-
@Override
public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
try {
- final var results = m.sendMessage(new Message(message, attachments), recipients);
- checkSendMessageResults(results.first(), results.second());
- return results.first();
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
+ final var results = m.sendMessage(new Message(message, attachments),
+ getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ .map(RecipientIdentifier.class::cast)
+ .collect(Collectors.toSet()));
+
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new Error.GroupNotFound(e.getMessage());
}
}
final long targetSentTimestamp, final List<String> recipients
) {
try {
- final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, recipients);
- checkSendMessageResults(results.first(), results.second());
- return results.first();
+ final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
+ getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ .map(RecipientIdentifier.class::cast)
+ .collect(Collectors.toSet()));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new Error.GroupNotFound(e.getMessage());
}
}
final long targetSentTimestamp, final byte[] groupId
) {
try {
- final var results = m.sendGroupRemoteDeleteMessage(targetSentTimestamp, GroupId.unknownVersion(groupId));
- checkSendMessageResults(results.first(), results.second());
- return results.first();
+ final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
+ Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
final List<String> recipients
) {
try {
- final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
- checkSendMessageResults(results.first(), results.second());
- return results.first();
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
+ final var results = m.sendMessageReaction(emoji,
+ remove,
+ getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
+ targetSentTimestamp,
+ getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ .map(RecipientIdentifier.class::cast)
+ .collect(Collectors.toSet()));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new Error.GroupNotFound(e.getMessage());
}
}
final String message, final List<String> attachments
) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
try {
- final var results = m.sendSelfMessage(new Message(message, attachments));
- checkSendMessageResult(results.first(), results.second());
- return results.first();
+ final var results = m.sendMessage(new Message(message, attachments),
+ Set.of(new RecipientIdentifier.NoteToSelf()));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new Error.GroupNotFound(e.getMessage());
}
}
@Override
public void sendEndSessionMessage(final List<String> recipients) {
try {
- final var results = m.sendEndSessionMessage(recipients);
- checkSendMessageResults(results.first(), results.second());
+ final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getUsername()));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
try {
- var results = m.sendGroupMessage(new Message(message, attachments), GroupId.unknownVersion(groupId));
- checkSendMessageResults(results.first(), results.second());
- return results.first();
+ var results = m.sendMessage(new Message(message, attachments),
+ Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
final byte[] groupId
) {
try {
- final var results = m.sendGroupMessageReaction(emoji,
+ final var results = m.sendMessageReaction(emoji,
remove,
- targetAuthor,
+ getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
targetSentTimestamp,
- GroupId.unknownVersion(groupId));
- checkSendMessageResults(results.first(), results.second());
- return results.first();
+ Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
+ return results.getTimestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
}
// the profile name
@Override
public String getContactName(final String number) {
- try {
- return m.getContactOrProfileName(number);
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
- }
+ return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getUsername()));
}
@Override
public void setContactName(final String number, final String name) {
try {
- m.setContactName(number, name);
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
+ m.setContactName(getSingleRecipientIdentifier(number, m.getUsername()), name);
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
@Override
public void setContactBlocked(final String number, final boolean blocked) {
try {
- m.setContactBlocked(number, blocked);
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
+ m.setContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()), blocked);
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
@Override
public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
try {
- m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
+ m.setGroupBlocked(getGroupId(groupId), blocked);
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage());
}
@Override
public String getGroupName(final byte[] groupId) {
- var group = m.getGroup(GroupId.unknownVersion(groupId));
+ var group = m.getGroup(getGroupId(groupId));
if (group == null) {
return "";
} else {
@Override
public List<String> getGroupMembers(final byte[] groupId) {
- var group = m.getGroup(GroupId.unknownVersion(groupId));
+ var group = m.getGroup(getGroupId(groupId));
if (group == null) {
return List.of();
} else {
if (name.isEmpty()) {
name = null;
}
- if (members.isEmpty()) {
- members = null;
- }
if (avatar.isEmpty()) {
avatar = null;
}
+ final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getUsername());
if (groupId == null) {
- final var results = m.createGroup(name, members, avatar == null ? null : new File(avatar));
- checkSendMessageResults(0, results.second());
+ final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar));
+ checkSendMessageResults(results.second().getTimestamp(), results.second().getResults());
return results.first().serialize();
} else {
- final var results = m.updateGroup(GroupId.unknownVersion(groupId),
+ final var results = m.updateGroup(getGroupId(groupId),
name,
null,
- members,
+ memberIdentifiers,
null,
null,
null,
null,
null);
if (results != null) {
- checkSendMessageResults(results.first(), results.second());
+ checkSendMessageResults(results.getTimestamp(), results.getResults());
}
return groupId;
}
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
}
@Override
public void quitGroup(final byte[] groupId) {
- var group = GroupId.unknownVersion(groupId);
+ var group = getGroupId(groupId);
try {
m.sendQuitGroupMessage(group, Set.of());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (IOException | LastGroupAdminException e) {
throw new Error.Failure(e.getMessage());
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
- public void joinGroup(final String groupLink) {
+ public byte[] joinGroup(final String groupLink) {
try {
final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
if (linkUrl == null) {
throw new Error.Failure("Group link is invalid:");
}
- m.joinGroup(linkUrl);
+ final var result = m.joinGroup(linkUrl);
+ return result.first().serialize();
} catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
throw new Error.Failure("Group link is invalid: " + e.getMessage());
} catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
@Override
public boolean isContactBlocked(final String number) {
- try {
- return m.isContactBlocked(number);
- } catch (InvalidNumberException e) {
- throw new Error.InvalidNumber(e.getMessage());
- }
+ return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()));
}
@Override
public boolean isGroupBlocked(final byte[] groupId) {
- var group = m.getGroup(GroupId.unknownVersion(groupId));
+ var group = m.getGroup(getGroupId(groupId));
if (group == null) {
return false;
} else {
@Override
public boolean isMember(final byte[] groupId) {
- var group = m.getGroup(GroupId.unknownVersion(groupId));
+ var group = m.getGroup(getGroupId(groupId));
if (group == null) {
return false;
} else {
return group.isMember(m.getSelfRecipientId());
}
}
+
+ private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
+ var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
+
+ if (error == null) {
+ return;
+ }
+
+ final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
+
+ if (result.getIdentityFailure() != null) {
+ throw new Error.UntrustedIdentity(message);
+ } else {
+ throw new Error.Failure(message);
+ }
+ }
+
+ private static void checkSendMessageResults(
+ long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results
+ ) throws DBusExecutionException {
+ final var sendMessageResults = results.values().stream().findFirst();
+ if (results.size() == 1 && sendMessageResults.get().size() == 1) {
+ checkSendMessageResult(timestamp, sendMessageResults.get().stream().findFirst().get());
+ return;
+ }
+
+ var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
+ if (errors.size() == 0) {
+ return;
+ }
+
+ var message = new StringBuilder();
+ message.append(timestamp).append('\n');
+ message.append("Failed to send (some) messages:\n");
+ for (var error : errors) {
+ message.append(error).append('\n');
+ }
+
+ throw new Error.Failure(message.toString());
+ }
+
+ private static void checkSendMessageResults(
+ long timestamp, Collection<SendMessageResult> results
+ ) throws DBusExecutionException {
+ if (results.size() == 1) {
+ checkSendMessageResult(timestamp, results.stream().findFirst().get());
+ return;
+ }
+
+ var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
+ if (errors.size() == 0) {
+ return;
+ }
+
+ var message = new StringBuilder();
+ message.append(timestamp).append('\n');
+ message.append("Failed to send (some) messages:\n");
+ for (var error : errors) {
+ message.append(error).append('\n');
+ }
+
+ throw new Error.Failure(message.toString());
+ }
+
+ private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
+ final Collection<String> recipientStrings, final String localNumber
+ ) throws DBusExecutionException {
+ final var identifiers = new HashSet<RecipientIdentifier.Single>();
+ for (var recipientString : recipientStrings) {
+ identifiers.add(getSingleRecipientIdentifier(recipientString, localNumber));
+ }
+ return identifiers;
+ }
+
+ private static RecipientIdentifier.Single getSingleRecipientIdentifier(
+ final String recipientString, final String localNumber
+ ) throws DBusExecutionException {
+ try {
+ return RecipientIdentifier.Single.fromString(recipientString, localNumber);
+ } catch (InvalidNumberException e) {
+ throw new Error.InvalidNumber(e.getMessage());
+ }
+ }
+
+ private static GroupId getGroupId(byte[] groupId) throws DBusExecutionException {
+ try {
+ return GroupId.unknownVersion(groupId);
+ } catch (Throwable e) {
+ throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage());
+ }
+ }
}
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.RecipientIdentifier;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
}
String name;
try {
- name = m.getContactOrProfileName(this.source);
+ name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(this.source, m.getUsername()));
} catch (InvalidNumberException | NullPointerException e) {
name = null;
}
--- /dev/null
+package org.asamk.signal.util;
+
+import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.groups.GroupIdFormatException;
+import org.whispersystems.signalservice.api.util.InvalidNumberException;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CommandUtil {
+
+ private CommandUtil() {
+ }
+
+ public static Set<RecipientIdentifier> getRecipientIdentifiers(
+ final Manager m,
+ final boolean isNoteToSelf,
+ final List<String> recipientStrings,
+ final List<String> groupIdStrings
+ ) throws UserErrorException {
+ final var recipientIdentifiers = new HashSet<RecipientIdentifier>();
+ if (isNoteToSelf) {
+ recipientIdentifiers.add(new RecipientIdentifier.NoteToSelf());
+ }
+ if (recipientStrings != null) {
+ final var localNumber = m.getUsername();
+ recipientIdentifiers.addAll(CommandUtil.getSingleRecipientIdentifiers(recipientStrings, localNumber));
+ }
+ if (groupIdStrings != null) {
+ recipientIdentifiers.addAll(CommandUtil.getGroupIdentifiers(groupIdStrings));
+ }
+
+ if (recipientIdentifiers.isEmpty()) {
+ throw new UserErrorException("No recipients given");
+ }
+ return recipientIdentifiers;
+ }
+
+ public static Set<RecipientIdentifier.Group> getGroupIdentifiers(Collection<String> groupIdStrings) throws UserErrorException {
+ if (groupIdStrings == null) {
+ return Set.of();
+ }
+ final var groupIds = new HashSet<RecipientIdentifier.Group>();
+ for (final var groupIdString : groupIdStrings) {
+ groupIds.add(new RecipientIdentifier.Group(getGroupId(groupIdString)));
+ }
+ return groupIds;
+ }
+
+ public static Set<GroupId> getGroupIds(Collection<String> groupIdStrings) throws UserErrorException {
+ if (groupIdStrings == null) {
+ return Set.of();
+ }
+ final var groupIds = new HashSet<GroupId>();
+ for (final var groupIdString : groupIdStrings) {
+ groupIds.add(getGroupId(groupIdString));
+ }
+ return groupIds;
+ }
+
+ public static GroupId getGroupId(String groupId) throws UserErrorException {
+ if (groupId == null) {
+ return null;
+ }
+ try {
+ return GroupId.fromBase64(groupId);
+ } catch (GroupIdFormatException e) {
+ throw new UserErrorException("Invalid group id: " + e.getMessage());
+ }
+ }
+
+ public static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
+ final Collection<String> recipientStrings, final String localNumber
+ ) throws UserErrorException {
+ if (recipientStrings == null) {
+ return Set.of();
+ }
+ final var identifiers = new HashSet<RecipientIdentifier.Single>();
+ for (var recipientString : recipientStrings) {
+ identifiers.add(getSingleRecipientIdentifier(recipientString, localNumber));
+ }
+ return identifiers;
+ }
+
+ public static RecipientIdentifier.Single getSingleRecipientIdentifier(
+ final String recipientString, final String localNumber
+ ) throws UserErrorException {
+ try {
+ return RecipientIdentifier.Single.fromString(recipientString, localNumber);
+ } catch (InvalidNumberException e) {
+ throw new UserErrorException("Invalid phone number '" + recipientString + "': " + e.getMessage());
+ }
+ }
+}
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.manager.api.RecipientIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
}
public static void handleSendMessageResults(
- List<SendMessageResult> results
+ Map<RecipientIdentifier, List<SendMessageResult>> mapResults
+ ) throws CommandException {
+ List<String> errors = getErrorMessagesFromSendMessageResults(mapResults);
+ handleSendMessageResultErrors(errors);
+ }
+
+ public static void handleSendMessageResults(
+ Collection<SendMessageResult> results
) throws CommandException {
var errors = getErrorMessagesFromSendMessageResults(results);
handleSendMessageResultErrors(errors);
}
- public static List<String> getErrorMessagesFromSendMessageResults(List<SendMessageResult> results) {
+ public static List<String> getErrorMessagesFromSendMessageResults(final Map<RecipientIdentifier, List<SendMessageResult>> mapResults) {
+ return mapResults.values()
+ .stream()
+ .flatMap(results -> getErrorMessagesFromSendMessageResults(results).stream())
+ .collect(Collectors.toList());
+ }
+
+ public static List<String> getErrorMessagesFromSendMessageResults(Collection<SendMessageResult> results) {
var errors = new ArrayList<String>();
for (var result : results) {
var error = getErrorMessageFromSendMessageResult(result);
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
-import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
return f.toString();
}
- public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
- return GroupId.fromBase64(groupId);
- }
-
public static String getLegacyIdentifier(final SignalServiceAddress address) {
return address.getNumber().or(() -> address.getUuid().get().toString());
}