]> nmode's Git Repositories - signal-cli/commitdiff
Allow calling signal-cli without -u flag
authorAsamK <asamk@gmx.de>
Fri, 15 Jan 2021 17:28:54 +0000 (18:28 +0100)
committerAsamK <asamk@gmx.de>
Sat, 16 Jan 2021 08:00:03 +0000 (09:00 +0100)
For daemon command all local users will be exposed as dbus objects
If only one local user exists, all other commands will use that user,
otherwise a user has to be specified.

19 files changed:
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/Cli.java
src/main/java/org/asamk/signal/DbusConfig.java
src/main/java/org/asamk/signal/Main.java
src/main/java/org/asamk/signal/commands/DaemonCommand.java
src/main/java/org/asamk/signal/commands/DbusCommand.java
src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java
src/main/java/org/asamk/signal/commands/JoinGroupCommand.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/MultiLocalCommand.java [new file with mode: 0644]
src/main/java/org/asamk/signal/commands/ReceiveCommand.java
src/main/java/org/asamk/signal/commands/RemovePinCommand.java
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/commands/SendContactsCommand.java
src/main/java/org/asamk/signal/commands/SetPinCommand.java
src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java
src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/manager/RegistrationManager.java

index bd51033fada4fb6fd420217bec7e1a1c817ce71a..5b1653c0cad8adc7ff4876276a261e31a0cdc9bb 100644 (file)
@@ -44,6 +44,11 @@ Make sure you have full read/write access to the given directory.
 Specify your phone number, that will be your identifier.
 The phone number must include the country calling code, i.e. the number must start with a "+" sign.
 
+This flag must not be given for the `link` command.
+It is optional for the `daemon` command.
+For all other commands it is only optional if there is exactly one local user in the
+config directory.
+
 *--dbus*::
 Make request via user dbus.
 
@@ -352,6 +357,8 @@ The path of the manifest.json or a zip file containing the sticker pack you wish
 === daemon
 
 signal-cli can run in daemon mode and provides an experimental dbus interface.
+If no `-u` username is given, all local users will be exported as separate dbus
+objects under the same bus name.
 
 *--system*::
 Use DBus system bus instead of user bus.
@@ -390,6 +397,11 @@ signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER
 Trust new key, without having verified it. Only use this if you don't care about security::
 signal-cli -u USERNAME trust -a NUMBER
 
+== Exit codes
+* *1*: Error is probably caused and fixable by the user
+* *2*: Some unexpected error
+* *3*: Server or IO error
+
 == Files
 
 The password and cryptographic keys are created when registering and stored in the current users home directory, the directory can be changed with *--config*:
index a0349a3121bc386e127ea7f2df3a94a4a3716170..4579f3f5ee272cdbcc54a73f5002556cdc55efd4 100644 (file)
@@ -8,9 +8,9 @@ import org.asamk.signal.commands.Commands;
 import org.asamk.signal.commands.DbusCommand;
 import org.asamk.signal.commands.ExtendedDbusCommand;
 import org.asamk.signal.commands.LocalCommand;
+import org.asamk.signal.commands.MultiLocalCommand;
 import org.asamk.signal.commands.ProvisioningCommand;
 import org.asamk.signal.commands.RegistrationCommand;
-import org.asamk.signal.dbus.DbusSignalImpl;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotRegisteredException;
 import org.asamk.signal.manager.ProvisioningManager;
