]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
Add mobile-coin-address to updateProfile command
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / ManagerImpl.java
index c7f1d22fa57fd70134beb6f5611da9d15def1500..96196f8ff8e6f3fc89039f4ab6bb424fd2cc8173 100644 (file)
@@ -38,6 +38,8 @@ import org.asamk.signal.manager.api.StickerPackUrl;
 import org.asamk.signal.manager.api.TypingAction;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.api.UpdateGroup;
+import org.asamk.signal.manager.api.UpdateProfile;
+import org.asamk.signal.manager.api.UserStatus;
 import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
@@ -50,9 +52,8 @@ import org.asamk.signal.manager.helper.Context;
 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.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+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;
@@ -66,6 +67,7 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 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.util.DeviceNameUtil;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
@@ -77,13 +79,14 @@ import java.io.IOException;
 import java.net.URI;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -140,10 +143,18 @@ class ManagerImpl implements Manager {
         final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
         final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
 
-        this.context = new Context(account, (number, aci) -> {
-            accountFileUpdater.updateAccountIdentifiers(number, aci);
-            synchronized (addressChangedListeners) {
-                addressChangedListeners.forEach(Runnable::run);
+        this.context = new Context(account, new AccountFileUpdater() {
+            @Override
+            public void updateAccountIdentifiers(final String number, final ACI aci) {
+                accountFileUpdater.updateAccountIdentifiers(number, aci);
+                synchronized (addressChangedListeners) {
+                    addressChangedListeners.forEach(Runnable::run);
+                }
+            }
+
+            @Override
+            public void removeAccount() {
+                accountFileUpdater.removeAccount();
             }
         }, dependencies, avatarStore, attachmentStore, stickerPackStore);
         this.context.getAccountHelper().setUnregisteredListener(this::close);
@@ -179,7 +190,7 @@ class ManagerImpl implements Manager {
     }
 
     @Override
-    public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
+    public Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException {
         final var canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
             try {
                 final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getNumber());
@@ -202,7 +213,13 @@ 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);
-            return new Pair<>(number.isEmpty() ? null : number, aci == null ? null : aci.uuid());
+            final var profile = aci == null
+                    ? null
+                    : context.getProfileHelper().getRecipientProfile(account.getRecipientStore().resolveRecipient(aci));
+            return new UserStatus(number.isEmpty() ? null : number,
+                    aci == null ? null : aci.uuid(),
+                    profile != null
+                            && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED);
         }));
     }
 
@@ -245,10 +262,16 @@ class ManagerImpl implements Manager {
     }
 
     @Override
