]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/Manager.java
Fix NPR when loading an inactive group
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
index be5238d140cd981b2188570e37b46a414b905856..affbaa9de5993f607d61c468cc689dffa663faf0 100644 (file)
 package org.asamk.signal.manager;
 
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+
+import org.asamk.signal.manager.api.AlreadyReceivingException;
+import org.asamk.signal.manager.api.AttachmentInvalidException;
+import org.asamk.signal.manager.api.CaptchaRejectedException;
+import org.asamk.signal.manager.api.CaptchaRequiredException;
 import org.asamk.signal.manager.api.Configuration;
 import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.DeviceLimitExceededException;
+import org.asamk.signal.manager.api.DeviceLinkUrl;
 import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.api.GroupId;
+import org.asamk.signal.manager.api.GroupInviteLinkUrl;
+import org.asamk.signal.manager.api.GroupNotFoundException;
+import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
 import org.asamk.signal.manager.api.Identity;
+import org.asamk.signal.manager.api.IdentityVerificationCode;
 import org.asamk.signal.manager.api.InactiveGroupLinkException;
+import org.asamk.signal.manager.api.IncorrectPinException;
 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.LastGroupAdminException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
+import org.asamk.signal.manager.api.NotAGroupMemberException;
+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.PinLockedException;
+import org.asamk.signal.manager.api.RateLimitException;
+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.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.TypingAction;
+import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.api.UpdateGroup;
-import org.asamk.signal.manager.config.ServiceConfig;
-import org.asamk.signal.manager.config.ServiceEnvironment;
-import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
-import org.asamk.signal.manager.groups.LastGroupAdminException;
-import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
-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.whispersystems.signalservice.api.util.PhoneNumberFormatter;
+import org.asamk.signal.manager.api.UpdateProfile;
+import org.asamk.signal.manager.api.UserStatus;
+import org.asamk.signal.manager.api.UsernameLinkUrl;
+import org.asamk.signal.manager.api.UsernameStatus;
+import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
-import java.net.URI;
-import java.util.Arrays;
+import java.io.InputStream;
+import java.time.Duration;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 
 public interface Manager extends Closeable {
 
-    static Manager init(
-            String number,
-            File settingsPath,
-            ServiceEnvironment serviceEnvironment,
-            String userAgent,
-            TrustNewIdentity trustNewIdentity
-    ) throws IOException, NotRegisteredException {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-
-        if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
-            throw new NotRegisteredException();
-        }
-
-        var account = SignalAccount.load(pathConfig.dataPath(), number, true, trustNewIdentity);
+    static boolean isValidNumber(final String e164Number, final String countryCode) {
+        return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode);
+    }
 
-        if (!account.isRegistered()) {
-            throw new NotRegisteredException();
+    static boolean isSignalClientAvailable() {
+        final Logger logger = LoggerFactory.getLogger(Manager.class);
+        try {
+            try {
+                org.signal.libsignal.internal.Native.UuidCiphertext_CheckValidContents(new byte[0]);
+            } catch (Exception e) {
+                logger.trace("Expected exception when checking libsignal-client: {}", e.getMessage());
+            }
+            return true;
+        } catch (UnsatisfiedLinkError e) {
+            logger.warn("Failed to call libsignal-client: {}", e.getMessage());
+            return false;
         }
-
-        final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
-
-        return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
     }
 
-    static void initLogger() {
-        LibSignalLogger.initLogger();
-    }
+    String getSelfNumber();
 
-    static boolean isValidNumber(final String e164Number, final String countryCode) {
-        return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
-    }
+    /**
+     * This is used for checking a set of phone numbers for registration on Signal
+     *
+     * @param numbers The set of phone number in question
+     * @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null.
+     * @throws IOException if it's unable to get the contacts to check if they're registered
+     */
+    Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
 
-    static List<String> getAllLocalNumbers(File settingsPath) {
-        var pathConfig = PathConfig.createDefault(settingsPath);
-        final var dataPath = pathConfig.dataPath();
-        final var files = dataPath.listFiles();
+    Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames);
 
-        if (files == null) {
-            return List.of();
-        }
+    void updateAccountAttributes(
+            String deviceName,
+            Boolean unrestrictedUnidentifiedSender,
+            final Boolean discoverableByNumber,
+            final Boolean numberSharing
+    ) throws IOException;
 
-        return Arrays.stream(files)
-                .filter(File::isFile)
-                .map(File::getName)
-                .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
-                .collect(Collectors.toList());
-    }
+    Configuration getConfiguration();
 
-    String getSelfNumber();
+    void updateConfiguration(Configuration configuration) throws NotPrimaryDeviceException;
 
-    void checkAccountState() throws IOException;
+    /**
+     * Update the user's profile.
+     * If a field is null, the previous value will be kept.
+     */
+    void updateProfile(UpdateProfile updateProfile) throws IOException;
 
-    Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException;
+    String getUsername();
 
-    void updateAccountAttributes(String deviceName) throws IOException;
+    UsernameLinkUrl getUsernameLink();
 
