X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/997b4f0c3fe371efe08e92ed4678835bc62538bf..404063a080a1a8784a1111304cc6b90b58362923:/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 56ecdc55..68218947 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -2,16 +2,22 @@ package org.asamk.signal.dbus; import org.asamk.Signal; import org.asamk.signal.BaseConfig; -import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.StickerPackInvalidException; -import org.asamk.signal.manager.UntrustedIdentityException; 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.InvalidNumberException; +import org.asamk.signal.manager.api.InvalidStickerException; import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.RecipientIdentifier; +import org.asamk.signal.manager.api.SendMessageResult; +import org.asamk.signal.manager.api.SendMessageResults; 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.groups.GroupId; import org.asamk.signal.manager.groups.GroupInviteLinkUrl; @@ -23,20 +29,15 @@ import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientAddress; -import org.asamk.signal.util.ErrorUtils; +import org.asamk.signal.util.SendMessageResultUtils; import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.interfaces.DBusInterface; import org.freedesktop.dbus.types.Variant; -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.SendMessageResult; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; -import org.whispersystems.signalservice.api.util.InvalidNumberException; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -50,6 +51,7 @@ 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.stream.Collectors; @@ -60,24 +62,43 @@ public class DbusSignalImpl implements Signal { private final Manager m; private final DBusConnection connection; private final String objectPath; + private final boolean noReceiveOnStart; private DBusPath thisDevice; private final List devices = new ArrayList<>(); private final List groups = new ArrayList<>(); + private DbusReceiveMessageHandler dbusMessageHandler; + private int subscriberCount; - public DbusSignalImpl(final Manager m, DBusConnection connection, final String objectPath) { + private final static Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class); + + public DbusSignalImpl( + final Manager m, DBusConnection connection, final String objectPath, final boolean noReceiveOnStart + ) { this.m = m; this.connection = connection; this.objectPath = objectPath; + this.noReceiveOnStart = noReceiveOnStart; } public void initObjects() { + if (!noReceiveOnStart) { + subscribeReceive(); + } + updateDevices(); updateGroups(); + updateConfiguration(); } public void close() { + if (dbusMessageHandler != null) { + m.removeReceiveHandler(dbusMessageHandler); + dbusMessageHandler = null; + } unExportDevices(); + unExportGroups(); + unExportConfiguration(); } @Override @@ -91,22 +112,56 @@ public class DbusSignalImpl implements Signal { } @Override - public void submitRateLimitChallenge(String challenge, String captchaString) throws IOErrorException { - final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", ""); + public void subscribeReceive() { + if (dbusMessageHandler == null) { + dbusMessageHandler = new DbusReceiveMessageHandler(connection, objectPath); + m.addReceiveHandler(dbusMessageHandler); + } + subscriberCount++; + } + + @Override + public void unsubscribeReceive() { + subscriberCount = Math.max(0, subscriberCount - 1); + if (subscriberCount == 0 && dbusMessageHandler != null) { + m.removeReceiveHandler(dbusMessageHandler); + dbusMessageHandler = null; + } + } + @Override + public void submitRateLimitChallenge(String challenge, String captcha) { try { m.submitRateLimitRecaptchaChallenge(challenge, captcha); } catch (IOException e) { - throw new IOErrorException("Submit challenge error: " + e.getMessage(), e); + throw new Error.Failure("Submit challenge error: " + e.getMessage()); + } + + } + + @Override + public void unregister() throws Error.Failure { + try { + m.unregister(); + } catch (IOException e) { + throw new Error.Failure("Failed to unregister: " + e.getMessage()); } + } + @Override + public void deleteAccount() throws Error.Failure { + try { + m.deleteAccount(); + } catch (IOException e) { + throw new Error.Failure("Failed to delete account: " + e.getMessage()); + } } @Override public void addDevice(String uri) { try { m.addDeviceLink(new URI(uri)); - } catch (IOException | InvalidKeyException e) { + } catch (IOException | InvalidDeviceLinkException e) { throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage()); } catch (URISyntaxException e) { throw new Error.InvalidUri(e.getClass().getSimpleName() @@ -139,27 +194,31 @@ public class DbusSignalImpl implements Signal { @Override public long sendMessage(final String message, final List attachments, final String recipient) { - var recipients = new ArrayList(1); - recipients.add(recipient); - return sendMessage(message, attachments, recipients); + return sendMessage(message, attachments, List.of(recipient)); } @Override public long sendMessage(final String message, final List attachments, final List recipients) { try { - final var results = m.sendMessage(new Message(message, attachments), + final var results = m.sendMessage(new Message(message, + attachments, + List.of(), + Optional.empty(), + Optional.empty()), getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet())); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results); + return results.timestamp(); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); + } catch (IOException | InvalidStickerException e) { + throw new Error.Failure(e); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -167,9 +226,7 @@ public class DbusSignalImpl implements Signal { public long sendRemoteDeleteMessage( final long targetSentTimestamp, final String recipient ) { - var recipients = new ArrayList(1); - recipients.add(recipient); - return sendRemoteDeleteMessage(targetSentTimestamp, recipients); + return sendRemoteDeleteMessage(targetSentTimestamp, List.of(recipient)); } @Override @@ -181,24 +238,8 @@ public class DbusSignalImpl implements Signal { getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet())); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { - throw new Error.GroupNotFound(e.getMessage()); - } - } - - @Override - public long sendGroupRemoteDeleteMessage( - final long targetSentTimestamp, final byte[] groupId - ) { - try { - final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, - Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -214,9 +255,7 @@ public class DbusSignalImpl implements Signal { final long targetSentTimestamp, final String recipient ) { - var recipients = new ArrayList(1); - recipients.add(recipient); - return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients); + return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, List.of(recipient)); } @Override @@ -235,12 +274,14 @@ public class DbusSignalImpl implements Signal { getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet())); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -249,18 +290,15 @@ public class DbusSignalImpl implements Signal { final String recipient, final boolean stop ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity { try { - var recipients = new ArrayList(1); - recipients.add(recipient); - m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, - getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() + final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, + getSingleRecipientIdentifiers(List.of(recipient), m.getSelfNumber()).stream() .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet())); + checkSendMessageResults(results); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); - } catch (UntrustedIdentityException e) { - throw new Error.UntrustedIdentity(e.getMessage()); } } @@ -269,11 +307,24 @@ public class DbusSignalImpl implements Signal { final String recipient, final List messageIds ) throws Error.Failure, Error.UntrustedIdentity { try { - m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); + final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), + messageIds); + checkSendMessageResults(results); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } + } + + @Override + public void sendViewedReceipt( + final String recipient, final List messageIds + ) throws Error.Failure, Error.UntrustedIdentity { + try { + final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), + messageIds); + checkSendMessageResults(results); } catch (IOException e) { throw new Error.Failure(e.getMessage()); - } catch (UntrustedIdentityException e) { - throw new Error.UntrustedIdentity(e.getMessage()); } } @@ -300,16 +351,21 @@ public class DbusSignalImpl implements Signal { final String message, final List attachments ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { try { - final var results = m.sendMessage(new Message(message, attachments), - Set.of(RecipientIdentifier.NoteToSelf.INSTANCE)); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + final var results = m.sendMessage(new Message(message, + attachments, + List.of(), + Optional.empty(), + Optional.empty()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE)); + checkSendMessageResults(results); + return results.timestamp(); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); - } catch (IOException e) { + } catch (IOException | InvalidStickerException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -317,25 +373,71 @@ public class DbusSignalImpl implements Signal { public void sendEndSessionMessage(final List recipients) { try { final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber())); - checkSendMessageResults(results.getTimestamp(), results.getResults()); + checkSendMessageResults(results); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } } + @Override + public void deleteRecipient(final String recipient) throws Error.Failure { + m.deleteRecipient(getSingleRecipientIdentifier(recipient, m.getSelfNumber())); + } + + @Override + public void deleteContact(final String recipient) throws Error.Failure { + m.deleteContact(getSingleRecipientIdentifier(recipient, m.getSelfNumber())); + } + @Override public long sendGroupMessage(final String message, final List attachments, final byte[] groupId) { try { - var results = m.sendMessage(new Message(message, attachments), - Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); - } catch (IOException e) { + var results = m.sendMessage(new Message(message, + attachments, + List.of(), + Optional.empty(), + Optional.empty()), Set.of(getGroupRecipientIdentifier(groupId))); + checkSendMessageResults(results); + return results.timestamp(); + } catch (IOException | InvalidStickerException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); + } + } + + @Override + public void sendGroupTyping( + final byte[] groupId, final boolean stop + ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity { + try { + final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, + Set.of(getGroupRecipientIdentifier(groupId))); + checkSendMessageResults(results); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { + throw new Error.GroupNotFound(e.getMessage()); + } + } + + @Override + public long sendGroupRemoteDeleteMessage( + final long targetSentTimestamp, final byte[] groupId + ) { + try { + final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, + Set.of(getGroupRecipientIdentifier(groupId))); + checkSendMessageResults(results); + return results.timestamp(); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { + throw new Error.GroupNotFound(e.getMessage()); } } @@ -352,13 +454,15 @@ public class DbusSignalImpl implements Signal { remove, getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()), targetSentTimestamp, - Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + Set.of(getGroupRecipientIdentifier(groupId))); + checkSendMessageResults(results); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -376,8 +480,10 @@ public class DbusSignalImpl implements Signal { m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name); } catch (NotMasterDeviceException e) { throw new Error.Failure("This command doesn't work on linked devices."); - } catch (UnregisteredUserException e) { + } catch (IOException e) { throw new Error.Failure("Contact is not registered."); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -387,6 +493,8 @@ public class DbusSignalImpl implements Signal { m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getSelfNumber()), expiration); } catch (IOException e) { throw new Error.Failure(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -398,6 +506,8 @@ public class DbusSignalImpl implements Signal { throw new Error.Failure("This command doesn't work on linked devices."); } catch (IOException e) { throw new Error.Failure(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -405,6 +515,8 @@ public class DbusSignalImpl implements Signal { public void setGroupBlocked(final byte[] groupId, final boolean blocked) { try { m.setGroupBlocked(getGroupId(groupId), blocked); + } catch (NotMasterDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); } catch (GroupNotFoundException e) { throw new Error.GroupNotFound(e.getMessage()); } catch (IOException e) { @@ -415,11 +527,7 @@ public class DbusSignalImpl implements Signal { @Override public List getGroupIds() { var groups = m.getGroups(); - var ids = new ArrayList(groups.size()); - for (var group : groups) { - ids.add(group.getGroupId().serialize()); - } - return ids; + return groups.stream().map(g -> g.groupId().serialize()).toList(); } @Override @@ -441,10 +549,10 @@ public class DbusSignalImpl implements Signal { @Override public String getGroupName(final byte[] groupId) { var group = m.getGroup(getGroupId(groupId)); - if (group == null || group.getTitle() == null) { + if (group == null || group.title() == null) { return ""; } else { - return group.getTitle(); + return group.title(); } } @@ -454,7 +562,7 @@ public class DbusSignalImpl implements Signal { if (group == null) { return List.of(); } else { - final var members = group.getMembers(); + final var members = group.members(); return getRecipientStrings(members); } } @@ -475,7 +583,8 @@ public class DbusSignalImpl implements Signal { final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber()); if (groupId == null) { final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar)); - checkSendMessageResults(results.second().getTimestamp(), results.second().getResults()); + updateGroups(); + checkGroupSendMessageResults(results.second().timestamp(), results.second().results()); return results.first().serialize(); } else { final var results = m.updateGroup(getGroupId(groupId), @@ -485,7 +594,7 @@ public class DbusSignalImpl implements Signal { .withAvatarFile(avatar == null ? null : new File(avatar)) .build()); if (results != null) { - checkSendMessageResults(results.getTimestamp(), results.getResults()); + checkGroupSendMessageResults(results.timestamp(), results.results()); } return groupId; } @@ -495,6 +604,8 @@ public class DbusSignalImpl implements Signal { throw new Error.GroupNotFound(e.getMessage()); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -511,9 +622,8 @@ public class DbusSignalImpl implements Signal { @Override public List isRegistered(List numbers) { - var results = new ArrayList(); if (numbers.isEmpty()) { - return results; + return List.of(); } Map> registered; @@ -526,7 +636,7 @@ public class DbusSignalImpl implements Signal { return numbers.stream().map(number -> { var uuid = registered.get(number).second(); return uuid != null; - }).collect(Collectors.toList()); + }).toList(); } @Override @@ -545,7 +655,7 @@ public class DbusSignalImpl implements Signal { aboutEmoji = nullIfEmpty(aboutEmoji); avatarPath = nullIfEmpty(avatarPath); Optional avatarFile = removeAvatar - ? Optional.absent() + ? Optional.empty() : avatarPath == null ? null : Optional.of(new File(avatarPath)); m.setProfile(givenName, familyName, about, aboutEmoji, avatarFile); } catch (IOException e) { @@ -567,11 +677,11 @@ public class DbusSignalImpl implements Signal { @Override public void removePin() { try { - m.setRegistrationLockPin(Optional.absent()); - } catch (UnauthenticatedResponseException e) { - throw new Error.Failure("Remove pin failed with unauthenticated response: " + e.getMessage()); + m.setRegistrationLockPin(Optional.empty()); } catch (IOException e) { throw new Error.Failure("Remove pin error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); } } @@ -579,10 +689,10 @@ public class DbusSignalImpl implements Signal { public void setPin(String registrationLockPin) { try { m.setRegistrationLockPin(Optional.of(registrationLockPin)); - } catch (UnauthenticatedResponseException e) { - throw new Error.Failure("Set pin error failed with unauthenticated response: " + e.getMessage()); } catch (IOException e) { throw new Error.Failure("Set pin error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); } } @@ -597,12 +707,12 @@ public class DbusSignalImpl implements Signal { // all numbers the system knows @Override public List listNumbers() { - return Stream.concat(m.getIdentities().stream().map(Identity::getRecipient), - m.getContacts().stream().map(Pair::first)) - .map(a -> a.getNumber().orElse(null)) + return Stream.concat(m.getIdentities().stream().map(Identity::recipient), + m.getContacts().stream().map(Pair::first)) + .map(a -> a.number().orElse(null)) .filter(Objects::nonNull) .distinct() - .collect(Collectors.toList()); + .toList(); } @Override @@ -617,13 +727,13 @@ public class DbusSignalImpl implements Signal { } // Try profiles if no contact name was found for (var identity : m.getIdentities()) { - final var address = identity.getRecipient(); - var number = address.getNumber().orElse(null); + final var address = identity.recipient(); + var number = address.number().orElse(null); if (number != null) { Profile profile = null; try { profile = m.getRecipientProfile(RecipientIdentifier.Single.fromAddress(address)); - } catch (UnregisteredUserException ignored) { + } catch (IOException | UnregisteredRecipientException ignored) { } if (profile != null && profile.getDisplayName().equals(name)) { numbers.add(number); @@ -642,6 +752,8 @@ public class DbusSignalImpl implements Signal { throw new Error.GroupNotFound(e.getMessage()); } catch (IOException | LastGroupAdminException e) { throw new Error.Failure(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } @@ -654,7 +766,7 @@ public class DbusSignalImpl implements Signal { } final var result = m.joinGroup(linkUrl); return result.first().serialize(); - } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) { + } catch (GroupInviteLinkUrl.InvalidGroupLinkException | InactiveGroupLinkException e) { throw new Error.Failure("Group link is invalid: " + e.getMessage()); } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) { throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage()); @@ -701,46 +813,44 @@ public class DbusSignalImpl implements Signal { } private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException { - var error = ErrorUtils.getErrorMessageFromSendMessageResult(result); + var error = SendMessageResultUtils.getErrorMessageFromSendMessageResult(result); if (error == null) { return; } - final var message = timestamp + "\nFailed to send message:\n" + error + '\n'; + final var message = "\nFailed to send message:\n" + error + '\n' + timestamp; - if (result.getIdentityFailure() != null) { + if (result.isIdentityFailure()) { throw new Error.UntrustedIdentity(message); } else { throw new Error.Failure(message); } } - private static void checkSendMessageResults( - long timestamp, Map> results - ) throws DBusExecutionException { - final var sendMessageResults = results.values().stream().findFirst(); - if (results.size() == 1 && sendMessageResults.get().size() == 1) { - checkSendMessageResult(timestamp, sendMessageResults.get().stream().findFirst().get()); + private void checkSendMessageResults(final SendMessageResults results) { + final var sendMessageResults = results.results().values().stream().findFirst(); + if (results.results().size() == 1 && sendMessageResults.get().size() == 1) { + checkSendMessageResult(results.timestamp(), sendMessageResults.get().stream().findFirst().get()); return; } - var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results); - if (errors.size() == 0) { + if (results.hasSuccess()) { return; } var message = new StringBuilder(); - message.append(timestamp).append('\n'); - message.append("Failed to send (some) messages:\n"); + message.append("Failed to send messages:\n"); + var errors = SendMessageResultUtils.getErrorMessagesFromSendMessageResults(results.results()); for (var error : errors) { message.append(error).append('\n'); } + message.append(results.timestamp()); throw new Error.Failure(message.toString()); } - private static void checkSendMessageResults( + private static void checkGroupSendMessageResults( long timestamp, Collection results ) throws DBusExecutionException { if (results.size() == 1) { @@ -748,23 +858,23 @@ public class DbusSignalImpl implements Signal { return; } - var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results); - if (errors.size() == 0) { + var errors = SendMessageResultUtils.getErrorMessagesFromSendMessageResults(results); + if (errors.size() < results.size()) { return; } var message = new StringBuilder(); - message.append(timestamp).append('\n'); - message.append("Failed to send (some) messages:\n"); + message.append("Failed to send message:\n"); for (var error : errors) { message.append(error).append('\n'); } + message.append(timestamp); throw new Error.Failure(message.toString()); } private static List getRecipientStrings(final Set members) { - return members.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList()); + return members.stream().map(RecipientAddress::getLegacyIdentifier).toList(); } private static Set getSingleRecipientIdentifiers( @@ -787,6 +897,10 @@ public class DbusSignalImpl implements Signal { } } + private RecipientIdentifier.Group getGroupRecipientIdentifier(final byte[] groupId) { + return new RecipientIdentifier.Group(getGroupId(groupId)); + } + private static GroupId getGroupId(byte[] groupId) throws DBusExecutionException { try { return GroupId.unknownVersion(groupId); @@ -824,15 +938,11 @@ public class DbusSignalImpl implements Signal { linkedDevices.forEach(d -> { final var object = new DbusSignalDeviceImpl(d); final var deviceObjectPath = object.getObjectPath(); - try { - connection.exportObject(object); - } catch (DBusException e) { - e.printStackTrace(); - } + exportObject(object); if (d.isThisDevice()) { thisDevice = new DBusPath(deviceObjectPath); } - this.devices.add(new StructDevice(new DBusPath(deviceObjectPath), d.getId(), emptyIfNull(d.getName()))); + this.devices.add(new StructDevice(new DBusPath(deviceObjectPath), d.id(), emptyIfNull(d.name()))); }); } @@ -859,15 +969,11 @@ public class DbusSignalImpl implements Signal { unExportGroups(); groups.forEach(g -> { - final var object = new DbusSignalGroupImpl(g.getGroupId()); - try { - connection.exportObject(object); - } catch (DBusException e) { - e.printStackTrace(); - } + final var object = new DbusSignalGroupImpl(g.groupId()); + exportObject(object); this.groups.add(new StructGroup(new DBusPath(object.getObjectPath()), - g.getGroupId().serialize(), - emptyIfNull(g.getTitle()))); + g.groupId().serialize(), + emptyIfNull(g.title()))); }); } @@ -876,28 +982,52 @@ public class DbusSignalImpl implements Signal { this.groups.clear(); } + private static String getConfigurationObjectPath(String basePath) { + return basePath + "/Configuration"; + } + + private void updateConfiguration() { + unExportConfiguration(); + final var object = new DbusSignalConfigurationImpl(); + exportObject(object); + } + + private void unExportConfiguration() { + final var objectPath = getConfigurationObjectPath(this.objectPath); + connection.unExportObject(objectPath); + } + + private void exportObject(final DBusInterface object) { + try { + connection.exportObject(object); + logger.debug("Exported dbus object: " + object.getObjectPath()); + } catch (DBusException e) { + e.printStackTrace(); + } + } + public class DbusSignalDeviceImpl extends DbusProperties implements Signal.Device { private final org.asamk.signal.manager.api.Device device; public DbusSignalDeviceImpl(final org.asamk.signal.manager.api.Device device) { super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Device", - List.of(new DbusProperty<>("Id", device::getId), - new DbusProperty<>("Name", () -> emptyIfNull(device.getName()), this::setDeviceName), - new DbusProperty<>("Created", device::getCreated), - new DbusProperty<>("LastSeen", device::getLastSeen)))); + List.of(new DbusProperty<>("Id", device::id), + new DbusProperty<>("Name", () -> emptyIfNull(device.name()), this::setDeviceName), + new DbusProperty<>("Created", device::created), + new DbusProperty<>("LastSeen", device::lastSeen)))); this.device = device; } @Override public String getObjectPath() { - return getDeviceObjectPath(objectPath, device.getId()); + return getDeviceObjectPath(objectPath, device.id()); } @Override public void removeDevice() throws Error.Failure { try { - m.removeLinkedDevices(device.getId()); + m.removeLinkedDevices(device.id()); updateDevices(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); @@ -918,6 +1048,78 @@ public class DbusSignalImpl implements Signal { } } + public class DbusSignalConfigurationImpl extends DbusProperties implements Signal.Configuration { + + public DbusSignalConfigurationImpl( + ) { + super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Configuration", + List.of(new DbusProperty<>("ReadReceipts", this::getReadReceipts, this::setReadReceipts), + new DbusProperty<>("UnidentifiedDeliveryIndicators", + this::getUnidentifiedDeliveryIndicators, + this::setUnidentifiedDeliveryIndicators), + new DbusProperty<>("TypingIndicators", + this::getTypingIndicators, + this::setTypingIndicators), + new DbusProperty<>("LinkPreviews", this::getLinkPreviews, this::setLinkPreviews)))); + + } + + @Override + public String getObjectPath() { + return getConfigurationObjectPath(objectPath); + } + + public void setReadReceipts(Boolean readReceipts) { + setConfiguration(readReceipts, null, null, null); + } + + public void setUnidentifiedDeliveryIndicators(Boolean unidentifiedDeliveryIndicators) { + setConfiguration(null, unidentifiedDeliveryIndicators, null, null); + } + + public void setTypingIndicators(Boolean typingIndicators) { + setConfiguration(null, null, typingIndicators, null); + } + + public void setLinkPreviews(Boolean linkPreviews) { + setConfiguration(null, null, null, linkPreviews); + } + + private void setConfiguration( + Boolean readReceipts, + Boolean unidentifiedDeliveryIndicators, + Boolean typingIndicators, + Boolean linkPreviews + ) { + try { + m.updateConfiguration(new org.asamk.signal.manager.api.Configuration(Optional.ofNullable(readReceipts), + Optional.ofNullable(unidentifiedDeliveryIndicators), + Optional.ofNullable(typingIndicators), + Optional.ofNullable(linkPreviews))); + } catch (IOException e) { + throw new Error.Failure("UpdateAccount error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); + } + } + + private boolean getReadReceipts() { + return m.getConfiguration().readReceipts().orElse(false); + } + + private boolean getUnidentifiedDeliveryIndicators() { + return m.getConfiguration().unidentifiedDeliveryIndicators().orElse(false); + } + + private boolean getTypingIndicators() { + return m.getConfiguration().typingIndicators().orElse(false); + } + + private boolean getLinkPreviews() { + return m.getConfiguration().linkPreviews().orElse(false); + } + } + public class DbusSignalGroupImpl extends DbusProperties implements Signal.Group { private final GroupId groupId; @@ -926,36 +1128,36 @@ public class DbusSignalImpl implements Signal { this.groupId = groupId; super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Group", List.of(new DbusProperty<>("Id", groupId::serialize), - new DbusProperty<>("Name", () -> emptyIfNull(getGroup().getTitle()), this::setGroupName), + new DbusProperty<>("Name", () -> emptyIfNull(getGroup().title()), this::setGroupName), new DbusProperty<>("Description", - () -> emptyIfNull(getGroup().getDescription()), + () -> emptyIfNull(getGroup().description()), this::setGroupDescription), new DbusProperty<>("Avatar", this::setGroupAvatar), new DbusProperty<>("IsBlocked", () -> getGroup().isBlocked(), this::setIsBlocked), new DbusProperty<>("IsMember", () -> getGroup().isMember()), new DbusProperty<>("IsAdmin", () -> getGroup().isAdmin()), new DbusProperty<>("MessageExpirationTimer", - () -> getGroup().getMessageExpirationTimer(), + () -> getGroup().messageExpirationTimer(), this::setMessageExpirationTime), new DbusProperty<>("Members", - () -> new Variant<>(getRecipientStrings(getGroup().getMembers()), "as")), + () -> new Variant<>(getRecipientStrings(getGroup().members()), "as")), new DbusProperty<>("PendingMembers", - () -> new Variant<>(getRecipientStrings(getGroup().getPendingMembers()), "as")), + () -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")), new DbusProperty<>("RequestingMembers", - () -> new Variant<>(getRecipientStrings(getGroup().getRequestingMembers()), "as")), + () -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")), new DbusProperty<>("Admins", - () -> new Variant<>(getRecipientStrings(getGroup().getAdminMembers()), "as")), + () -> new Variant<>(getRecipientStrings(getGroup().adminMembers()), "as")), new DbusProperty<>("PermissionAddMember", - () -> getGroup().getPermissionAddMember().name(), + () -> getGroup().permissionAddMember().name(), this::setGroupPermissionAddMember), new DbusProperty<>("PermissionEditDetails", - () -> getGroup().getPermissionEditDetails().name(), + () -> getGroup().permissionEditDetails().name(), this::setGroupPermissionEditDetails), new DbusProperty<>("PermissionSendMessage", - () -> getGroup().getPermissionSendMessage().name(), + () -> getGroup().permissionSendMessage().name(), this::setGroupPermissionSendMessage), new DbusProperty<>("GroupInviteLink", () -> { - final var groupInviteLinkUrl = getGroup().getGroupInviteLinkUrl(); + final var groupInviteLinkUrl = getGroup().groupInviteLinkUrl(); return groupInviteLinkUrl == null ? "" : groupInviteLinkUrl.getUrl(); })))); } @@ -975,7 +1177,19 @@ public class DbusSignalImpl implements Signal { throw new Error.Failure(e.getMessage()); } catch (LastGroupAdminException e) { throw new Error.LastGroupAdmin(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); + } + } + + @Override + public void deleteGroup() throws Error.Failure, Error.LastGroupAdmin { + try { + m.deleteGroup(groupId); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); } + updateGroups(); } @Override @@ -1060,6 +1274,8 @@ public class DbusSignalImpl implements Signal { private void setIsBlocked(final boolean isBlocked) { try { m.setGroupBlocked(groupId, isBlocked); + } catch (NotMasterDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); } catch (GroupNotFoundException e) { throw new Error.GroupNotFound(e.getMessage()); } catch (IOException e) { @@ -1076,6 +1292,8 @@ public class DbusSignalImpl implements Signal { throw new Error.GroupNotFound(e.getMessage()); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); + } catch (UnregisteredRecipientException e) { + throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered."); } } }