-    public void setProfile(
-            String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
-    ) throws IOException {
-        context.getProfileHelper().setProfile(givenName, familyName, about, aboutEmoji, avatar);
+    public void updateProfile(UpdateProfile updateProfile) throws IOException {
+        context.getProfileHelper()
+                .setProfile(updateProfile.getGivenName(),
+                        updateProfile.getFamilyName(),
+                        updateProfile.getAbout(),
+                        updateProfile.getAboutEmoji(),
+                        updateProfile.isDeleteAvatar()
+                                ? Optional.empty()
+                                : updateProfile.getAvatar() == null ? null : Optional.of(updateProfile.getAvatar()),
+                        updateProfile.getMobileCoinAddress());
         context.getSyncHelper().sendSyncFetchProfileMessage();
     }
 
@@ -319,7 +342,7 @@ class ManagerImpl implements Manager {
     }
 
     @Override
-    public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException {
+    public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredRecipientException {
         return context.getProfileHelper().getRecipientProfile(context.getRecipientHelper().resolveRecipient(recipient));
     }
 
@@ -477,7 +500,7 @@ class ManagerImpl implements Manager {
     @Override
     public SendMessageResults sendReadReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException {
+    ) {
         final var timestamp = System.currentTimeMillis();
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
                 messageIds,
@@ -489,7 +512,7 @@ class ManagerImpl implements Manager {
     @Override
     public SendMessageResults sendViewedReceipt(
             RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException {
+    ) {
         final var timestamp = System.currentTimeMillis();
         var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
                 messageIds,
@@ -502,7 +525,7 @@ class ManagerImpl implements Manager {
             final RecipientIdentifier.Single sender,
             final long timestamp,
             final SignalServiceReceiptMessage receiptMessage
-    ) throws IOException {
+    ) {
         try {
             final var result = context.getSendHelper()
                     .sendReceiptMessage(receiptMessage, context.getRecipientHelper().resolveRecipient(sender));
@@ -517,6 +540,11 @@ class ManagerImpl implements Manager {
     public SendMessageResults sendMessage(
             Message message, Set<RecipientIdentifier> recipients
     ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException {
+        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.");
+        }
         final var messageBuilder = SignalServiceDataMessage.newBuilder();
         applyMessage(messageBuilder, message);
         return sendMessage(messageBuilder, recipients);
@@ -569,7 +597,7 @@ class ManagerImpl implements Manager {
         }
     }
 
-    private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws IOException, UnregisteredRecipientException {
+    private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException {
         final var mentions = new ArrayList<SignalServiceDataMessage.Mention>();
         for (final var m : mentionList) {
             final var recipientId = context.getRecipientHelper().resolveRecipient(m.recipient());
@@ -617,6 +645,20 @@ class ManagerImpl implements Manager {
         return sendMessage(messageBuilder, recipients);
     }
 
+    @Override
+    public SendMessageResults sendPaymentNotificationMessage(
+            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 messageBuilder = SignalServiceDataMessage.newBuilder().withPayment(payment);
+        try {
+            return sendMessage(messageBuilder, Set.of(recipient));
+        } catch (NotAGroupMemberException | GroupNotFoundException | GroupSendingNotAllowedException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     @Override
     public SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException {
         var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage();
@@ -653,7 +695,7 @@ class ManagerImpl implements Manager {
     @Override
     public void setContactName(
             RecipientIdentifier.Single recipient, String name
-    ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException {
+    ) throws NotMasterDeviceException, UnregisteredRecipientException {
         if (!account.isMasterDevice()) {
             throw new NotMasterDeviceException();
         }
@@ -661,26 +703,58 @@ class ManagerImpl implements Manager {
     }
 
     @Override
-    public void setContactBlocked(
-            RecipientIdentifier.Single recipient, boolean blocked
+    public void setContactsBlocked(
+            Collection<RecipientIdentifier.Single> recipients, boolean blocked
     ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException {
         if (!account.isMasterDevice()) {
             throw new NotMasterDeviceException();
         }
-        context.getContactHelper().setContactBlocked(context.getRecipientHelper().resolveRecipient(recipient), blocked);
-        // TODO cycle our profile key, if we're not together in a group with recipient
+        if (recipients.size() == 0) {
+            return;
+        }
+        final var recipientIds = context.getRecipientHelper().resolveRecipients(recipients);
+        final var selfRecipientId = account.getSelfRecipientId();
+        boolean shouldRotateProfileKey = false;
+        for (final var recipientId : recipientIds) {
+            if (context.getContactHelper().isContactBlocked(recipientId) == blocked) {
+                continue;
+            }
+            context.getContactHelper().setContactBlocked(recipientId, blocked);
+            // if we don't have a common group with the blocked contact we need to rotate the profile key
+            shouldRotateProfileKey = blocked && (
+                    shouldRotateProfileKey || account.getGroupStore()
+                            .getGroups()
+                            .stream()
+                            .noneMatch(g -> g.isMember(selfRecipientId) && g.isMember(recipientId))
+            );
+        }
+        if (shouldRotateProfileKey) {
+            context.getProfileHelper().rotateProfileKey();
+        }
         context.getSyncHelper().sendBlockedList();
     }
 
     @Override
-    public void setGroupBlocked(
-            final GroupId groupId, final boolean blocked
-    ) throws GroupNotFoundException, NotMasterDeviceException {
+    public void setGroupsBlocked(
+            final Collection<GroupId> groupIds, final boolean blocked
+    ) throws GroupNotFoundException, NotMasterDeviceException, IOException {
         if (!account.isMasterDevice()) {
             throw new NotMasterDeviceException();
         }
-        context.getGroupHelper().setGroupBlocked(groupId, blocked);
-        // TODO cycle our profile key
+        if (groupIds.size() == 0) {
+            return;
+        }
+        boolean shouldRotateProfileKey = false;
+        for (final var groupId : groupIds) {
+            if (context.getGroupHelper().isGroupBlocked(groupId) == blocked) {
+                continue;
+            }
+            context.getGroupHelper().setGroupBlocked(groupId, blocked);
+            shouldRotateProfileKey = blocked;
+        }
+        if (shouldRotateProfileKey) {
+            context.getProfileHelper().rotateProfileKey();
+        }
         context.getSyncHelper().sendBlockedList();
     }
 
@@ -877,7 +951,7 @@ class ManagerImpl implements Manager {
         final RecipientId recipientId;
         try {
             recipientId = context.getRecipientHelper().resolveRecipient(recipient);
-        } catch (IOException | UnregisteredRecipientException e) {
+        } catch (UnregisteredRecipientException e) {
             return false;
         }
         return context.getContactHelper().isContactBlocked(recipientId);
@@ -889,12 +963,22 @@ class ManagerImpl implements Manager {
     }
 
     @Override
-    public List<Pair<RecipientAddress, Contact>> getContacts() {
-        return account.getContactStore()
-                .getContacts()
-                .stream()
-                .map(p -> new Pair<>(account.getRecipientStore().resolveRecipientAddress(p.first()), p.second()))
-                .toList();
+    public List<Recipient> getRecipients(
+            boolean onlyContacts,
+            Optional<Boolean> blocked,
+            Collection<RecipientIdentifier.Single> recipients,
+            Optional<String> name
+    ) {
+        final var recipientIds = recipients.stream().map(a -> {
+            try {
+                return context.getRecipientHelper().resolveRecipient(a);
+            } catch (UnregisteredRecipientException e) {
+                return null;
+            }
+        }).filter(Objects::nonNull).collect(Collectors.toSet());
+        // refresh profiles of explicitly given recipients
+        context.getProfileHelper().refreshRecipientProfiles(recipientIds);
+        return account.getRecipientStore().getRecipients(onlyContacts, blocked, recipientIds, name);
     }
 
     @Override
@@ -902,7 +986,7 @@ class ManagerImpl implements Manager {
         final RecipientId recipientId;
         try {
             recipientId = context.getRecipientHelper().resolveRecipient(recipient);
-        } catch (IOException | UnregisteredRecipientException e) {
+        } catch (UnregisteredRecipientException e) {
             return null;
         }
 
@@ -952,7 +1036,7 @@ class ManagerImpl implements Manager {
         try {
             identity = account.getIdentityKeyStore()
                     .getIdentity(context.getRecipientHelper().resolveRecipient(recipient));
-        } catch (IOException | UnregisteredRecipientException e) {
+        } catch (UnregisteredRecipientException e) {
             identity = null;
         }
         return identity == null ? List.of() : List.of(toIdentity(identity));
@@ -989,12 +1073,7 @@ class ManagerImpl implements Manager {
     private boolean trustIdentity(
             RecipientIdentifier.Single recipient, Function<RecipientId, Boolean> trustMethod
     ) throws UnregisteredRecipientException {
-        RecipientId recipientId;
-        try {
-            recipientId = context.getRecipientHelper().resolveRecipient(recipient);
-        } catch (IOException e) {
-            return false;
-        }
+        final var recipientId = context.getRecipientHelper().resolveRecipient(recipient);
         final var updated = trustMethod.apply(recipientId);
         if (updated && this.isReceiving()) {
             context.getReceiveHelper().setNeedsToRetryFailedMessages(true);