X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/d4838bd646c736a5fe1d40b45aa12be239b01ed3..82bb4f22f07bae40ff42570d0ada81125adf4392:/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 698ce7c1..9f6f1340 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -1,21 +1,27 @@ package org.asamk.signal.dbus; import org.asamk.Signal; -import org.asamk.Signal.Error; 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.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.TypingAction; +import org.asamk.signal.manager.api.UpdateGroup; 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; @@ -26,25 +32,24 @@ 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.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.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.types.Variant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; import java.util.Collection; 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; @@ -57,7 +62,10 @@ public class DbusSignalImpl implements Signal { private final String objectPath; private DBusPath thisDevice; - private final List devices = new ArrayList<>(); + private final List devices = new ArrayList<>(); + private final List groups = new ArrayList<>(); + + private final static Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class); public DbusSignalImpl(final Manager m, DBusConnection connection, final String objectPath) { this.m = m; @@ -67,10 +75,14 @@ public class DbusSignalImpl implements Signal { public void initObjects() { updateDevices(); + updateGroups(); + updateConfiguration(); } public void close() { unExportDevices(); + unExportGroups(); + unExportConfiguration(); } @Override @@ -84,13 +96,13 @@ public class DbusSignalImpl implements Signal { } @Override - public void submitRateLimitChallenge(String challenge, String captchaString) throws IOErrorException { + public void submitRateLimitChallenge(String challenge, String captchaString) { final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", ""); 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()); } } @@ -99,7 +111,7 @@ public class DbusSignalImpl implements Signal { 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() @@ -111,45 +123,19 @@ public class DbusSignalImpl implements Signal { @Override public DBusPath getDevice(long deviceId) { updateDevices(); - return new DBusPath(getDeviceObjectPath(objectPath, deviceId)); + final var deviceOptional = devices.stream().filter(g -> g.getId().equals(deviceId)).findFirst(); + if (deviceOptional.isEmpty()) { + throw new Error.DeviceNotFound("Device not found"); + } + return deviceOptional.get().getObjectPath(); } @Override - public List listDevices() { + public List listDevices() { updateDevices(); return this.devices; } - private void updateDevices() { - List linkedDevices; - try { - linkedDevices = m.getLinkedDevices(); - } catch (IOException | Error.Failure e) { - throw new Error.Failure("Failed to get linked devices: " + e.getMessage()); - } - - unExportDevices(); - - linkedDevices.forEach(d -> { - final var object = new DbusSignalDeviceImpl(d); - final var deviceObjectPath = object.getObjectPath(); - try { - connection.exportObject(object); - } catch (DBusException e) { - e.printStackTrace(); - } - if (d.isThisDevice()) { - thisDevice = new DBusPath(deviceObjectPath); - } - this.devices.add(new DBusPath(deviceObjectPath)); - }); - } - - private void unExportDevices() { - this.devices.stream().map(DBusPath::getPath).forEach(connection::unExportObject); - this.devices.clear(); - } - @Override public DBusPath getThisDevice() { updateDevices(); @@ -171,12 +157,12 @@ public class DbusSignalImpl implements Signal { .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet())); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); } catch (IOException e) { - throw new Error.Failure(e.getMessage()); + throw new Error.Failure(e); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new Error.GroupNotFound(e.getMessage()); } @@ -200,8 +186,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(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -216,8 +202,8 @@ public class DbusSignalImpl implements Signal { try { final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -254,8 +240,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(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -296,6 +282,19 @@ public class DbusSignalImpl implements Signal { } } + @Override + public void sendViewedReceipt( + final String recipient, final List messageIds + ) throws Error.Failure, Error.UntrustedIdentity { + try { + m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (UntrustedIdentityException e) { + throw new Error.UntrustedIdentity(e.getMessage()); + } + } + @Override public void sendContacts() { try { @@ -321,8 +320,8 @@ public class DbusSignalImpl implements Signal { try { final var results = m.sendMessage(new Message(message, attachments), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE)); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (AttachmentInvalidException e) { throw new Error.AttachmentInvalid(e.getMessage()); } catch (IOException e) { @@ -336,7 +335,7 @@ 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.timestamp(), results.results()); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } @@ -347,8 +346,8 @@ public class DbusSignalImpl implements Signal { try { var results = m.sendMessage(new Message(message, attachments), Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -372,8 +371,8 @@ public class DbusSignalImpl implements Signal { getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()), targetSentTimestamp, Set.of(new RecipientIdentifier.Group(getGroupId(groupId)))); - checkSendMessageResults(results.getTimestamp(), results.getResults()); - return results.getTimestamp(); + checkSendMessageResults(results.timestamp(), results.results()); + return results.timestamp(); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { @@ -395,7 +394,7 @@ 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."); } } @@ -424,6 +423,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) { @@ -436,18 +437,34 @@ public class DbusSignalImpl implements Signal { var groups = m.getGroups(); var ids = new ArrayList(groups.size()); for (var group : groups) { - ids.add(group.getGroupId().serialize()); + ids.add(group.groupId().serialize()); } return ids; } + @Override + public DBusPath getGroup(final byte[] groupId) { + updateGroups(); + final var groupOptional = groups.stream().filter(g -> Arrays.equals(g.getId(), groupId)).findFirst(); + if (groupOptional.isEmpty()) { + throw new Error.GroupNotFound("Group not found"); + } + return groupOptional.get().getObjectPath(); + } + + @Override + public List listGroups() { + updateGroups(); + return groups; + } + @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(); } } @@ -457,10 +474,18 @@ public class DbusSignalImpl implements Signal { if (group == null) { return List.of(); } else { - return group.getMembers().stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList()); + final var members = group.members(); + return getRecipientStrings(members); } } + @Override + public byte[] createGroup( + final String name, final List members, final String avatar + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber { + return updateGroup(new byte[0], name, members, avatar); + } + @Override public byte[] updateGroup(byte[] groupId, String name, List members, String avatar) { try { @@ -470,25 +495,17 @@ 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()); + checkSendMessageResults(results.second().timestamp(), results.second().results()); return results.first().serialize(); } else { final var results = m.updateGroup(getGroupId(groupId), - name, - null, - memberIdentifiers, - null, - null, - null, - false, - null, - null, - null, - avatar == null ? null : new File(avatar), - null, - null); + UpdateGroup.newBuilder() + .withName(name) + .withMembers(memberIdentifiers) + .withAvatarFile(avatar == null ? null : new File(avatar)) + .build()); if (results != null) { - checkSendMessageResults(results.getTimestamp(), results.getResults()); + checkSendMessageResults(results.timestamp(), results.results()); } return groupId; } @@ -548,7 +565,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) { @@ -570,9 +587,7 @@ 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()); } @@ -582,8 +597,6 @@ 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()); } @@ -600,8 +613,8 @@ 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)) + return Stream.concat(m.getIdentities().stream().map(Identity::recipient), + m.getContacts().stream().map(Pair::first)) .map(a -> a.getNumber().orElse(null)) .filter(Objects::nonNull) .distinct() @@ -620,13 +633,13 @@ public class DbusSignalImpl implements Signal { } // Try profiles if no contact name was found for (var identity : m.getIdentities()) { - final var address = identity.getRecipient(); + final var address = identity.recipient(); var number = address.getNumber().orElse(null); if (number != null) { Profile profile = null; try { profile = m.getRecipientProfile(RecipientIdentifier.Single.fromAddress(address)); - } catch (UnregisteredUserException ignored) { + } catch (IOException ignored) { } if (profile != null && profile.getDisplayName().equals(name)) { numbers.add(number); @@ -657,7 +670,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()); @@ -712,7 +725,7 @@ public class DbusSignalImpl implements Signal { final var message = timestamp + "\nFailed to send message:\n" + error + '\n'; - if (result.getIdentityFailure() != null) { + if (result.isIdentityFailure()) { throw new Error.UntrustedIdentity(message); } else { throw new Error.Failure(message); @@ -766,6 +779,10 @@ public class DbusSignalImpl implements Signal { throw new Error.Failure(message.toString()); } + private static List getRecipientStrings(final Set members) { + return members.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList()); + } + private static Set getSingleRecipientIdentifiers( final Collection recipientStrings, final String localNumber ) throws DBusExecutionException { @@ -802,35 +819,117 @@ public class DbusSignalImpl implements Signal { return name.isEmpty() ? null : name; } + private String emptyIfNull(final String string) { + return string == null ? "" : string; + } + private static String getDeviceObjectPath(String basePath, long deviceId) { return basePath + "/Devices/" + deviceId; } + private void updateDevices() { + List linkedDevices; + try { + linkedDevices = m.getLinkedDevices(); + } catch (IOException e) { + throw new Error.Failure("Failed to get linked devices: " + e.getMessage()); + } + + unExportDevices(); + + linkedDevices.forEach(d -> { + final var object = new DbusSignalDeviceImpl(d); + final var deviceObjectPath = object.getObjectPath(); + exportObject(object); + if (d.isThisDevice()) { + thisDevice = new DBusPath(deviceObjectPath); + } + this.devices.add(new StructDevice(new DBusPath(deviceObjectPath), d.id(), emptyIfNull(d.name()))); + }); + } + + private void unExportDevices() { + this.devices.stream() + .map(StructDevice::getObjectPath) + .map(DBusPath::getPath) + .forEach(connection::unExportObject); + this.devices.clear(); + } + + private static String getGroupObjectPath(String basePath, byte[] groupId) { + return basePath + "/Groups/" + Base64.getEncoder() + .encodeToString(groupId) + .replace("+", "_") + .replace("/", "_") + .replace("=", "_"); + } + + private void updateGroups() { + List groups; + groups = m.getGroups(); + + unExportGroups(); + + groups.forEach(g -> { + final var object = new DbusSignalGroupImpl(g.groupId()); + exportObject(object); + this.groups.add(new StructGroup(new DBusPath(object.getObjectPath()), + g.groupId().serialize(), + emptyIfNull(g.title()))); + }); + } + + private void unExportGroups() { + this.groups.stream().map(StructGroup::getObjectPath).map(DBusPath::getPath).forEach(connection::unExportObject); + 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(); super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Device", - List.of(new DbusProperty<>("Id", device::getId), - new DbusProperty<>("Name", - () -> device.getName() == null ? "" : 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()); @@ -850,4 +949,240 @@ 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; + + public DbusSignalGroupImpl(final GroupId groupId) { + this.groupId = groupId; + super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Group", + List.of(new DbusProperty<>("Id", groupId::serialize), + new DbusProperty<>("Name", () -> emptyIfNull(getGroup().title()), this::setGroupName), + new DbusProperty<>("Description", + () -> 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().messageExpirationTimer(), + this::setMessageExpirationTime), + new DbusProperty<>("Members", + () -> new Variant<>(getRecipientStrings(getGroup().members()), "as")), + new DbusProperty<>("PendingMembers", + () -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")), + new DbusProperty<>("RequestingMembers", + () -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")), + new DbusProperty<>("Admins", + () -> new Variant<>(getRecipientStrings(getGroup().adminMembers()), "as")), + new DbusProperty<>("PermissionAddMember", + () -> getGroup().permissionAddMember().name(), + this::setGroupPermissionAddMember), + new DbusProperty<>("PermissionEditDetails", + () -> getGroup().permissionEditDetails().name(), + this::setGroupPermissionEditDetails), + new DbusProperty<>("PermissionSendMessage", + () -> getGroup().permissionSendMessage().name(), + this::setGroupPermissionSendMessage), + new DbusProperty<>("GroupInviteLink", () -> { + final var groupInviteLinkUrl = getGroup().groupInviteLinkUrl(); + return groupInviteLinkUrl == null ? "" : groupInviteLinkUrl.getUrl(); + })))); + } + + @Override + public String getObjectPath() { + return getGroupObjectPath(objectPath, groupId.serialize()); + } + + @Override + public void quitGroup() throws Error.Failure { + try { + m.quitGroup(groupId, Set.of()); + } catch (GroupNotFoundException | NotAGroupMemberException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (LastGroupAdminException e) { + throw new Error.LastGroupAdmin(e.getMessage()); + } + } + + @Override + public void addMembers(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withMembers(memberIdentifiers).build()); + } + + @Override + public void removeMembers(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withRemoveMembers(memberIdentifiers).build()); + } + + @Override + public void addAdmins(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withAdmins(memberIdentifiers).build()); + } + + @Override + public void removeAdmins(final List recipients) throws Error.Failure { + final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()); + updateGroup(UpdateGroup.newBuilder().withRemoveAdmins(memberIdentifiers).build()); + } + + @Override + public void resetLink() throws Error.Failure { + updateGroup(UpdateGroup.newBuilder().withResetGroupLink(true).build()); + } + + @Override + public void disableLink() throws Error.Failure { + updateGroup(UpdateGroup.newBuilder().withGroupLinkState(GroupLinkState.DISABLED).build()); + } + + @Override + public void enableLink(final boolean requiresApproval) throws Error.Failure { + updateGroup(UpdateGroup.newBuilder() + .withGroupLinkState(requiresApproval + ? GroupLinkState.ENABLED_WITH_APPROVAL + : GroupLinkState.ENABLED) + .build()); + } + + private org.asamk.signal.manager.api.Group getGroup() { + return m.getGroup(groupId); + } + + private void setGroupName(final String name) { + updateGroup(UpdateGroup.newBuilder().withName(name).build()); + } + + private void setGroupDescription(final String description) { + updateGroup(UpdateGroup.newBuilder().withDescription(description).build()); + } + + private void setGroupAvatar(final String avatar) { + updateGroup(UpdateGroup.newBuilder().withAvatarFile(new File(avatar)).build()); + } + + private void setMessageExpirationTime(final int expirationTime) { + updateGroup(UpdateGroup.newBuilder().withExpirationTimer(expirationTime).build()); + } + + private void setGroupPermissionAddMember(final String permission) { + updateGroup(UpdateGroup.newBuilder().withAddMemberPermission(GroupPermission.valueOf(permission)).build()); + } + + private void setGroupPermissionEditDetails(final String permission) { + updateGroup(UpdateGroup.newBuilder() + .withEditDetailsPermission(GroupPermission.valueOf(permission)) + .build()); + } + + private void setGroupPermissionSendMessage(final String permission) { + updateGroup(UpdateGroup.newBuilder() + .withIsAnnouncementGroup(GroupPermission.valueOf(permission) == GroupPermission.ONLY_ADMINS) + .build()); + } + + 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) { + throw new Error.Failure(e.getMessage()); + } + } + + private void updateGroup(final UpdateGroup updateGroup) { + try { + m.updateGroup(groupId, updateGroup); + } catch (IOException 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()); + } + } + } }