X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/f5ba7894ae06909baeebfe9c23ccb9ab6b00a147..fc2e9bbfaec8dbe6e317b951c0d28fa2e9421347:/lib/src/main/java/org/asamk/signal/manager/Manager.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index f70c4e29..41e08fb6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -1,111 +1,124 @@ 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.CaptchaRequiredException; +import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; +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.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.libsignal.InvalidKeyException; -import org.whispersystems.libsignal.util.Pair; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; -import org.whispersystems.signalservice.api.messages.SignalServiceContent; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; +import org.asamk.signal.manager.api.UpdateProfile; +import org.asamk.signal.manager.api.UserStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; 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.getDataPath(), number)) { - throw new NotRegisteredException(); - } - - var account = SignalAccount.load(pathConfig.getDataPath(), number, true, trustNewIdentity); + static boolean isValidNumber(final String e164Number, final String countryCode) { + return PhoneNumberFormatter.isValidNumber(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 List getAllLocalNumbers(File settingsPath) { - var pathConfig = PathConfig.createDefault(settingsPath); - final var dataPath = pathConfig.getDataPath(); - final var files = dataPath.listFiles(); + String getSelfNumber(); - if (files == null) { - return List.of(); - } + /** + * 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 getUserStatus(Set numbers) throws IOException, RateLimitException; - return Arrays.stream(files) - .filter(File::isFile) - .map(File::getName) - .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) - .collect(Collectors.toList()); - } + void updateAccountAttributes(String deviceName) throws IOException; - String getSelfNumber(); + Configuration getConfiguration(); - void checkAccountState() throws IOException; + void updateConfiguration(Configuration configuration) throws NotPrimaryDeviceException; - Map> areUsersRegistered(Set numbers) throws IOException; + /** + * Update the user's profile. + * If a field is null, the previous value will be kept. + */ + void updateProfile(UpdateProfile updateProfile) throws IOException; - void updateAccountAttributes(String deviceName) throws IOException; + /** + * Set a username for the account. + * If the username is null, it will be deleted. + */ + String setUsername(String username) throws IOException, InvalidUsernameException; - void updateConfiguration( - final Boolean readReceipts, - final Boolean unidentifiedDeliveryIndicators, - final Boolean typingIndicators, - final Boolean linkPreviews - ) 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 avatar - ) throws IOException; + void startChangeNumber( + String newNumber, boolean voiceVerification, String captcha + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException; + + void finishChangeNumber( + String newNumber, String verificationCode, String pin + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException; void unregister() throws IOException; @@ -115,49 +128,51 @@ public interface Manager extends Closeable { List getLinkedDevices() throws IOException; - void removeLinkedDevices(long deviceId) throws IOException; + void removeLinkedDevices(int deviceId) throws IOException; - void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException; + void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException; - void setRegistrationLockPin(Optional pin) throws IOException, UnauthenticatedResponseException; - - Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException; + void setRegistrationLockPin(Optional pin) throws IOException, NotPrimaryDeviceException; List getGroups(); SendGroupMessageResults quitGroup( GroupId groupId, Set groupAdmins - ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException; + ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException; void deleteGroup(GroupId groupId) throws IOException; Pair createGroup( - String name, Set members, File avatarFile - ) throws IOException, AttachmentInvalidException; + String name, Set members, String avatarFile + ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException; SendGroupMessageResults updateGroup( final GroupId groupId, final UpdateGroup updateGroup - ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException; + ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException; Pair joinGroup( GroupInviteLinkUrl inviteLinkUrl - ) throws IOException, GroupLinkNotActiveException; + ) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException; - void sendTypingMessage( + SendMessageResults sendTypingMessage( TypingAction action, Set recipients - ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; - void sendReadReceipt( + SendMessageResults sendReadReceipt( RecipientIdentifier.Single sender, List messageIds - ) throws IOException, UntrustedIdentityException; + ); - void sendViewedReceipt( + SendMessageResults sendViewedReceipt( RecipientIdentifier.Single sender, List messageIds - ) throws IOException, UntrustedIdentityException; + ); SendMessageResults sendMessage( Message message, Set recipients - ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; + + SendMessageResults sendEditMessage( + Message message, Set recipients, long editTargetTimestamp + ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; SendMessageResults sendRemoteDeleteMessage( long targetSentTimestamp, Set recipients @@ -168,46 +183,90 @@ public interface Manager extends Closeable { boolean remove, RecipientIdentifier.Single targetAuthor, long targetSentTimestamp, - Set recipients - ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; + Set recipients, + final boolean isStory + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException; + + SendMessageResults sendPaymentNotificationMessage( + byte[] receipt, String note, RecipientIdentifier.Single recipient + ) throws IOException; SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; + void deleteRecipient(RecipientIdentifier.Single recipient); + + void deleteContact(RecipientIdentifier.Single recipient); + void setContactName( - RecipientIdentifier.Single recipient, String name - ) throws NotMasterDeviceException, UnregisteredUserException; + RecipientIdentifier.Single recipient, String givenName, final String familyName + ) throws NotPrimaryDeviceException, UnregisteredRecipientException; - void setContactBlocked( - RecipientIdentifier.Single recipient, boolean blocked - ) throws NotMasterDeviceException, IOException; + void setContactsBlocked( + Collection recipient, boolean blocked + ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException; - void setGroupBlocked( - GroupId groupId, boolean blocked - ) throws GroupNotFoundException, IOException, NotMasterDeviceException; + void setGroupsBlocked( + Collection groupId, boolean blocked + ) throws GroupNotFoundException, IOException, NotPrimaryDeviceException; + /** + * Change the expiration timer for a contact + */ void setExpirationTimer( RecipientIdentifier.Single recipient, int messageExpirationTimer - ) throws IOException; + ) 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; - URI uploadStickerPack(File path) throws IOException, StickerPackInvalidException; + void installStickerPack(StickerPackUrl url) throws IOException; + + List getStickerPacks(); void requestAllSyncData() throws IOException; - void receiveMessages( - long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler - ) throws IOException; + /** + * Add a handler to receive new messages. + * Will start receiving messages from server, if not already started. + */ + default void addReceiveHandler(ReceiveMessageHandler handler) { + addReceiveHandler(handler, false); + } - void setIgnoreAttachments(boolean ignoreAttachments); + void addReceiveHandler(ReceiveMessageHandler handler, final boolean isWeakListener); - boolean hasCaughtUpWithOldMessages(); + /** + * Remove a handler to receive new messages. + * Will stop receiving messages from server, if this was the last registered receiver. + */ + void removeReceiveHandler(ReceiveMessageHandler handler); - boolean isContactBlocked(RecipientIdentifier.Single recipient); + boolean isReceiving(); + + /** + * Receive new messages from server, returns if no new message arrive in a timespan of timeout. + */ + void receiveMessages( + Optional timeout, Optional maxMessages, ReceiveMessageHandler handler + ) throws IOException, AlreadyReceivingException; + + void setReceiveConfig(ReceiveConfig receiveConfig); - File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId); + boolean isContactBlocked(RecipientIdentifier.Single recipient); void sendContacts() throws IOException; - List> getContacts(); + List getRecipients( + boolean onlyContacts, + Optional blocked, + Collection address, + Optional name + ); String getContactOrProfileName(RecipientIdentifier.Single recipient); @@ -217,21 +276,36 @@ public interface Manager extends Closeable { List 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; - boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, byte[] safetyNumber); + void addAddressChangedListener(Runnable listener); - boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient); + void addClosedListener(Runnable listener); - SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address); + InputStream retrieveAttachment(final String id) throws IOException; @Override - void close() throws IOException; + void close(); interface ReceiveMessageHandler { - void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e); + ReceiveMessageHandler EMPTY = (envelope, e) -> { + }; + + void handleMessage(MessageEnvelope envelope, Throwable e); } }