X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/badbb55ef2151d7396c23be5a827c40712cd645a..91700ce995ae381dd97b246ea3ff11afb748e421:/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index dc7e743e..6895da63 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -16,6 +16,7 @@ */ package org.asamk.signal.manager; +import org.asamk.signal.manager.api.AlreadyReceivingException; import org.asamk.signal.manager.api.AttachmentInvalidException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; @@ -24,18 +25,22 @@ import org.asamk.signal.manager.api.Identity; import org.asamk.signal.manager.api.InactiveGroupLinkException; import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.InvalidStickerException; +import org.asamk.signal.manager.api.InvalidUsernameException; import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.PendingAdminApprovalException; import org.asamk.signal.manager.api.ReceiveConfig; +import org.asamk.signal.manager.api.Recipient; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; import org.asamk.signal.manager.api.SendMessageResult; import org.asamk.signal.manager.api.SendMessageResults; -import org.asamk.signal.manager.api.StickerPack; import org.asamk.signal.manager.api.StickerPackId; import org.asamk.signal.manager.api.StickerPackInvalidException; import org.asamk.signal.manager.api.StickerPackUrl; +import org.asamk.signal.manager.api.TextStyle; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.api.UpdateGroup; @@ -54,14 +59,15 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.identities.IdentityInfo; import org.asamk.signal.manager.storage.recipients.Profile; -import org.asamk.signal.manager.storage.recipients.Recipient; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack; import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; -import org.asamk.signal.manager.storage.stickers.Sticker; +import org.asamk.signal.manager.storage.stickers.StickerPack; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.KeyUtils; +import org.asamk.signal.manager.util.MimeUtils; import org.asamk.signal.manager.util.StickerUtils; +import org.signal.libsignal.usernames.BaseUsernameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalSessionLock; @@ -70,15 +76,20 @@ import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.push.ACI; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; +import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.signalservice.internal.util.Util; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -166,10 +177,12 @@ class ManagerImpl implements Manager { this.notifyAll(); } }); - disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(recipientId -> { - logger.trace("Archiving old sessions for {}", recipientId); - account.getSessionStore().archiveSessions(recipientId); - account.getSenderKeyStore().deleteSharedWith(recipientId); + disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(serviceId -> { + logger.trace("Archiving old sessions for {}", serviceId); + account.getAciSessionStore().archiveSessions(serviceId); + account.getPniSessionStore().archiveSessions(serviceId); + account.getSenderKeyStore().deleteSharedWith(serviceId); + final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId); final var profile = account.getProfileStore().getProfile(recipientId); if (profile != null) { account.getProfileStore() @@ -214,13 +227,14 @@ class ManagerImpl implements Manager { return numbers.stream().collect(Collectors.toMap(n -> n, n -> { final var number = canonicalizedNumbers.get(n); - final var aci = registeredUsers.get(number); - final var profile = aci == null + final var user = registeredUsers.get(number); + final var serviceId = user == null ? null : user.getServiceId(); + final var profile = serviceId == null ? null : context.getProfileHelper() - .getRecipientProfile(account.getRecipientResolver().resolveRecipient(aci)); + .getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId)); return new UserStatus(number.isEmpty() ? null : number, - aci == null ? null : aci.uuid(), + serviceId == null ? null : serviceId.uuid(), profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED); })); @@ -232,6 +246,7 @@ class ManagerImpl implements Manager { context.getAccountHelper().setDeviceName(deviceName); } context.getAccountHelper().updateAccountAttributes(); + context.getAccountHelper().checkWhoAmiI(); } @Override @@ -278,6 +293,24 @@ class ManagerImpl implements Manager { context.getSyncHelper().sendSyncFetchProfileMessage(); } + void refreshCurrentUsername() throws IOException, BaseUsernameException { + context.getAccountHelper().refreshCurrentUsername(); + } + + @Override + public String setUsername(final String username) throws IOException, InvalidUsernameException { + try { + return context.getAccountHelper().reserveUsername(username); + } catch (BaseUsernameException e) { + throw new InvalidUsernameException(e.getMessage() + " (" + e.getClass().getSimpleName() + ")", e); + } + } + + @Override + public void deleteUsername() throws IOException { + context.getAccountHelper().deleteUsername(); + } + @Override public void unregister() throws IOException { context.getAccountHelper().unregister(); @@ -382,7 +415,7 @@ class ManagerImpl implements Manager { @Override public Pair createGroup( - String name, Set members, File avatarFile + String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException { return context.getGroupHelper() .createGroup(name, @@ -428,12 +461,20 @@ class ManagerImpl implements Manager { @Override public Pair joinGroup( GroupInviteLinkUrl inviteLinkUrl - ) throws IOException, InactiveGroupLinkException { + ) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException { return context.getGroupHelper().joinGroup(inviteLinkUrl); } private SendMessageResults sendMessage( SignalServiceDataMessage.Builder messageBuilder, Set recipients + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { + return sendMessage(messageBuilder, recipients, Optional.empty()); + } + + private SendMessageResults sendMessage( + SignalServiceDataMessage.Builder messageBuilder, + Set recipients, + Optional editTargetTimestamp ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); long timestamp = System.currentTimeMillis(); @@ -442,17 +483,19 @@ class ManagerImpl implements Manager { if (recipient instanceof RecipientIdentifier.Single single) { try { final var recipientId = context.getRecipientHelper().resolveRecipient(single); - final var result = context.getSendHelper().sendMessage(messageBuilder, recipientId); + final var result = context.getSendHelper() + .sendMessage(messageBuilder, recipientId, editTargetTimestamp); results.put(recipient, List.of(toSendMessageResult(result))); } catch (UnregisteredRecipientException e) { results.put(recipient, List.of(SendMessageResult.unregisteredFailure(single.toPartialRecipientAddress()))); } } else if (recipient instanceof RecipientIdentifier.NoteToSelf) { - final var result = context.getSendHelper().sendSelfMessage(messageBuilder); + final var result = context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp); results.put(recipient, List.of(toSendMessageResult(result))); } else if (recipient instanceof RecipientIdentifier.Group group) { - final var result = context.getSendHelper().sendAsGroupMessage(messageBuilder, group.groupId()); + final var result = context.getSendHelper() + .sendAsGroupMessage(messageBuilder, group.groupId(), editTargetTimestamp); results.put(recipient, result.stream().map(this::toSendMessageResult).toList()); } } @@ -542,32 +585,54 @@ class ManagerImpl implements Manager { final var selfProfile = context.getProfileHelper().getSelfProfile(); if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) { logger.warn( - "No profile name set. When sending a message it's recommended to set a profile name wit the updateProfile command. This may become mandatory in the future."); + "No profile name set. When sending a message it's recommended to set a profile name with the updateProfile command. This may become mandatory in the future."); } final var messageBuilder = SignalServiceDataMessage.newBuilder(); applyMessage(messageBuilder, message); return sendMessage(messageBuilder, recipients); } + @Override + public SendMessageResults sendEditMessage( + Message message, Set recipients, long editTargetTimestamp + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { + final var messageBuilder = SignalServiceDataMessage.newBuilder(); + applyMessage(messageBuilder, message); + return sendMessage(messageBuilder, recipients, Optional.of(editTargetTimestamp)); + } + private void applyMessage( final SignalServiceDataMessage.Builder messageBuilder, final Message message ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { - messageBuilder.withBody(message.messageText()); + if (message.messageText().length() > 2000) { + final var messageBytes = message.messageText().getBytes(StandardCharsets.UTF_8); + final var textAttachment = AttachmentUtils.createAttachmentStream(new StreamDetails(new ByteArrayInputStream( + messageBytes), MimeUtils.LONG_TEXT, messageBytes.length), Optional.empty()); + messageBuilder.withBody(message.messageText().substring(0, 2000)); + messageBuilder.withAttachment(context.getAttachmentHelper().uploadAttachment(textAttachment)); + } else { + messageBuilder.withBody(message.messageText()); + } if (message.attachments().size() > 0) { messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments())); } if (message.mentions().size() > 0) { messageBuilder.withMentions(resolveMentions(message.mentions())); } + if (message.textStyles().size() > 0) { + messageBuilder.withBodyRanges(message.textStyles().stream().map(TextStyle::toBodyRange).toList()); + } if (message.quote().isPresent()) { final var quote = message.quote().get(); messageBuilder.withQuote(new SignalServiceDataMessage.Quote(quote.timestamp(), context.getRecipientHelper() - .resolveSignalServiceAddress(context.getRecipientHelper().resolveRecipient(quote.author())), + .resolveSignalServiceAddress(context.getRecipientHelper().resolveRecipient(quote.author())) + .getServiceId(), quote.message(), List.of(), resolveMentions(quote.mentions()), - SignalServiceDataMessage.Quote.Type.NORMAL)); + SignalServiceDataMessage.Quote.Type.NORMAL, + quote.textStyles().stream().map(TextStyle::toBodyRange).toList())); } if (message.sticker().isPresent()) { final var sticker = message.sticker().get(); @@ -578,7 +643,7 @@ class ManagerImpl implements Manager { if (stickerPack == null) { throw new InvalidStickerException("Sticker pack not found"); } - final var manifest = context.getStickerHelper().getOrRetrieveStickerPack(packId, stickerPack.getPackKey()); + final var manifest = context.getStickerHelper().getOrRetrieveStickerPack(packId, stickerPack.packKey()); if (manifest.stickers().size() <= stickerId) { throw new InvalidStickerException("Sticker id not part of this pack"); } @@ -588,7 +653,7 @@ class ManagerImpl implements Manager { throw new InvalidStickerException("Missing local sticker file"); } messageBuilder.withSticker(new SignalServiceDataMessage.Sticker(packId.serialize(), - stickerPack.getPackKey(), + stickerPack.packKey(), stickerId, manifestSticker.emoji(), AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()))); @@ -606,6 +671,14 @@ class ManagerImpl implements Manager { } messageBuilder.withPreviews(previews); } + if (message.storyReply().isPresent()) { + final var storyReply = message.storyReply().get(); + final var authorServiceId = context.getRecipientHelper() + .resolveSignalServiceAddress(context.getRecipientHelper().resolveRecipient(storyReply.author())) + .getServiceId(); + messageBuilder.withStoryContext(new SignalServiceDataMessage.StoryContext(authorServiceId, + storyReply.timestamp())); + } } private ArrayList resolveMentions(final List mentionList) throws UnregisteredRecipientException { @@ -626,10 +699,17 @@ class ManagerImpl implements Manager { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); for (final var recipient : recipients) { - if (recipient instanceof RecipientIdentifier.Single r) { + if (recipient instanceof RecipientIdentifier.Uuid u) { + account.getMessageSendLogStore() + .deleteEntryForRecipientNonGroup(targetSentTimestamp, ServiceId.from(u.uuid())); + } else if (recipient instanceof RecipientIdentifier.Single r) { try { final var recipientId = context.getRecipientHelper().resolveRecipient(r); - account.getMessageSendLogStore().deleteEntryForRecipientNonGroup(targetSentTimestamp, recipientId); + final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); + if (address.serviceId().isPresent()) { + account.getMessageSendLogStore() + .deleteEntryForRecipientNonGroup(targetSentTimestamp, address.serviceId().get()); + } } catch (UnregisteredRecipientException ignored) { } } else if (recipient instanceof RecipientIdentifier.Group r) { @@ -645,14 +725,19 @@ class ManagerImpl implements Manager { boolean remove, RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, - Set recipients + Set recipients, + final boolean isStory ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException { var targetAuthorRecipientId = context.getRecipientHelper().resolveRecipient(targetAuthor); - var reaction = new SignalServiceDataMessage.Reaction(emoji, - remove, - context.getRecipientHelper().resolveSignalServiceAddress(targetAuthorRecipientId), - targetSentTimestamp); + final var authorServiceId = context.getRecipientHelper() + .resolveSignalServiceAddress(targetAuthorRecipientId) + .getServiceId(); + var reaction = new SignalServiceDataMessage.Reaction(emoji, remove, authorServiceId, targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); + if (isStory) { + messageBuilder.withStoryContext(new SignalServiceDataMessage.StoryContext(authorServiceId, + targetSentTimestamp)); + } return sendMessage(messageBuilder, recipients); } @@ -661,7 +746,7 @@ class ManagerImpl implements Manager { byte[] receipt, String note, RecipientIdentifier.Single recipient ) throws IOException { final var paymentNotification = new SignalServiceDataMessage.PaymentNotification(receipt, note); - final var payment = new SignalServiceDataMessage.Payment(paymentNotification); + final var payment = new SignalServiceDataMessage.Payment(paymentNotification, null); final var messageBuilder = SignalServiceDataMessage.newBuilder().withPayment(payment); try { return sendMessage(messageBuilder, Set.of(recipient)); @@ -687,20 +772,31 @@ class ManagerImpl implements Manager { } catch (UnregisteredRecipientException e) { continue; } - account.getSessionStore().deleteAllSessions(recipientId); + final var serviceId = context.getAccount() + .getRecipientAddressResolver() + .resolveRecipientAddress(recipientId) + .serviceId(); + if (serviceId.isPresent()) { + account.getAciSessionStore().deleteAllSessions(serviceId.get()); + } } } } @Override public void deleteRecipient(final RecipientIdentifier.Single recipient) { - account.removeRecipient(account.getRecipientResolver().resolveRecipient(recipient.toPartialRecipientAddress())); + final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient); + if (recipientIdOptional.isPresent()) { + account.removeRecipient(recipientIdOptional.get()); + } } @Override public void deleteContact(final RecipientIdentifier.Single recipient) { - account.getContactStore() - .deleteContact(account.getRecipientResolver().resolveRecipient(recipient.toPartialRecipientAddress())); + final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient); + if (recipientIdOptional.isPresent()) { + account.getContactStore().deleteContact(recipientIdOptional.get()); + } } @Override @@ -794,21 +890,21 @@ class ManagerImpl implements Manager { var packIdString = messageSender.uploadStickerManifest(manifest, packKey); var packId = StickerPackId.deserialize(Hex.fromStringCondensed(packIdString)); - var sticker = new Sticker(packId, packKey); - account.getStickerStore().updateSticker(sticker); + var sticker = new StickerPack(packId, packKey); + account.getStickerStore().addStickerPack(sticker); return new StickerPackUrl(packId, packKey); } @Override - public List getStickerPacks() { + public List getStickerPacks() { final var stickerPackStore = context.getStickerPackStore(); return account.getStickerStore().getStickerPacks().stream().map(pack -> { - if (stickerPackStore.existsStickerPack(pack.getPackId())) { + if (stickerPackStore.existsStickerPack(pack.packId())) { try { - final var manifest = stickerPackStore.retrieveManifest(pack.getPackId()); - return new StickerPack(pack.getPackId(), - new StickerPackUrl(pack.getPackId(), pack.getPackKey()), + final var manifest = stickerPackStore.retrieveManifest(pack.packId()); + return new org.asamk.signal.manager.api.StickerPack(pack.packId(), + new StickerPackUrl(pack.packId(), pack.packKey()), pack.isInstalled(), manifest.title(), manifest.author(), @@ -819,7 +915,7 @@ class ManagerImpl implements Manager { } } - return new StickerPack(pack.getPackId(), pack.getPackKey(), pack.isInstalled()); + return new org.asamk.signal.manager.api.StickerPack(pack.packId(), pack.packKey(), pack.isInstalled()); }).toList(); } @@ -830,16 +926,11 @@ class ManagerImpl implements Manager { } void retrieveRemoteStorage() throws IOException { - if (account.getStorageKey() != null) { - context.getStorageHelper().readDataFromStorage(); - } + context.getStorageHelper().readDataFromStorage(); } @Override public void addReceiveHandler(final ReceiveMessageHandler handler, final boolean isWeakListener) { - if (isReceivingSynchronous) { - throw new IllegalStateException("Already receiving message synchronously."); - } synchronized (messageHandlers) { if (isWeakListener) { weakHandlers.add(handler); @@ -853,22 +944,12 @@ class ManagerImpl implements Manager { private static final AtomicInteger threadNumber = new AtomicInteger(0); private void startReceiveThreadIfRequired() { - if (receiveThread != null) { + if (receiveThread != null || isReceivingSynchronous) { return; } receiveThread = new Thread(() -> { logger.debug("Starting receiving messages"); - context.getReceiveHelper().receiveMessagesContinuously((envelope, e) -> { - synchronized (messageHandlers) { - Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> { - try { - h.handleMessage(envelope, e); - } catch (Throwable ex) { - logger.warn("Message handler failed, ignoring", ex); - } - }); - } - }); + context.getReceiveHelper().receiveMessagesContinuously(this::passReceivedMessageToHandlers); logger.debug("Finished receiving messages"); synchronized (messageHandlers) { receiveThread = null; @@ -885,6 +966,18 @@ class ManagerImpl implements Manager { receiveThread.start(); } + private void passReceivedMessageToHandlers(MessageEnvelope envelope, Throwable e) { + synchronized (messageHandlers) { + Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> { + try { + h.handleMessage(envelope, e); + } catch (Throwable ex) { + logger.warn("Message handler failed, ignoring", ex); + } + }); + } + } + @Override public void removeReceiveHandler(final ReceiveMessageHandler handler) { final Thread thread; @@ -923,28 +1016,35 @@ class ManagerImpl implements Manager { } @Override - public void receiveMessages(Duration timeout, ReceiveMessageHandler handler) throws IOException { - receiveMessages(timeout, true, handler); - } - - @Override - public void receiveMessages(ReceiveMessageHandler handler) throws IOException { - receiveMessages(Duration.ofMinutes(1), false, handler); + public void receiveMessages( + Optional timeout, Optional maxMessages, ReceiveMessageHandler handler + ) throws IOException, AlreadyReceivingException { + receiveMessages(timeout.orElse(Duration.ofMinutes(1)), timeout.isPresent(), maxMessages.orElse(null), handler); } private void receiveMessages( - Duration timeout, boolean returnOnTimeout, ReceiveMessageHandler handler - ) throws IOException { - if (isReceiving()) { - throw new IllegalStateException("Already receiving message."); + Duration timeout, boolean returnOnTimeout, Integer maxMessages, ReceiveMessageHandler handler + ) throws IOException, AlreadyReceivingException { + synchronized (messageHandlers) { + if (isReceiving()) { + throw new AlreadyReceivingException("Already receiving message."); + } + isReceivingSynchronous = true; + receiveThread = Thread.currentThread(); } - isReceivingSynchronous = true; - receiveThread = Thread.currentThread(); try { - context.getReceiveHelper().receiveMessages(timeout, returnOnTimeout, handler); + context.getReceiveHelper().receiveMessages(timeout, returnOnTimeout, maxMessages, (envelope, e) -> { + passReceivedMessageToHandlers(envelope, e); + handler.handleMessage(envelope, e); + }); } finally { - receiveThread = null; - isReceivingSynchronous = false; + synchronized (messageHandlers) { + receiveThread = null; + isReceivingSynchronous = false; + if (messageHandlers.size() > 0) { + startReceiveThreadIfRequired(); + } + } } } @@ -988,9 +1088,21 @@ class ManagerImpl implements Manager { return null; } }).filter(Objects::nonNull).collect(Collectors.toSet()); + if (!recipients.isEmpty() && recipientIds.isEmpty()) { + return List.of(); + } // refresh profiles of explicitly given recipients context.getProfileHelper().refreshRecipientProfiles(recipientIds); - return account.getRecipientStore().getRecipients(onlyContacts, blocked, recipientIds, name); + return account.getRecipientStore() + .getRecipients(onlyContacts, blocked, recipientIds, name) + .stream() + .map(s -> new Recipient(s.getRecipientId(), + s.getAddress().toApiRecipientAddress(), + s.getContact(), + s.getProfileKey(), + s.getExpiringProfileKeyCredential(), + s.getProfile())) + .toList(); } @Override @@ -1031,27 +1143,32 @@ class ManagerImpl implements Manager { } final var address = account.getRecipientAddressResolver() - .resolveRecipientAddress(identityInfo.getRecipientId()); + .resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(identityInfo.getServiceId())); final var scannableFingerprint = context.getIdentityHelper() - .computeSafetyNumberForScanning(identityInfo.getRecipientId(), identityInfo.getIdentityKey()); - return new Identity(address, + .computeSafetyNumberForScanning(identityInfo.getServiceId(), identityInfo.getIdentityKey()); + return new Identity(address.toApiRecipientAddress(), identityInfo.getIdentityKey(), context.getIdentityHelper() - .computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()), + .computeSafetyNumber(identityInfo.getServiceId(), identityInfo.getIdentityKey()), scannableFingerprint == null ? null : scannableFingerprint.getSerialized(), identityInfo.getTrustLevel(), - identityInfo.getDateAdded()); + identityInfo.getDateAddedTimestamp()); } @Override public List getIdentities(RecipientIdentifier.Single recipient) { - IdentityInfo identity; + ServiceId serviceId; try { - identity = account.getIdentityKeyStore() - .getIdentity(context.getRecipientHelper().resolveRecipient(recipient)); + final var address = account.getRecipientAddressResolver() + .resolveRecipientAddress(context.getRecipientHelper().resolveRecipient(recipient)); + if (address.serviceId().isEmpty()) { + return List.of(); + } + serviceId = address.serviceId().get(); } catch (UnregisteredRecipientException e) { - identity = null; + return List.of(); } + final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId); return identity == null ? List.of() : List.of(toIdentity(identity)); } @@ -1108,6 +1225,11 @@ class ManagerImpl implements Manager { } } + @Override + public InputStream retrieveAttachment(final String id) throws IOException { + return context.getAttachmentHelper().retrieveAttachment(id).getStream(); + } + @Override public void close() { Thread thread;