-    Configuration getConfiguration();
+    /**
+     * Set a username for the account.
+     * If the username is null, it will be deleted.
+     */
+    void setUsername(String username) throws IOException, InvalidUsernameException;
 
-    void updateConfiguration(Configuration configuration) throws IOException, NotMasterDeviceException;
+    /**
+     * Set a username for the account.
+     * If the username is null, it will be deleted.
+     */
+    void deleteUsername() throws IOException;
 
-    void setProfile(
-            String givenName, String familyName, String about, String aboutEmoji, Optional<File> avatar
-    ) throws IOException;
+    void startChangeNumber(
+            String newNumber,
+            boolean voiceVerification,
+            String captcha
+    ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException;
+
+    void finishChangeNumber(
+            String newNumber,
+            String verificationCode,
+            String pin
+    ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException;
 
     void unregister() throws IOException;
 
     void deleteAccount() throws IOException;
 
-    void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException;
+    void submitRateLimitRecaptchaChallenge(
+            String challenge,
+            String captcha
+    ) throws IOException, CaptchaRejectedException;
 
     List<Device> getLinkedDevices() throws IOException;
 
-    void removeLinkedDevices(long deviceId) throws IOException;
-
-    void addDeviceLink(URI linkUri) throws IOException, InvalidDeviceLinkException;
+    void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException;
 
-    void setRegistrationLockPin(Optional<String> pin) throws IOException;
+    void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException;
 
-    Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException;
+    void setRegistrationLockPin(Optional<String> pin) throws IOException, NotPrimaryDeviceException;
 
     List<Group> getGroups();
 
     SendGroupMessageResults quitGroup(
-            GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
-    ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException;
+            GroupId groupId,
+            Set<RecipientIdentifier.Single> groupAdmins
+    ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException;
 
     void deleteGroup(GroupId groupId) throws IOException;
 
     Pair<GroupId, SendGroupMessageResults> createGroup(
-            String name, Set<RecipientIdentifier.Single> members, File avatarFile
-    ) throws IOException, AttachmentInvalidException;
+            String name,
+            Set<RecipientIdentifier.Single> members,
+            String avatarFile
+    ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException;
 
     SendGroupMessageResults updateGroup(
-            final GroupId groupId, final UpdateGroup updateGroup
-    ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException;
+            final GroupId groupId,
+            final UpdateGroup updateGroup
+    ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException;
 
     Pair<GroupId, SendGroupMessageResults> joinGroup(
             GroupInviteLinkUrl inviteLinkUrl
-    ) throws IOException, InactiveGroupLinkException;
+    ) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException;
 
-    void sendTypingMessage(
-            TypingAction action, Set<RecipientIdentifier> recipients
-    ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+    SendMessageResults sendTypingMessage(
+            TypingAction action,
+            Set<RecipientIdentifier> recipients
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
 
-    void sendReadReceipt(
-            RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException;
+    SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
 
-    void sendViewedReceipt(
-            RecipientIdentifier.Single sender, List<Long> messageIds
-    ) throws IOException, UntrustedIdentityException;
+    SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
 
     SendMessageResults sendMessage(
-            Message message, Set<RecipientIdentifier> recipients
-    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+            Message message,
+            Set<RecipientIdentifier> recipients,
+            boolean notifySelf
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
+
+    SendMessageResults sendEditMessage(
+            Message message,
+            Set<RecipientIdentifier> recipients,
+            long editTargetTimestamp
+    ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
 
     SendMessageResults sendRemoteDeleteMessage(
-            long targetSentTimestamp, Set<RecipientIdentifier> recipients
+            long targetSentTimestamp,
+            Set<RecipientIdentifier> recipients
     ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
 
     SendMessageResults sendMessageReaction(
@@ -169,28 +214,67 @@ public interface Manager extends Closeable {
             boolean remove,
             RecipientIdentifier.Single targetAuthor,
             long targetSentTimestamp,
-            Set<RecipientIdentifier> recipients
-    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+            Set<RecipientIdentifier> recipients,
+            final boolean isStory
+    ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException;
+
+    SendMessageResults sendPaymentNotificationMessage(
+            byte[] receipt,
+            String note,
+            RecipientIdentifier.Single recipient
+    ) throws IOException;
 
     SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
 
-    void setContactName(
-            RecipientIdentifier.Single recipient, String name
-    ) throws NotMasterDeviceException, IOException;
+    SendMessageResults sendMessageRequestResponse(
+            MessageEnvelope.Sync.MessageRequestResponse.Type type,
+            Set<RecipientIdentifier> recipientIdentifiers
+    );
+
+    void hideRecipient(RecipientIdentifier.Single recipient);
+
+    void deleteRecipient(RecipientIdentifier.Single recipient);
 
-    void setContactBlocked(
-            RecipientIdentifier.Single recipient, boolean blocked
-    ) throws NotMasterDeviceException, IOException;
+    void deleteContact(RecipientIdentifier.Single recipient);
 
-    void setGroupBlocked(
-            GroupId groupId, boolean blocked
-    ) throws GroupNotFoundException, IOException, NotMasterDeviceException;
+    void setContactName(
+            final RecipientIdentifier.Single recipient,
+            final String givenName,
+            final String familyName,
+            final String nickGivenName,
+            final String nickFamilyName,
+            final String note
+    ) throws NotPrimaryDeviceException, UnregisteredRecipientException;
+
+    void setContactsBlocked(
+            Collection<RecipientIdentifier.Single> recipient,
+            boolean blocked
+    ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
+
+    void setGroupsBlocked(
+            Collection<GroupId> groupId,
+            boolean blocked
+    ) throws GroupNotFoundException, IOException, NotPrimaryDeviceException;
 
+    /**
+     * Change the expiration timer for a contact
+     */
     void setExpirationTimer(
-            RecipientIdentifier.Single recipient, int messageExpirationTimer
-    ) throws IOException;
+            RecipientIdentifier.Single recipient,
+            int messageExpirationTimer
+    ) throws IOException, UnregisteredRecipientException;
+
+    /**
+     * Upload the sticker pack from path.
+     *
+     * @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
+     */
+    StickerPackUrl uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
+
+    void installStickerPack(StickerPackUrl url) throws IOException;
 
-    URI uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
+    List<StickerPack> getStickerPacks();
 
     void requestAllSyncData() throws IOException;
 
@@ -198,7 +282,11 @@ public interface Manager extends Closeable {
      * Add a handler to receive new messages.
      * Will start receiving messages from server, if not already started.
      */
-    void addReceiveHandler(ReceiveMessageHandler handler);
+    default void addReceiveHandler(ReceiveMessageHandler handler) {
+        addReceiveHandler(handler, false);
+    }
+
+    void addReceiveHandler(ReceiveMessageHandler handler, final boolean isWeakListener);
 
     /**
      * Remove a handler to receive new messages.
@@ -211,22 +299,26 @@ public interface Manager extends Closeable {
     /**
      * Receive new messages from server, returns if no new message arrive in a timespan of timeout.
      */
-    void receiveMessages(long timeout, TimeUnit unit, ReceiveMessageHandler handler) throws IOException;
-
-    /**
-     * Receive new messages from server, returns only if the thread is interrupted.
-     */
-    void receiveMessages(ReceiveMessageHandler handler) throws IOException;
+    void receiveMessages(
+            Optional<Duration> timeout,
+            Optional<Integer> maxMessages,
+            ReceiveMessageHandler handler
+    ) throws IOException, AlreadyReceivingException;
 
-    void setIgnoreAttachments(boolean ignoreAttachments);
+    void stopReceiveMessages();
 
-    boolean hasCaughtUpWithOldMessages();
+    void setReceiveConfig(ReceiveConfig receiveConfig);
 
     boolean isContactBlocked(RecipientIdentifier.Single recipient);
 
     void sendContacts() throws IOException;
 
-    List<Pair<RecipientAddress, Contact>> getContacts();
+    List<Recipient> getRecipients(
+            boolean onlyContacts,
+            Optional<Boolean> blocked,
+            Collection<RecipientIdentifier.Single> address,
+            Optional<String> name
+    );
 
     String getContactOrProfileName(RecipientIdentifier.Single recipient);
 
@@ -236,19 +328,45 @@ public interface Manager extends Closeable {
 
     List<Identity> getIdentities(RecipientIdentifier.Single recipient);
 
-    boolean trustIdentityVerified(RecipientIdentifier.Single recipient, byte[] fingerprint);
+    /**
+     * Trust this the identity with this fingerprint/safetyNumber
+     *
+     * @param recipient account of the identity
+     */
+    boolean trustIdentityVerified(
+            RecipientIdentifier.Single recipient,
+            IdentityVerificationCode verificationCode
+    ) throws UnregisteredRecipientException;
 
-    boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, String safetyNumber);
+    /**
+     * Trust all keys of this identity without verification
+     *
+     * @param recipient account of the identity
+     */
+    boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) throws UnregisteredRecipientException;
+
+    void addAddressChangedListener(Runnable listener);
+
+    void addClosedListener(Runnable listener);
+
+    InputStream retrieveAttachment(final String id) throws IOException;
 
-    boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, byte[] safetyNumber);
+    InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
 
-    boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient);
+    InputStream retrieveProfileAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
+
+    InputStream retrieveGroupAvatar(final GroupId groupId) throws IOException;
+
+    InputStream retrieveSticker(final StickerPackId stickerPackId, final int stickerId) throws IOException;
 
     @Override
-    void close() throws IOException;
+    void close();
 
     interface ReceiveMessageHandler {
 
+        ReceiveMessageHandler EMPTY = (envelope, e) -> {
+        };
+
         void handleMessage(MessageEnvelope envelope, Throwable e);
     }
 }