@@ -21,10 +21,14 @@ import org.freedesktop.dbus.connections.impl.DBusConnection;
 import org.freedesktop.dbus.exceptions.DBusException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 public class Cli {
 
@@ -40,15 +44,16 @@ public class Cli {
         Command command = getCommand();
         if (command == null) {
             logger.error("Command not implemented!");
-            return 2;
+            return 1;
         }
 
+        String username = ns.getString("username");
+
         if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
-            return initDbusClient(command, ns.getBoolean("dbus_system"));
+            // If username is null, it will connect to the default object path
+            return initDbusClient(command, username, ns.getBoolean("dbus_system"));
         }
 
-        final String username = ns.getString("username");
-
         final File dataPath;
         String config = ns.getString("config");
         if (config != null) {
@@ -65,59 +70,150 @@ public class Cli {
                     + " because the required native library dependency is missing: libzkgroup");
         }
 
-        if (username == null) {
-            ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
-            return handleCommand(command, pm);
+        if (command instanceof ProvisioningCommand) {
+            if (username != null) {
+                System.err.println("You cannot specify a username (phone number) when linking");
+                return 1;
+            }
+
+            return handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceConfiguration);
         }
 
-        if (command instanceof RegistrationCommand) {
-            final RegistrationManager manager;
-            try {
-                manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
-            } catch (Throwable e) {
-                logger.error("Error loading or creating state file: {}", e.getMessage());
+        if (username == null) {
+            List<String> usernames = Manager.getAllLocalUsernames(dataPath);
+            if (usernames.size() == 0) {
+                System.err.println("No local users found, you first need to register or link an account");
                 return 1;
             }
-            try (RegistrationManager m = manager) {
-                return handleCommand(command, m);
-            } catch (Exception e) {
-                logger.error("Cleanup failed", e);
-                return 2;
+
+            if (command instanceof MultiLocalCommand) {
+                return handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceConfiguration, usernames);
+            }
+
+            if (usernames.size() > 1) {
+                System.err.println("Multiple users found, you need to specify a username (phone number) with -u");
+                return 1;
             }
+
+            username = usernames.get(0);
+        } else if (!PhoneNumberFormatter.isValidNumber(username, null)) {
+            System.err.println("Invalid username (phone number), make sure you include the country code.");
+            return 1;
         }
 
-        Manager manager;
+        if (command instanceof RegistrationCommand) {
+            return handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceConfiguration);
+        }
+
+        if (!(command instanceof LocalCommand)) {
+            System.err.println("Command only works via dbus");
+            return 1;
+        }
+
+        return handleLocalCommand((LocalCommand) command, username, dataPath, serviceConfiguration);
+    }
+
+    private int handleProvisioningCommand(
+            final ProvisioningCommand command,
+            final File dataPath,
+            final SignalServiceConfiguration serviceConfiguration
+    ) {
+        ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
+        return command.handleCommand(ns, pm);
+    }
+
+    private int handleRegistrationCommand(
+            final RegistrationCommand command,
+            final String username,
+            final File dataPath,
+            final SignalServiceConfiguration serviceConfiguration
+    ) {
+        final RegistrationManager manager;
         try {
-            manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
-        } catch (NotRegisteredException e) {
-            System.err.println("User is not registered.");
-            return 0;
+            manager = RegistrationManager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
         } catch (Throwable e) {
-            logger.error("Error loading state file: {}", e.getMessage());
-            return 1;
+            logger.error("Error loading or creating state file: {}", e.getMessage());
+            return 2;
+        }
+        try (RegistrationManager m = manager) {
+            return command.handleCommand(ns, m);
+        } catch (IOException e) {
+            logger.error("Cleanup failed", e);
+            return 2;
         }
+    }
 
-        try (Manager m = manager) {
-            try {
-                m.checkAccountState();
-            } catch (IOException e) {
-                logger.error("Error while checking account: {}", e.getMessage());
-                return 1;
+    private int handleLocalCommand(
+            final LocalCommand command,
+            final String username,
+            final File dataPath,
+            final SignalServiceConfiguration serviceConfiguration
+    ) {
+        try (Manager m = loadManager(username, dataPath, serviceConfiguration)) {
+            if (m == null) {
+                return 2;
             }
 
-            return handleCommand(command, m);
+            return command.handleCommand(ns, m);
         } catch (IOException e) {
             logger.error("Cleanup failed", e);
             return 2;
         }
     }
 
+    private int handleMultiLocalCommand(
+            final MultiLocalCommand command,
+            final File dataPath,
+            final SignalServiceConfiguration serviceConfiguration,
+            final List<String> usernames
+    ) {
+        final List<Manager> managers = usernames.stream()
+                .map(u -> loadManager(u, dataPath, serviceConfiguration))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        int result = command.handleCommand(ns, managers);
+
+        for (Manager m : managers) {
+            try {
+                m.close();
+            } catch (IOException e) {
+                logger.warn("Cleanup failed", e);
+            }
+        }
+        return result;
+    }
+
+    private Manager loadManager(
+            final String username, final File dataPath, final SignalServiceConfiguration serviceConfiguration
+    ) {
+        Manager manager;
+        try {
+            manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
+        } catch (NotRegisteredException e) {
+            logger.error("User " + username + " is not registered.");
+            return null;
+        } catch (Throwable e) {
+            logger.error("Error loading state file for user " + username + ": {}", e.getMessage());
+            return null;
+        }
+
+        try {
+            manager.checkAccountState();
+        } catch (IOException e) {
+            logger.error("Error while checking account " + username + ": {}", e.getMessage());
+            return null;
+        }
+
+        return manager;
+    }
+
     private Command getCommand() {
         String commandKey = ns.getString("command");
         return Commands.getCommand(commandKey);
     }
 
-    private int initDbusClient(final Command command, final boolean systemBus) {
+    private int initDbusClient(final Command command, final String username, final boolean systemBus) {
         try {
             DBusConnection.DBusBusType busType;
             if (systemBus) {
@@ -126,8 +222,8 @@ public class Cli {
                 busType = DBusConnection.DBusBusType.SESSION;
             }
             try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) {
-                Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME,
-                        DbusConfig.SIGNAL_OBJECTPATH,
+                Signal ts = dBusConn.getRemoteObject(DbusConfig.getBusname(),
+                        DbusConfig.getObjectPath(username),
                         Signal.class);
 
                 return handleCommand(command, ts, dBusConn);
@@ -149,33 +245,6 @@ public class Cli {
         }
     }
 
-    private int handleCommand(Command command, ProvisioningManager pm) {
-        if (command instanceof ProvisioningCommand) {
-            return ((ProvisioningCommand) command).handleCommand(ns, pm);
-        } else {
-            System.err.println("Command only works with a username");
-            return 1;
-        }
-    }
-
-    private int handleCommand(Command command, RegistrationManager m) {
-        if (command instanceof RegistrationCommand) {
-            return ((RegistrationCommand) command).handleCommand(ns, m);
-        }
-        return 1;
-    }
-
-    private int handleCommand(Command command, Manager m) {
-        if (command instanceof LocalCommand) {
-            return ((LocalCommand) command).handleCommand(ns, m);
-        } else if (command instanceof DbusCommand) {
-            return ((DbusCommand) command).handleCommand(ns, new DbusSignalImpl(m));
-        } else {
-            System.err.println("Command only works via dbus");
-            return 1;
-        }
-    }
-
     /**
      * Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
      * - $HOME/.config/signal
index c0d2317511eb6e2b96dd7b4df9f93a016fde9179..eb457c392318b012bafee897ca5263546f735468 100644 (file)
@@ -2,6 +2,22 @@ package org.asamk.signal;
 
 public class DbusConfig {
 
-    public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
-    public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
+    private static final String SIGNAL_BUSNAME = "org.asamk.Signal";
+    private static final String SIGNAL_OBJECT_BASE_PATH = "/org/asamk/Signal";
+
+    public static String getBusname() {
+        return SIGNAL_BUSNAME;
+    }
+
+    public static String getObjectPath() {
+        return getObjectPath(null);
+    }
+
+    public static String getObjectPath(String username) {
+        if (username == null) {
+            return SIGNAL_OBJECT_BASE_PATH;
+        }
+
+        return SIGNAL_OBJECT_BASE_PATH + "/" + username.replace('+', '_');
+    }
 }
index a8fd3e2bd56a94ed5714c2733f91372c01f51765..ddcf47e10ab6056adc9a03fc2faed97b45ab96e8 100644 (file)
@@ -30,7 +30,6 @@ import org.asamk.signal.commands.Commands;
 import org.asamk.signal.manager.LibSignalLogger;
 import org.asamk.signal.util.SecurityProvider;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 
 import java.security.Security;
 import java.util.Map;
@@ -57,7 +56,7 @@ public class Main {
 
         Namespace ns = parseArgs(args);
         if (ns == null) {
-            System.exit(1);
+            System.exit(2);
         }
 
         int res = new Cli(ns).init();
@@ -94,24 +93,6 @@ public class Main {
             return null;
         }
 
-        if ("link".equals(ns.getString("command"))) {
-            if (ns.getString("username") != null) {
-                parser.printUsage();
-                System.err.println("You cannot specify a username (phone number) when linking");
-                System.exit(2);
-            }
-        } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
-            if (ns.getString("username") == null) {
-                parser.printUsage();
-                System.err.println("You need to specify a username (phone number)");
-                System.exit(2);
-            }
-            if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"), null)) {
-                System.err.println("Invalid username (phone number), make sure you include the country code.");
-                System.exit(2);
-            }
-        }
-
         if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
             System.err.println("You cannot specify recipients by phone number and groups at the same time");
             System.exit(2);
@@ -152,8 +133,9 @@ public class Main {
         parser.addArgument("--config")
                 .help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
 
+        parser.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification.");
+
         MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
-        mut.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification.");
         mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue());
         mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue());
 
index 7ebecea6fa5cdfc28272f9ce1285f594755f0945..53f45ca3addc1f7213d63d8c49a86265d67870ab 100644 (file)
@@ -4,6 +4,7 @@ import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
+import org.asamk.signal.DbusConfig;
 import org.asamk.signal.DbusReceiveMessageHandler;
 import org.asamk.signal.JsonDbusReceiveMessageHandler;
 import org.asamk.signal.dbus.DbusSignalImpl;
@@ -14,13 +15,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import static org.asamk.signal.DbusConfig.SIGNAL_BUSNAME;
-import static org.asamk.signal.DbusConfig.SIGNAL_OBJECTPATH;
-import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
-
-public class DaemonCommand implements LocalCommand {
+public class DaemonCommand implements MultiLocalCommand {
 
     private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class);
 
@@ -46,46 +45,98 @@ public class DaemonCommand implements LocalCommand {
             logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
         }
 
-        DBusConnection conn = null;
-        try {
+        boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
+
+        DBusConnection.DBusBusType busType;
+        if (ns.getBoolean("system")) {
+            busType = DBusConnection.DBusBusType.SYSTEM;
+        } else {
+            busType = DBusConnection.DBusBusType.SESSION;
+        }
+
+        try (DBusConnection conn = DBusConnection.getConnection(busType)) {
+            String objectPath = DbusConfig.getObjectPath();
+            Thread t = run(conn, objectPath, m, ignoreAttachments, inJson);
+
+            conn.requestBusName(DbusConfig.getBusname());
+
             try {
-                DBusConnection.DBusBusType busType;
-                if (ns.getBoolean("system")) {
-                    busType = DBusConnection.DBusBusType.SYSTEM;
-                } else {
-                    busType = DBusConnection.DBusBusType.SESSION;
-                }
-                conn = DBusConnection.getConnection(busType);
-                conn.exportObject(SIGNAL_OBJECTPATH, new DbusSignalImpl(m));
-                conn.requestBusName(SIGNAL_BUSNAME);
-            } catch (UnsatisfiedLinkError e) {
-                System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
-                return 1;
-            } catch (DBusException e) {
-                e.printStackTrace();
-                return 2;
+                t.join();
+            } catch (InterruptedException ignored) {
             }
-            boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
-            try {
-                m.receiveMessages(1,
-                        TimeUnit.HOURS,
-                        false,
-                        ignoreAttachments,
-                        inJson
-                                ? new JsonDbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH)
-                                : new DbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH));
-                return 0;
-            } catch (IOException e) {
-                System.err.println("Error while receiving messages: " + e.getMessage());
-                return 3;
-            } catch (AssertionError e) {
-                handleAssertionError(e);
-                return 1;
+            return 0;
+        } catch (DBusException | IOException e) {
+            logger.error("Dbus command failed", e);
+            return 2;
+        }
+    }
+
+    @Override
+    public int handleCommand(final Namespace ns, final List<Manager> managers) {
+        boolean inJson = ns.getString("output").equals("json") || ns.getBoolean("json");
+
+        // TODO delete later when "json" variable is removed
+        if (ns.getBoolean("json")) {
+            logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
+        }
+
+        boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
+
+        DBusConnection.DBusBusType busType;
+        if (ns.getBoolean("system")) {
+            busType = DBusConnection.DBusBusType.SYSTEM;
+        } else {
+            busType = DBusConnection.DBusBusType.SESSION;
+        }
+
+        try (DBusConnection conn = DBusConnection.getConnection(busType)) {
+            List<Thread> receiveThreads = new ArrayList<>();
+            for (Manager m : managers) {
+                String objectPath = DbusConfig.getObjectPath(m.getUsername());
+                Thread thread = run(conn, objectPath, m, ignoreAttachments, inJson);
+                receiveThreads.add(thread);
             }
-        } finally {
-            if (conn != null) {
-                conn.disconnect();
+
+            conn.requestBusName(DbusConfig.getBusname());
+
+            for (Thread t : receiveThreads) {
+                try {
+                    t.join();
+                } catch (InterruptedException ignored) {
+                }
             }
+            return 0;
+        } catch (DBusException | IOException e) {
+            logger.error("Dbus command failed", e);
+            return 2;
         }
     }
+
+    private Thread run(
+            DBusConnection conn, String objectPath, Manager m, boolean ignoreAttachments, boolean inJson
+    ) throws DBusException {
+        conn.exportObject(objectPath, new DbusSignalImpl(m));
+
+        final Thread thread = new Thread(() -> {
+            while (true) {
+                try {
+                    m.receiveMessages(1,
+                            TimeUnit.HOURS,
+                            false,
+                            ignoreAttachments,
+                            inJson
+                                    ? new JsonDbusReceiveMessageHandler(m, conn, objectPath)
+                                    : new DbusReceiveMessageHandler(m, conn, objectPath));
+                } catch (IOException e) {
+                    logger.warn("Receiving messages failed, retrying", e);
+                }
+            }
+        });
+
+        logger.info("Exported dbus object: " + objectPath);
+
+        thread.start();
+
+        return thread;
+    }
 }
index 4dee75b2b0e58485cc36b18f804826a38e6e0d2c..1b3a0268652675349536deea247cb5b6ee863c1a 100644 (file)
@@ -3,8 +3,14 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 
 import org.asamk.Signal;
+import org.asamk.signal.dbus.DbusSignalImpl;
+import org.asamk.signal.manager.Manager;
 
-public interface DbusCommand extends Command {
+public interface DbusCommand extends LocalCommand {
 
     int handleCommand(Namespace ns, Signal signal);
+
+    default int handleCommand(final Namespace ns, final Manager m) {
+        return handleCommand(ns, new DbusSignalImpl(m));
+    }
 }
index 8b694cb620647c84176ec9eac9f40a1f430ef2c7..f17fb96725000f847f44c81d17682b086d992e8b 100644 (file)
@@ -50,7 +50,7 @@ public class GetUserStatusCommand implements LocalCommand {
             registered = m.areUsersRegistered(new HashSet<>(ns.getList("number")));
         } catch (IOException e) {
             System.err.println("Unable to check if users are registered");
-            return 1;
+            return 3;
         }
 
         // Output
index c5975b0cfe0b7d1a2851d778fa1a5efcfa42b06a..bfe46650c8d006853c607174aacd0aa89fdec348 100644 (file)
@@ -3,7 +3,6 @@ package org.asamk.signal.commands;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
 
-import org.asamk.Signal;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
@@ -35,15 +34,15 @@ public class JoinGroupCommand implements LocalCommand {
             linkUrl = GroupInviteLinkUrl.fromUri(uri);
         } catch (GroupInviteLinkUrl.InvalidGroupLinkException e) {
             System.err.println("Group link is invalid: " + e.getMessage());
-            return 2;
+            return 1;
         } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
             System.err.println("Group link was created with an incompatible version: " + e.getMessage());
-            return 2;
+            return 1;
         }
 
         if (linkUrl == null) {
             System.err.println("Link is not a signal group invitation link");
-            return 2;
+            return 1;
         }
 
         try {
@@ -64,16 +63,13 @@ public class JoinGroupCommand implements LocalCommand {
         } catch (IOException e) {
             e.printStackTrace();
             handleIOException(e);
-            return 1;
-        } catch (Signal.Error.AttachmentInvalid e) {
-            System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
-            return 1;
+            return 3;
         } catch (DBusExecutionException e) {
             System.err.println("Failed to send message: " + e.getMessage());
-            return 1;
+            return 2;
         } catch (GroupLinkNotActiveException e) {
             System.err.println("Group link is not valid: " + e.getMessage());
-            return 2;
+            return 1;
         }
     }
 }
index debc2a4211ffb6941aae2f51c2872344e7b8f450..2a267a9a2c757537572a0fbda0cf661e0de23fa3 100644 (file)
@@ -1,5 +1,10 @@
 package org.asamk.signal.commands;
 
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import net.sourceforge.argparse4j.impl.Arguments;
 import net.sourceforge.argparse4j.inf.Namespace;
 import net.sourceforge.argparse4j.inf.Subparser;
@@ -9,11 +14,6 @@ import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
 import org.asamk.signal.manager.storage.groups.GroupInfo;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -23,7 +23,8 @@ import java.util.stream.Collectors;
 public class ListGroupsCommand implements LocalCommand {
 
     private static Set<String> resolveMembers(Manager m, Set<SignalServiceAddress> addresses) {
-        return addresses.stream().map(m::resolveSignalServiceAddress)
+        return addresses.stream()
+                .map(m::resolveSignalServiceAddress)
                 .map(SignalServiceAddress::getLegacyIdentifier)
                 .collect(Collectors.toSet());
     }
@@ -34,7 +35,7 @@ public class ListGroupsCommand implements LocalCommand {
             System.out.println();
         } catch (IOException e) {
             System.err.println(e.getMessage());
-            return 1;
+            return 3;
         }
 
         return 0;
@@ -65,7 +66,8 @@ public class ListGroupsCommand implements LocalCommand {
 
     @Override
     public void attachToSubparser(final Subparser subparser) {
-        subparser.addArgument("-d", "--detailed").action(Arguments.storeTrue())
+        subparser.addArgument("-d", "--detailed")
+                .action(Arguments.storeTrue())
                 .help("List the members and group invite links of each group. If output=json, then this is always set");
 
         subparser.help("List group information including names, ids, active status, blocked status and members");
@@ -114,10 +116,16 @@ public class ListGroupsCommand implements LocalCommand {
         public Set<String> requestingMembers;
         public String groupInviteLink;
 
-        public JsonGroup(String id, String name, boolean isMember, boolean isBlocked,
-                         Set<String> members, Set<String> pendingMembers,
-                         Set<String> requestingMembers, String groupInviteLink)
-        {
+        public JsonGroup(
+                String id,
+                String name,
+                boolean isMember,
+                boolean isBlocked,
+                Set<String> members,
+                Set<String> pendingMembers,
+                Set<String> requestingMembers,
+                String groupInviteLink
+        ) {
             this.id = id;
             this.name = name;
             this.isMember = isMember;
diff --git a/src/main/java/org/asamk/signal/commands/MultiLocalCommand.java b/src/main/java/org/asamk/signal/commands/MultiLocalCommand.java
new file mode 100644 (file)
index 0000000..e8ee8e1
--- /dev/null
@@ -0,0 +1,17 @@
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+
+import org.asamk.signal.manager.Manager;
+
+import java.util.List;
+
+public interface MultiLocalCommand extends LocalCommand {
+
+    int handleCommand(Namespace ns, List<Manager> m);
+
+    @Override
+    default int handleCommand(final Namespace ns, final Manager m) {
+        return handleCommand(ns, List.of(m));
+    }
+}
index e2002258a245db55da73598a56ba44a23808d392..30518cb44f9dc5711dc7e2d4654cf002cb7c6416 100644 (file)
@@ -141,12 +141,9 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
                     System.out.println();
                 }
             });
-        } catch (UnsatisfiedLinkError e) {
-            System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
-            return 1;
         } catch (DBusException e) {
             e.printStackTrace();
-            return 1;
+            return 2;
         }
         while (true) {
             try {
index ada9c446699ebc6787ef612de11d13cff56d8585..52b111eac1aab9687d0e8944d09e1e8ff162ddbc 100644 (file)
@@ -20,7 +20,10 @@ public class RemovePinCommand implements LocalCommand {
         try {
             m.setRegistrationLockPin(Optional.absent());
             return 0;
-        } catch (IOException | UnauthenticatedResponseException e) {
+        } catch (UnauthenticatedResponseException e) {
+            System.err.println("Remove pin error: " + e.getMessage());
+            return 2;
+        } catch (IOException e) {
             System.err.println("Remove pin error: " + e.getMessage());
             return 3;
         }
index a4bdc89b01e3fc2c14714ba36b03338c5a5f22a4..235fc53cb4638125586362badf0034342ae20680 100644 (file)
@@ -8,6 +8,7 @@ import org.asamk.Signal;
 import org.asamk.signal.manager.groups.GroupIdFormatException;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
+import org.freedesktop.dbus.errors.UnknownObject;
 import org.freedesktop.dbus.exceptions.DBusExecutionException;
 
 import java.io.IOException;
@@ -50,7 +51,7 @@ public class SendCommand implements DbusCommand {
                 return 1;
             } catch (DBusExecutionException e) {
                 System.err.println("Failed to send message: " + e.getMessage());
-                return 1;
+                return 2;
             }
         }
 
@@ -89,7 +90,7 @@ public class SendCommand implements DbusCommand {
             return 1;
         } catch (DBusExecutionException e) {
             System.err.println("Failed to send message: " + e.getMessage());
-            return 1;
+            return 2;
         }
 
         try {
@@ -99,9 +100,12 @@ public class SendCommand implements DbusCommand {
         } catch (AssertionError e) {
             handleAssertionError(e);
             return 1;
+        } catch (UnknownObject e) {
+            System.err.println("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
+            return 1;
         } catch (DBusExecutionException e) {
             System.err.println("Failed to send message: " + e.getMessage());
-            return 1;
+            return 2;
         }
     }
 }
index aaca283ab344b541a91fe457d86573243e576437..f5eacf8129f51d772850ab9bd70713e2714b0b2d 100644 (file)
@@ -20,7 +20,10 @@ public class SendContactsCommand implements LocalCommand {
         try {
             m.sendContacts();
             return 0;
-        } catch (IOException | UntrustedIdentityException e) {
+        } catch (UntrustedIdentityException e) {
+            System.err.println("SendContacts error: " + e.getMessage());
+            return 2;
+        } catch (IOException e) {
             System.err.println("SendContacts error: " + e.getMessage());
             return 3;
         }
index ac601b3b11a7797c465cd4a7aebd51af7db2dfd8..dbdc83dfaa2d0c0131c647f4ef66fe54b3040261 100644 (file)
@@ -23,7 +23,10 @@ public class SetPinCommand implements LocalCommand {
             String registrationLockPin = ns.getString("registrationLockPin");
             m.setRegistrationLockPin(Optional.of(registrationLockPin));
             return 0;
-        } catch (IOException | UnauthenticatedResponseException e) {
+        } catch (UnauthenticatedResponseException e) {
+            System.err.println("Set pin error: " + e.getMessage());
+            return 2;
+        } catch (IOException e) {
             System.err.println("Set pin error: " + e.getMessage());
             return 3;
         }
index de8564f05a83eabac586bdfbd568b731ee8e08fc..736190b3d3bd7c7117b2eda961f6222f1f9bef2f 100644 (file)
@@ -69,7 +69,7 @@ public class UpdateGroupCommand implements DbusCommand {
             return 1;
         } catch (DBusExecutionException e) {
             System.err.println("Failed to send message: " + e.getMessage());
-            return 1;
+            return 2;
         }
     }
 }
index f9f5d95bb15894150bedfa1e3f5b5df750c3a980..725f91b3222d15349a644aa9c82b68e3303d5d96 100644 (file)
@@ -29,7 +29,7 @@ public class UploadStickerPackCommand implements LocalCommand {
             return 3;
         } catch (StickerPackInvalidException e) {
             System.err.println("Invalid sticker pack: " + e.getMessage());
-            return 3;
+            return 1;
         }
     }
 }
index a0f6aa532a1ef2313eb4deaf1fdf5b3a3eb3c1a3..09e514459e9759c417bb42b3bf810386368b4673 100644 (file)
@@ -288,6 +288,22 @@ public class Manager implements Closeable {
         return new Manager(account, pathConfig, serviceConfiguration, userAgent);
     }
 
+    public static List<String> getAllLocalUsernames(File settingsPath) {
+        PathConfig pathConfig = PathConfig.createDefault(settingsPath);
+        final File dataPath = pathConfig.getDataPath();
+        final File[] 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());
+    }
+
     public void checkAccountState() throws IOException {
         if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
             refreshPreKeys();
index b3df9376adee40f9e1ba69dd302836d9fee8f018..d6bf78b13a012c6e129681de5bf4355052c8f246 100644 (file)
@@ -37,11 +37,12 @@ import org.whispersystems.signalservice.internal.push.LockedException;
 import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
 import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
 import java.util.Locale;
 
-public class RegistrationManager implements AutoCloseable {
+public class RegistrationManager implements Closeable {
 
     private SignalAccount account;
     private final PathConfig pathConfig;
@@ -184,7 +185,7 @@ public class RegistrationManager implements AutoCloseable {
     }
 
     @Override
-    public void close() throws Exception {
+    public void close() throws IOException {
         if (account != null) {
             account.close();
             account = null;