package org.asamk.signal.manager;
+import org.asamk.signal.manager.api.AttachmentInvalidException;
+import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Group;
+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.Message;
+import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.Pair;
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.config.ServiceConfig;
-import org.asamk.signal.manager.config.ServiceEnvironment;
+import org.asamk.signal.manager.api.UnregisteredRecipientException;
+import org.asamk.signal.manager.api.UpdateGroup;
+import org.asamk.signal.manager.api.UserStatus;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-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.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.groups.GroupInfo;
-import org.asamk.signal.manager.storage.identities.IdentityInfo;
-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.RecipientId;
-import org.whispersystems.libsignal.IdentityKey;
-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.storage.recipients.Recipient;
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.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 username,
- File settingsPath,
- ServiceEnvironment serviceEnvironment,
- String userAgent,
- TrustNewIdentity trustNewIdentity
- ) throws IOException, NotRegisteredException {
- var pathConfig = PathConfig.createDefault(settingsPath);
-
- if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
- throw new NotRegisteredException();
- }
-
- var account = SignalAccount.load(pathConfig.getDataPath(), username, true, trustNewIdentity);
-
- if (!account.isRegistered()) {
- throw new NotRegisteredException();
- }
-
- final var serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
-
- return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
+ static boolean isValidNumber(final String e164Number, final String countryCode) {
+ return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
}
- static List<String> getAllLocalUsernames(File settingsPath) {
- var pathConfig = PathConfig.createDefault(settingsPath);
- final var dataPath = pathConfig.getDataPath();
- final var files = dataPath.listFiles();
-
- if (files == null) {
- return List.of();
- }
-
- return Arrays.stream(files)
- .filter(File::isFile)
- .map(File::getName)
- .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
- .collect(Collectors.toList());
- }
-
- String getUsername();
-
- RecipientId getSelfRecipientId();
+ String getSelfNumber();
- int getDeviceId();
+ /**
+ * 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;
- void checkAccountState() throws IOException;
+ void updateAccountAttributes(String deviceName) throws IOException;
- Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException;
+ Configuration getConfiguration();
- void updateAccountAttributes(String deviceName) throws IOException;
+ void updateConfiguration(Configuration configuration) throws IOException, NotMasterDeviceException;
+ /**
+ * @param givenName if null, the previous givenName will be kept
+ * @param familyName if null, the previous familyName will be kept
+ * @param about if null, the previous about text will be kept
+ * @param aboutEmoji if null, the previous about emoji will be kept
+ * @param avatar if avatar is null the image from the local avatar store is used (if present),
+ */
void setProfile(
String givenName, String familyName, String about, String aboutEmoji, Optional<File> avatar
) throws IOException;
void removeLinkedDevices(int deviceId) throws IOException;
- void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException;
+ void addDeviceLink(URI linkUri) throws IOException, InvalidDeviceLinkException;
- void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException;
+ void setRegistrationLockPin(Optional<String> pin) throws IOException, NotMasterDeviceException;
- Profile getRecipientProfile(RecipientId recipientId);
+ Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
- List<GroupInfo> getGroups();
+ List<Group> getGroups();
SendGroupMessageResults quitGroup(
GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
- ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException;
+ ) 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;
+ ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException;
SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> removeAdmins,
- boolean resetGroupLink,
- GroupLinkState groupLinkState,
- GroupPermission addMemberPermission,
- GroupPermission editDetailsPermission,
- File avatarFile,
- Integer expirationTimer,
- Boolean isAnnouncementGroup
- ) 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, GroupLinkNotActiveException;
+ ) throws IOException, InactiveGroupLinkException;
- void sendTypingMessage(
+ SendMessageResults sendTypingMessage(
TypingAction action, Set<RecipientIdentifier> recipients
- ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+ ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
- void sendReadReceipt(
+ SendMessageResults sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds
- ) throws IOException, UntrustedIdentityException;
+ ) throws IOException;
- void sendViewedReceipt(
+ SendMessageResults sendViewedReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds
- ) throws IOException, UntrustedIdentityException;
+ ) throws IOException;
SendMessageResults sendMessage(
Message message, Set<RecipientIdentifier> recipients
- ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+ ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendRemoteDeleteMessage(
long targetSentTimestamp, Set<RecipientIdentifier> recipients
RecipientIdentifier.Single targetAuthor,
long targetSentTimestamp,
Set<RecipientIdentifier> recipients
- ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
+ ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException;
SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
+ void deleteRecipient(RecipientIdentifier.Single recipient);
+
+ void deleteContact(RecipientIdentifier.Single recipient);
+
void setContactName(
RecipientIdentifier.Single recipient, String name
- ) throws NotMasterDeviceException, UnregisteredUserException;
+ ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException;
- void setContactBlocked(
- RecipientIdentifier.Single recipient, boolean blocked
- ) throws NotMasterDeviceException, IOException;
+ void setContactsBlocked(
+ Collection<RecipientIdentifier.Single> recipient, boolean blocked
+ ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException;
- void setGroupBlocked(
- GroupId groupId, boolean blocked
- ) throws GroupNotFoundException, IOException;
+ void setGroupsBlocked(
+ Collection<GroupId> groupId, boolean blocked
+ ) throws GroupNotFoundException, IOException, NotMasterDeviceException;
+ /**
+ * Change the expiration timer for a contact
+ */
void setExpirationTimer(
RecipientIdentifier.Single recipient, int messageExpirationTimer
- ) throws IOException;
+ ) throws IOException, UnregisteredRecipientException;
- URI uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
+ /**
+ * 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 requestAllSyncData() throws IOException;
-
- void receiveMessages(
- long timeout,
- TimeUnit unit,
- boolean returnOnTimeout,
- boolean ignoreAttachments,
- ReceiveMessageHandler handler
- ) throws IOException;
-
- boolean hasCaughtUpWithOldMessages();
-
- boolean isContactBlocked(RecipientIdentifier.Single recipient);
-
- File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId);
-
- void sendContacts() throws IOException;
-
- List<Pair<RecipientId, Contact>> getContacts();
-
- String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier);
+ List<StickerPack> getStickerPacks();
- GroupInfo getGroup(GroupId groupId);
+ void requestAllSyncData() throws IOException;
- List<IdentityInfo> getIdentities();
+ /**
+ * 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);
+ }
- List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient);
+ void addReceiveHandler(ReceiveMessageHandler handler, final boolean isWeakListener);
- boolean trustIdentityVerified(RecipientIdentifier.Single recipient, byte[] fingerprint);
+ /**
+ * 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 trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, String safetyNumber);
+ boolean isReceiving();
- boolean trustIdentityVerifiedSafetyNumber(RecipientIdentifier.Single recipient, byte[] safetyNumber);
+ /**
+ * Receive new messages from server, returns if no new message arrive in a timespan of timeout.
+ */
+ void receiveMessages(Duration timeout, ReceiveMessageHandler handler) throws IOException;
- boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient);
+ /**
+ * Receive new messages from server, returns only if the thread is interrupted.
+ */
+ void receiveMessages(ReceiveMessageHandler handler) throws IOException;
- String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
+ void setIgnoreAttachments(boolean ignoreAttachments);
- byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
+ boolean hasCaughtUpWithOldMessages();
- SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address);
+ boolean isContactBlocked(RecipientIdentifier.Single recipient);
- SignalServiceAddress resolveSignalServiceAddress(UUID uuid);
+ void sendContacts() throws IOException;
- SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId);
+ List<Recipient> getRecipients(
+ boolean onlyContacts,
+ Optional<Boolean> blocked,
+ Collection<RecipientIdentifier.Single> address,
+ Optional<String> name
+ );
+
+ String getContactOrProfileName(RecipientIdentifier.Single recipient);
+
+ Group getGroup(GroupId groupId);
+
+ List<Identity> getIdentities();
+
+ List<Identity> getIdentities(RecipientIdentifier.Single recipient);
+
+ /**
+ * Trust this the identity with this fingerprint
+ *
+ * @param recipient account of the identity
+ * @param fingerprint Fingerprint
+ */
+ boolean trustIdentityVerified(
+ RecipientIdentifier.Single recipient, byte[] fingerprint
+ ) throws UnregisteredRecipientException;
+
+ /**
+ * Trust this the identity with this safety number
+ *
+ * @param recipient account of the identity
+ * @param safetyNumber Safety number
+ */
+ boolean trustIdentityVerifiedSafetyNumber(
+ RecipientIdentifier.Single recipient, String safetyNumber
+ ) throws UnregisteredRecipientException;
+
+ /**
+ * Trust this the identity with this scannable safety number
+ *
+ * @param recipient account of the identity
+ * @param safetyNumber Scannable safety number
+ */
+ boolean trustIdentityVerifiedSafetyNumber(
+ RecipientIdentifier.Single recipient, byte[] safetyNumber
+ ) throws UnregisteredRecipientException;
+
+ /**
+ * 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);
@Override
void close() throws IOException;
interface ReceiveMessageHandler {
- void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e);
+ ReceiveMessageHandler EMPTY = (envelope, e) -> {
+ };
+
+ void handleMessage(MessageEnvelope envelope, Throwable e);
}
}