From 15da0601272db4b907bf94240b436667870cea87 Mon Sep 17 00:00:00 2001 From: John Freed Date: Fri, 15 Oct 2021 00:09:26 +0200 Subject: [PATCH 1/1] Configuration for Dbus and main Main program subcommand - fix logic to take into account previously unset flags - provide output in json and plain-text formats new Dbus Properties: - ConfigurationReadReceipts - ConfigurationUnidentifiedDeliveryIndicators - ConfigurationTypingIndicators - ConfigurationLinkPreviews removed getConfiguration and setConfiguration methods updated documentation --- man/signal-cli-dbus.5.adoc | 42 ++-- src/main/java/org/asamk/Signal.java | 11 +- .../commands/UpdateConfigurationCommand.java | 60 ++++- .../asamk/signal/dbus/DbusManagerImpl.java | 22 +- .../org/asamk/signal/dbus/DbusProperties.java | 1 + .../org/asamk/signal/dbus/DbusSignalImpl.java | 229 ++++++++++++++++-- 6 files changed, 300 insertions(+), 65 deletions(-) diff --git a/man/signal-cli-dbus.5.adoc b/man/signal-cli-dbus.5.adoc index 1a0fbfaa..e379dcfb 100755 --- a/man/signal-cli-dbus.5.adoc +++ b/man/signal-cli-dbus.5.adoc @@ -364,6 +364,26 @@ removeDevice() -> <>:: Exceptions: Failure +=== Configuration properties +The configuration's object path, which exists only for primary devices, is constructed as follows: +"/org/asamk/Signal/" + DBusNumber + "/Configuration" +* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+) + +Configurations have the following (case-sensitive) properties: +* ConfigurationReadReceipts : should send read receipts (true/false) +* ConfigurationUnidentifiedDeliveryIndicators : should show unidentified delivery indicators (true/false) +* ConfigurationTypingIndicators : should send/show typing indicators (true/false) +* ConfigurationLinkPreviews : should generate link previews (true/false) + +To get a property, use (replacing `--session` with `--system` if needed): +`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Get string:org.asamk.Signal.Configuration string:$PROPERTY_NAME` + +To set a property, use: +`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Set string:org.asamk.Signal.Configuration string:$PROPERTY_NAME variant:$PROPERTY_TYPE:$PROPERTY_VALUE` + +To get all properties, use: +`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.GetAll string:org.asamk.Signal.Configuration` + === Other methods getContactName(number) -> name:: @@ -529,27 +549,7 @@ uploadStickerPack(stickerPackPath) -> url:: * stickerPackPath : Path to the manifest.json file or a zip file in the same directory * url : URL of sticker pack after successful upload -Exception: Failure, IOError - -getConfiguration() -> [readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews] -> <>:: -* readReceipts : Should Signal send read receipts (true/false). -* unidentifiedDeliveryIndicators : Should Signal show unidentified delivery indicators (true/false). -* typingIndicators : Should Signal send/show typing indicators (true/false). -* linkPreviews : Should Signal generate link previews (true/false). - -Gets an array of four booleans as indicated. Only works from primary device. - -Exceptions: IOError, UserError - -setConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews) -> <>:: -* readReceipts : Should Signal send read receipts (true/false). -* unidentifiedDeliveryIndicators : Should Signal show unidentified delivery indicators (true/false). -* typingIndicators : Should Signal send/show typing indicators (true/false). -* linkPreviews : Should Signal generate link previews (true/false). - -Update Signal configurations and sync them to linked devices. Only works from primary device. - -Exceptions: IOError, UserError +Exceptions: Failure version() -> version:: * version : Version string of signal-cli diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 28c192a0..2dba13f8 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -160,10 +160,6 @@ public interface Signal extends DBusInterface { String uploadStickerPack(String stickerPackPath) throws Error.Failure; - void setConfiguration(boolean readReceipts, boolean unidentifiedDeliveryIndicators, boolean typingIndicators, boolean linkPreviews) throws Error.IOError, Error.UserError; - - List getConfiguration(); - void submitRateLimitChallenge(String challenge, String captchaString) throws IOErrorException; class MessageReceived extends DBusSignal { @@ -322,6 +318,13 @@ public interface Signal extends DBusInterface { void removeDevice() throws Error.Failure; } + @DBusProperty(name = "ConfigurationReadReceipts", type = Boolean.class) + @DBusProperty(name = "ConfigurationUnidentifiedDeliveryIndicators", type = Boolean.class) + @DBusProperty(name = "ConfigurationTypingIndicators", type = Boolean.class) + @DBusProperty(name = "ConfigurationLinkPreviews", type = Boolean.class) + interface Configuration extends DBusInterface, Properties { + } + class StructGroup extends Struct { @Position(0) diff --git a/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java index 9ca126d0..1588a72d 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java @@ -3,7 +3,9 @@ package org.asamk.signal.commands; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; +import org.asamk.signal.JsonWriter; import org.asamk.signal.OutputWriter; +import org.asamk.signal.PlainTextWriter; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; @@ -11,6 +13,9 @@ import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotMasterDeviceException; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class UpdateConfigurationCommand implements JsonRpcLocalCommand { @@ -40,12 +45,59 @@ public class UpdateConfigurationCommand implements JsonRpcLocalCommand { public void handleCommand( final Namespace ns, final Manager m, final OutputWriter outputWriter ) throws CommandException { - final var readReceipts = ns.getBoolean("read-receipts"); - final var unidentifiedDeliveryIndicators = ns.getBoolean("unidentified-delivery-indicators"); - final var typingIndicators = ns.getBoolean("typing-indicators"); - final var linkPreviews = ns.getBoolean("link-previews"); + var readReceipts = ns.getBoolean("read-receipts"); + var unidentifiedDeliveryIndicators = ns.getBoolean("unidentified-delivery-indicators"); + var typingIndicators = ns.getBoolean("typing-indicators"); + var linkPreviews = ns.getBoolean("link-previews"); + List configuration = new ArrayList<>(4); + + try { + configuration = m.getConfiguration(); + } catch (IOException | NotMasterDeviceException e) { + throw new CommandException(e.getMessage()); + } + + if (readReceipts == null) { + try { + readReceipts = configuration.get(0); + } catch (NullPointerException e) { + readReceipts = true; + } + } + if (unidentifiedDeliveryIndicators == null) { + try { + unidentifiedDeliveryIndicators = configuration.get(1); + } catch (NullPointerException e) { + unidentifiedDeliveryIndicators = true; + } + } + if (typingIndicators == null) { + try { + typingIndicators = configuration.get(2); + } catch (NullPointerException e) { + typingIndicators = true; + } + } + if (linkPreviews == null) { + try { + linkPreviews = configuration.get(3); + } catch (NullPointerException e) { + linkPreviews = true; + } + } try { m.updateConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + if (outputWriter instanceof JsonWriter) { + final var writer = (JsonWriter) outputWriter; + writer.write(Map.of("readReceipts", readReceipts, "unidentifiedDeliveryIndicators", unidentifiedDeliveryIndicators, "typingIndicators", typingIndicators, "linkPreviews", linkPreviews)); + } else { + final var writer = (PlainTextWriter) outputWriter; + writer.println("readReceipts=" + readReceipts + + "\nunidentifiedDeliveryIndicators=" + unidentifiedDeliveryIndicators + + "\ntypingIndicators=" + typingIndicators + + "\nlinkPreviews=" + linkPreviews + ); + } } catch (IOException e) { throw new IOErrorException("UpdateAccount error: " + e.getMessage(), e); } catch (NotMasterDeviceException e) { diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index a8f071f7..eb5b590c 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -7,6 +7,7 @@ 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.Configuration; import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.Group; import org.asamk.signal.manager.api.Identity; @@ -100,24 +101,21 @@ public class DbusManagerImpl implements Manager { } } + + @Override + public List getConfiguration() { + throw new UnsupportedOperationException(); + } + @Override public void updateConfiguration( final Boolean readReceipts, final Boolean unidentifiedDeliveryIndicators, final Boolean typingIndicators, final Boolean linkPreviews - ) throws IOException { - signal.setConfiguration( - readReceipts, - unidentifiedDeliveryIndicators, - typingIndicators, - linkPreviews - ); - } - - @Override - public List getConfiguration() { - return signal.getConfiguration(); + ) + { + throw new UnsupportedOperationException(); } @Override diff --git a/src/main/java/org/asamk/signal/dbus/DbusProperties.java b/src/main/java/org/asamk/signal/dbus/DbusProperties.java index bbe01d6b..4700deee 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusProperties.java +++ b/src/main/java/org/asamk/signal/dbus/DbusProperties.java @@ -67,4 +67,5 @@ public abstract class DbusProperties implements Properties { return o instanceof Variant ? (Variant) o : new Variant<>(o); })); } + } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index c6e3273a..1f1f954f 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -2,12 +2,14 @@ package org.asamk.signal.dbus; import org.asamk.Signal; import org.asamk.signal.BaseConfig; +import org.asamk.signal.commands.exceptions.CommandException; 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.Configuration; import org.asamk.signal.manager.api.Identity; import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.RecipientIdentifier; @@ -29,6 +31,8 @@ import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.types.Variant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; @@ -65,6 +69,8 @@ public class DbusSignalImpl implements Signal { 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; this.connection = connection; @@ -74,10 +80,13 @@ public class DbusSignalImpl implements Signal { public void initObjects() { updateDevices(); updateGroups(); + updateConfiguration(); } public void close() { unExportDevices(); + unExportGroups(); + unExportConfiguration(); } @Override @@ -702,30 +711,6 @@ public class DbusSignalImpl implements Signal { } } - @Override - public void setConfiguration(boolean readReceipts, boolean unidentifiedDeliveryIndicators, boolean typingIndicators, boolean linkPreviews) { - try { - m.updateConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); - } catch (IOException e) { - throw new Error.IOError("UpdateAccount error: " + e.getMessage()); - } catch (NotMasterDeviceException e) { - throw new Error.UserError("This command doesn't work on linked devices."); - } - } - - @Override - public List getConfiguration() { - List config = new ArrayList<>(4); - try { - config = m.getConfiguration(); - } catch (IOException e) { - throw new Error.IOError("Configuration storage error: " + e.getMessage()); - } catch (NotMasterDeviceException e) { - throw new Error.UserError("This command doesn't work on linked devices."); - } - return config; - } - private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException { var error = ErrorUtils.getErrorMessageFromSendMessageResult(result); @@ -852,6 +837,7 @@ public class DbusSignalImpl implements Signal { final var deviceObjectPath = object.getObjectPath(); try { connection.exportObject(object); + logger.info("Exported dbus object: " + deviceObjectPath); } catch (DBusException e) { e.printStackTrace(); } @@ -888,6 +874,7 @@ public class DbusSignalImpl implements Signal { final var object = new DbusSignalGroupImpl(g.getGroupId()); try { connection.exportObject(object); + logger.info("Exported dbus object: " + object.getObjectPath()); } catch (DBusException e) { e.printStackTrace(); } @@ -902,6 +889,82 @@ public class DbusSignalImpl implements Signal { this.groups.clear(); } + private void unExportConfiguration() { + final var object = getConfigurationObjectPath(objectPath); + connection.unExportObject(object); + } + + private static String getConfigurationObjectPath(String basePath) { + return basePath + "/Configuration"; + } + + private void updateConfiguration() { + + Boolean readReceipts = null; + Boolean unidentifiedDeliveryIndicators = null; + Boolean typingIndicators = null; + Boolean linkPreviews = null; + List configuration = new ArrayList<>(4); + + try { + configuration = m.getConfiguration(); + } catch (NotMasterDeviceException e) { + logger.info("Not exporting Configuration for " + m.getSelfNumber() + ": " + e.getMessage()); + return; + } catch (IOException e) { + throw new Error.IOError(objectPath + e.getMessage()); + } catch (NullPointerException e) { + logger.info("No configuration found, creating one for " + m.getSelfNumber() + ": " + e.getMessage()); + readReceipts = true; + unidentifiedDeliveryIndicators = true; + typingIndicators = true; + linkPreviews = true; + } + if (readReceipts == null) { + try { + readReceipts = configuration.get(0); + } catch (NullPointerException e) { + readReceipts = true; + } + } + if (unidentifiedDeliveryIndicators == null) { + try { + unidentifiedDeliveryIndicators = configuration.get(1); + } catch (NullPointerException e) { + unidentifiedDeliveryIndicators = true; + } + } + if (typingIndicators == null) { + try { + typingIndicators = configuration.get(2); + } catch (NullPointerException e) { + typingIndicators = true; + } + } + if (linkPreviews == null) { + try { + linkPreviews = configuration.get(3); + } catch (NullPointerException e) { + linkPreviews = true; + } + } + + try { + unExportConfiguration(); + m.updateConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + final var object = new DbusSignalConfigurationImpl(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + connection.exportObject(object); + logger.info("Exported dbus object: " + objectPath + "/Configuration"); + } catch (NotMasterDeviceException ignore) { + /* already caught */ + } catch (IOException e) { + throw new Error.IOError(objectPath + e.getMessage()); + } catch (DBusException e) { + e.printStackTrace(); + } + } + + public class DbusSignalDeviceImpl extends DbusProperties implements Signal.Device { private final org.asamk.signal.manager.api.Device device; @@ -944,6 +1007,124 @@ public class DbusSignalImpl implements Signal { } } + public class DbusSignalConfigurationImpl extends DbusProperties implements Signal.Configuration { + + private final Boolean readReceipts; + private final Boolean unidentifiedDeliveryIndicators; + private final Boolean typingIndicators; + private final Boolean linkPreviews; + + public DbusSignalConfigurationImpl(final Boolean readReceipts, final Boolean unidentifiedDeliveryIndicators, final Boolean typingIndicators, final Boolean linkPreviews) { + this.readReceipts = readReceipts; + this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators; + this.typingIndicators = typingIndicators; + this.linkPreviews = linkPreviews; + super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Configuration", + List.of(new DbusProperty<>("ConfigurationReadReceipts", () -> getReadReceipts(), this::setReadReceipts), + new DbusProperty<>("ConfigurationUnidentifiedDeliveryIndicators", () -> getUnidentifiedDeliveryIndicators(), this::setUnidentifiedDeliveryIndicators), + new DbusProperty<>("ConfigurationTypingIndicators", () -> getTypingIndicators(), this::setTypingIndicators), + new DbusProperty<>("ConfigurationLinkPreviews", () -> 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 { + if (readReceipts == null) { + readReceipts = m.getConfiguration().get(0); + } + if (unidentifiedDeliveryIndicators == null) { + unidentifiedDeliveryIndicators = m.getConfiguration().get(1); + } + if (typingIndicators == null) { + typingIndicators = m.getConfiguration().get(2); + } + if (linkPreviews == null) { + linkPreviews = m.getConfiguration().get(3); + } + m.updateConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + } catch (IOException e) { + throw new Error.IOError("UpdateAccount error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + } + + public List getConfiguration() { + List config = new ArrayList<>(4); + try { + config = m.getConfiguration(); + } catch (IOException e) { + throw new Error.IOError("Configuration storage error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + return config; + } + + public Boolean getReadReceipts() { + try { + return m.getConfiguration().get(0); + } catch (IOException e) { + throw new Error.IOError("Configuration storage error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + } + + public Boolean getUnidentifiedDeliveryIndicators() { + try { + return m.getConfiguration().get(1); + } catch (IOException e) { + throw new Error.IOError("Configuration storage error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + } + + public Boolean getTypingIndicators() { + try { + return m.getConfiguration().get(2); + } catch (IOException e) { + throw new Error.IOError("Configuration storage error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + } + + public Boolean getLinkPreviews() { + try { + return m.getConfiguration().get(3); + } catch (IOException e) { + throw new Error.IOError("Configuration storage error: " + e.getMessage()); + } catch (NotMasterDeviceException e) { + throw new Error.UserError("This command doesn't work on linked devices."); + } + } + } + public class DbusSignalGroupImpl extends DbusProperties implements Signal.Group { private final GroupId groupId; -- 2.51.0