Add support for a global configuration file

This commit is contained in:
AsamK 2026-05-23 16:35:22 +02:00
parent 393e1efcd1
commit 44d54b3215
11 changed files with 315 additions and 51 deletions

View File

@ -13,7 +13,7 @@ signal-cli-dbus - A commandline and dbus interface for the Signal messenger
== Synopsis == Synopsis
*signal-cli* [--verbose] [--config CONFIG] [-a ACCOUNT] [-o {plain-text,json}] daemon [--dbus] [--dbus-system] *signal-cli* [--verbose] [--data-dir DATA_DIR] [-a ACCOUNT] [-o {plain-text,json}] daemon [--dbus] [--dbus-system]
*dbus-send* [--system | --session] [--print-reply] --type=method_call --dest="org.asamk.Signal" /org/asamk/Signal[/_<phonenumber>] org.asamk.Signal.<method> [string:<string argument>] [array:<type>:<array argument>] *dbus-send* [--system | --session] [--print-reply] --type=method_call --dest="org.asamk.Signal" /org/asamk/Signal[/_<phonenumber>] org.asamk.Signal.<method> [string:<string argument>] [array:<type>:<array argument>]

View File

@ -13,9 +13,9 @@ signal-cli-jsonrpc - A commandline and dbus interface for the Signal messenger
== Synopsis == Synopsis
*signal-cli* [--verbose] [--config CONFIG] [-a ACCOUNT] daemon [--socket[=SOCKET_PATH]] [--tcp[=HOST:PORT]] [--http[=HOST:PORT]] *signal-cli* [--verbose] [--data-dir DATA_DIR] [-a ACCOUNT] daemon [--socket[=SOCKET_PATH]] [--tcp[=HOST:PORT]] [--http[=HOST:PORT]]
*signal-cli* [--verbose] [--config CONFIG] [-a ACCOUNT] jsonRpc *signal-cli* [--verbose] [--data-dir DATA_DIR] [-a ACCOUNT] jsonRpc
== Description == Description

View File

@ -13,7 +13,7 @@ signal-cli - A commandline interface for the Signal messenger
== Synopsis == Synopsis
*signal-cli* [--config CONFIG] [-h | -v | -a ACCOUNT | --dbus | --dbus-system] command [command-options] *signal-cli* [--data-dir DATA_DIR] [-h | -v | -a ACCOUNT | --dbus | --dbus-system] command [command-options]
== Description == Description
@ -57,8 +57,8 @@ If `--verbose` is also given, the detailed logs will only be written to the log
Scrub possibly sensitive information from the log, like phone numbers and UUIDs. Scrub possibly sensitive information from the log, like phone numbers and UUIDs.
Doesn't work reliably on dbus logs with very verbose logging (`-vvv`) Doesn't work reliably on dbus logs with very verbose logging (`-vvv`)
*--config* CONFIG:: *-d* DATA_DIR, *--data-dir* DATA_DIR, *-c* CONFIG, *--config* CONFIG::
Set the path, where to store the config. Set the path where to store account data and local configuration.
Make sure you have full read/write access to the given directory. Make sure you have full read/write access to the given directory.
(Default: `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`)) (Default: `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`))
@ -1172,10 +1172,25 @@ signal-cli -a ACCOUNT trust -a RECIPIENT
== Files == 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*: The password and cryptographic keys are created when registering and stored in the current users home directory, the directory can be changed with *--data-dir* (legacy *--config*):
`$XDG_DATA_HOME/signal-cli/` (`$HOME/.local/share/signal-cli/`) `$XDG_DATA_HOME/signal-cli/` (`$HOME/.local/share/signal-cli/`)
=== Configuration file
signal-cli supports a JSON-based global configuration file that provides defaults for CLI options.
Keys use camelCase and generally match the long CLI parameter names (for example `dataDir`, `verbose`, `logFile`, `serviceEnvironment`, `trustNewIdentities`, `output`, `disableSendLog`, `account`).
Configuration files are read and merged in this order; later files override earlier ones:
- `/etc/signal-cli/config.json` (system-wide defaults)
- the path in the `SIGNAL_CLI_CONFIG` environment variable (if set)
- `$XDG_CONFIG_HOME/signal-cli/config.json` (per-user; defaults to `$HOME/.config/signal-cli/config.json`)
When multiple configuration files are present their settings are merged; values from later files override earlier values.
Command-line options always take precedence over configuration file values.
Overall precedence (highest → lowest): command-line options → per-user config → system config → built-in defaults.
== Authors == Authors
Maintained by AsamK <asamk@gmx.de>, who is assisted by other open source contributors. Maintained by AsamK <asamk@gmx.de>, who is assisted by other open source contributors.

View File

@ -46,7 +46,9 @@ public class App {
private final Namespace ns; private final Namespace ns;
static ArgumentParser buildArgumentParser() { static ArgumentParser buildArgumentParser(GlobalConfig config) {
final var cfg = config == null ? GlobalConfig.DEFAULT : config;
var parser = ArgumentParsers.newFor("signal-cli", VERSION_0_9_0_DEFAULT_SETTINGS) var parser = ArgumentParsers.newFor("signal-cli", VERSION_0_9_0_DEFAULT_SETTINGS)
.includeArgumentNamesAsKeysInResult(true) .includeArgumentNamesAsKeysInResult(true)
.build() .build()
@ -57,47 +59,60 @@ public class App {
parser.addArgument("--version").help("Show package version.").action(Arguments.version()); parser.addArgument("--version").help("Show package version.").action(Arguments.version());
parser.addArgument("-v", "--verbose") parser.addArgument("-v", "--verbose")
.help("Raise log level and include lib signal logs. Specify multiple times for even more logs.") .help("Raise log level and include lib signal logs. Specify multiple times for even more logs.")
.action(Arguments.count()); .action(Arguments.count())
.setDefault(cfg.verbose());
parser.addArgument("--log-file") parser.addArgument("--log-file")
.type(File.class) .type(File.class)
.help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file."); .help("Write log output to the given file. If --verbose is also given, the detailed logs will only be written to the log file.")
.setDefault(cfg.logFile() == null ? null : new File(cfg.logFile()));
parser.addArgument("--scrub-log") parser.addArgument("--scrub-log")
.action(Arguments.storeTrue()) .action(Arguments.storeTrue())
.help("Scrub possibly sensitive information from the log, like phone numbers and UUIDs."); .help("Scrub possibly sensitive information from the log, like phone numbers and UUIDs.")
parser.addArgument("-c", "--config") .setDefault(cfg.scrubLog());
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli)."); parser.addArgument("-d", "--data-dir", "-c", "--config")
.help("Set the path where to store data (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).")
.setDefault(cfg.dataDir());
parser.addArgument("-a", "--account", "-u", "--username") parser.addArgument("-a", "--account", "-u", "--username")
.help("Specify your phone number, that will be your identifier."); .help("Specify your phone number, that will be your identifier.");
var mut = parser.addMutuallyExclusiveGroup(); var mut = parser.addMutuallyExclusiveGroup();
mut.addArgument("--dbus").dest("global-dbus").help("Make request via user dbus.").action(Arguments.storeTrue()); mut.addArgument("--dbus")
.dest("global-dbus")
.help("Make request via user dbus.")
.action(Arguments.storeTrue())
.setDefault(cfg.dbus());
mut.addArgument("--dbus-system") mut.addArgument("--dbus-system")
.dest("global-dbus-system") .dest("global-dbus-system")
.help("Make request via system dbus.") .help("Make request via system dbus.")
.action(Arguments.storeTrue()); .action(Arguments.storeTrue())
.setDefault(cfg.dbusSystem());
parser.addArgument("--bus-name") parser.addArgument("--bus-name")
.dest("global-bus-name") .dest("global-bus-name")
.setDefault(DbusConfig.getBusname()) .setDefault(cfg.busName() != null ? cfg.busName() : DbusConfig.getBusname())
.help("Specify the D-Bus bus name to connect to."); .help("Specify the D-Bus bus name to connect to.");
parser.addArgument("-o", "--output") parser.addArgument("-o", "--output")
.help("Choose to output in plain text or JSON") .help("Choose to output in plain text or JSON")
.type(Arguments.enumStringType(OutputType.class)); .type(Arguments.enumStringType(OutputType.class))
.setDefault(cfg.output() == null ? null : cfg.output());
parser.addArgument("--service-environment") parser.addArgument("--service-environment")
.help("Choose the server environment to use.") .help("Choose the server environment to use.")
.type(Arguments.enumStringType(ServiceEnvironmentCli.class)) .type(Arguments.enumStringType(ServiceEnvironmentCli.class))
.setDefault(ServiceEnvironmentCli.LIVE); .setDefault(cfg.serviceEnvironment() != null ? cfg.serviceEnvironment() : ServiceEnvironmentCli.LIVE);
parser.addArgument("--trust-new-identities") parser.addArgument("--trust-new-identities")
.help("Choose when to trust new identities.") .help("Choose when to trust new identities.")
.type(Arguments.enumStringType(TrustNewIdentityCli.class)) .type(Arguments.enumStringType(TrustNewIdentityCli.class))
.setDefault(TrustNewIdentityCli.ON_FIRST_USE); .setDefault(cfg.trustNewIdentities() != null
? cfg.trustNewIdentities()
: TrustNewIdentityCli.ON_FIRST_USE);
parser.addArgument("--disable-send-log") parser.addArgument("--disable-send-log")
.help("Disable message send log (for resending messages that recipient couldn't decrypt)") .help("Disable message send log (for resending messages that recipient couldn't decrypt)")
.action(Arguments.storeTrue()); .action(Arguments.storeTrue())
.setDefault(cfg.disableSendLog());
parser.epilog( parser.epilog(
"The global arguments are shown with 'signal-cli -h' and need to come before the subcommand, while the subcommand-specific arguments (shown with 'signal-cli SUBCOMMAND -h') need to be given after the subcommand."); "The global arguments are shown with 'signal-cli -h' and need to come before the subcommand, while the subcommand-specific arguments (shown with 'signal-cli SUBCOMMAND -h') need to be given after the subcommand.");
@ -219,12 +234,12 @@ public class App {
} }
private SignalAccountFiles loadSignalAccountFiles() throws IOErrorException { private SignalAccountFiles loadSignalAccountFiles() throws IOErrorException {
final File configPath; final File dataPath;
final var config = ns.getString("config"); final var dataDir = ns.getString("data-dir");
if (config != null) { if (dataDir != null) {
configPath = new File(config); dataPath = new File(dataDir);
} else { } else {
configPath = getDefaultConfigPath(); dataPath = getDefaultDataPath();
} }
final var serviceEnvironmentCli = ns.<ServiceEnvironmentCli>get("service-environment"); final var serviceEnvironmentCli = ns.<ServiceEnvironmentCli>get("service-environment");
@ -240,7 +255,7 @@ public class App {
final var disableSendLog = Boolean.TRUE.equals(ns.getBoolean("disable-send-log")); final var disableSendLog = Boolean.TRUE.equals(ns.getBoolean("disable-send-log"));
try { try {
return new SignalAccountFiles(configPath, return new SignalAccountFiles(dataPath,
serviceEnvironment, serviceEnvironment,
BaseConfig.USER_AGENT, BaseConfig.USER_AGENT,
new Settings(trustNewIdentity, disableSendLog)); new Settings(trustNewIdentity, disableSendLog));
@ -339,7 +354,7 @@ public class App {
/** /**
* @return the default data directory to be used by signal-cli. * @return the default data directory to be used by signal-cli.
*/ */
private static File getDefaultConfigPath() { private static File getDefaultDataPath() {
return new File(IOUtils.getDataHomeDir(), "signal-cli"); return new File(IOUtils.getDataHomeDir(), "signal-cli");
} }
} }

View File

@ -0,0 +1,81 @@
package org.asamk.signal;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.asamk.signal.commands.exceptions.UserErrorException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Loads and merges configuration files. Merge order (later files override earlier):
* - /etc/signal-cli/config.json
* - file pointed to by SIGNAL_CLI_CONFIG (if set)
* - $XDG_CONFIG_HOME/signal-cli/config.json or $HOME/.config/signal-cli/config.json
*/
public final class ConfigLoader {
private ConfigLoader() {
}
public static GlobalConfig load() throws UserErrorException {
final ObjectMapper mapper = new ObjectMapper();
final ObjectNode merged = mapper.createObjectNode();
// System config
addIfExists(merged, mapper, Paths.get("/etc/signal-cli/config.json"));
// User config via env (if set) else XDG or ~/.config
final String env = System.getenv("SIGNAL_CLI_CONFIG");
if (env != null && !env.isEmpty()) {
addIfExists(merged, mapper, Paths.get(env));
} else {
final String xdg = System.getenv("XDG_CONFIG_HOME");
if (xdg != null && !xdg.isEmpty()) {
addIfExists(merged, mapper, Paths.get(xdg, "signal-cli", "config.json"));
} else {
addIfExists(merged,
mapper,
Paths.get(System.getProperty("user.home"), ".config", "signal-cli", "config.json"));
}
}
try {
if (merged.isEmpty()) {
return GlobalConfig.DEFAULT;
}
return mapper.treeToValue(merged, GlobalConfig.class);
} catch (Exception e) {
throw new UserErrorException("Failed to parse configuration file(s): " + e.getMessage(), e);
}
}
private static void addIfExists(ObjectNode merged, ObjectMapper mapper, Path p) throws UserErrorException {
if (p == null) return;
try {
if (Files.exists(p)) {
final JsonNode node = mapper.readTree(p.toFile());
merge(merged, node);
}
} catch (IOException e) {
throw new UserErrorException("Failed to load config from " + p + ": " + e.getMessage(), e);
}
}
private static void merge(ObjectNode target, JsonNode source) {
source.properties().forEach(entry -> {
final String name = entry.getKey();
final JsonNode value = entry.getValue();
final JsonNode existing = target.get(name);
if (existing != null && existing.isObject() && value.isObject()) {
merge((ObjectNode) existing, value);
} else {
target.set(name, value);
}
});
}
}

View File

@ -0,0 +1,36 @@
package org.asamk.signal;
import com.fasterxml.jackson.annotation.JsonProperty;
public record GlobalConfig(
@JsonProperty("verbose") Integer verbose,
@JsonProperty("logFile") String logFile,
@JsonProperty("scrubLog") Boolean scrubLog,
@JsonProperty("dataDir") String dataDir,
@JsonProperty("busName") String busName,
@JsonProperty("dbus") Boolean dbus,
@JsonProperty("dbusSystem") Boolean dbusSystem,
@JsonProperty("output") OutputType output,
@JsonProperty("serviceEnvironment") ServiceEnvironmentCli serviceEnvironment,
@JsonProperty("trustNewIdentities") TrustNewIdentityCli trustNewIdentities,
@JsonProperty("disableSendLog") Boolean disableSendLog,
@JsonProperty("account") String account
) {
public static final GlobalConfig DEFAULT = new GlobalConfig(null,
null,
null,
null,
null,
null,
null,
null,
ServiceEnvironmentCli.LIVE,
TrustNewIdentityCli.ON_FIRST_USE,
null,
null);
public static GlobalConfig empty() {
return new GlobalConfig(null, null, null, null, null, null, null, null, null, null, null, null);
}
}

View File

@ -40,27 +40,32 @@ import java.security.Security;
public class Main { public class Main {
public static void main(String[] args) { static void main(String[] args) {
// enable unlimited strength crypto via Policy, supported on relevant JREs // enable unlimited strength crypto via Policy, supported on relevant JREs
Security.setProperty("crypto.policy", "unlimited"); Security.setProperty("crypto.policy", "unlimited");
installSecurityProviderWorkaround(); installSecurityProviderWorkaround();
// Load global config early so we can use its values as parser defaults
final GlobalConfig globalConfig;
try {
globalConfig = ConfigLoader.load();
} catch (UserErrorException e) {
System.exit(handleCommandException(e, null));
return;
}
// Configuring the logger needs to happen before any logger is initialized // Configuring the logger needs to happen before any logger is initialized
final var loggingConfig = parseLoggingConfig(args); final var loggingConfig = parseLoggingConfig(args, globalConfig);
configureLogging(loggingConfig); configureLogging(loggingConfig);
final var parser = App.buildArgumentParser(); final var parser = App.buildArgumentParser(globalConfig);
final var ns = parser.parseArgsOrFail(args); final var ns = parser.parseArgsOrFail(args);
int status = 0; int status = 0;
try { try {
new App(ns).init(); new App(ns).init();
} catch (CommandException e) { } catch (CommandException e) {
System.err.println(e.getMessage()); status = handleCommandException(e, loggingConfig);
if (loggingConfig.verboseLevel > 0 && e.getCause() != null) {
e.getCause().printStackTrace(System.err);
}
status = getStatusForError(e);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
status = 2; status = 2;
@ -69,16 +74,27 @@ public class Main {
System.exit(status); System.exit(status);
} }
private static int handleCommandException(final CommandException e, final LoggingConfig loggingConfig) {
System.err.println(e.getMessage());
if (loggingConfig != null && loggingConfig.verboseLevel > 0 && e.getCause() != null) {
e.getCause().printStackTrace(System.err);
}
return getStatusForError(e);
}
private static void installSecurityProviderWorkaround() { private static void installSecurityProviderWorkaround() {
// Register our own security provider // Register our own security provider
Security.insertProviderAt(new SecurityProvider(), 1); Security.insertProviderAt(new SecurityProvider(), 1);
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
private static LoggingConfig parseLoggingConfig(final String[] args) { private static LoggingConfig parseLoggingConfig(final String[] args, final GlobalConfig config) {
final var nsLog = parseArgs(args); final var nsLog = parseArgs(args, config);
if (nsLog == null) { if (nsLog == null) {
return new LoggingConfig(0, null, false); final var verbose = config != null && config.verbose() != null ? config.verbose() : 0;
final var logFile = config != null && config.logFile() != null ? new File(config.logFile()) : null;
final var scrubLog = config != null && Boolean.TRUE.equals(config.scrubLog());
return new LoggingConfig(verbose, logFile, scrubLog);
} }
final var verboseLevel = nsLog.getInt("verbose"); final var verboseLevel = nsLog.getInt("verbose");
@ -90,14 +106,20 @@ public class Main {
/** /**
* This method only parses commandline args relevant for logging configuration. * This method only parses commandline args relevant for logging configuration.
*/ */
private static Namespace parseArgs(String[] args) { private static Namespace parseArgs(String[] args, final GlobalConfig config) {
var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS) var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS)
.includeArgumentNamesAsKeysInResult(true) .includeArgumentNamesAsKeysInResult(true)
.build() .build()
.defaultHelp(false); .defaultHelp(false);
parser.addArgument("-v", "--verbose").action(Arguments.count()); parser.addArgument("-v", "--verbose")
parser.addArgument("--log-file").type(File.class); .action(Arguments.count())
parser.addArgument("--scrub-log").action(Arguments.storeTrue()); .setDefault(config == null ? null : config.verbose());
parser.addArgument("--log-file")
.type(File.class)
.setDefault(config == null || config.logFile() == null ? null : new File(config.logFile()));
parser.addArgument("--scrub-log")
.action(Arguments.storeTrue())
.setDefault(config == null ? null : config.scrubLog());
try { try {
return parser.parseKnownArgs(args, null); return parser.parseKnownArgs(args, null);
@ -124,12 +146,12 @@ public class Main {
private static int getStatusForError(final CommandException e) { private static int getStatusForError(final CommandException e) {
return switch (e) { return switch (e) {
case UserErrorException userErrorException -> 1; case UserErrorException _ -> 1;
case UnexpectedErrorException unexpectedErrorException -> 2; case UnexpectedErrorException _ -> 2;
case IOErrorException ioErrorException -> 3; case IOErrorException _ -> 3;
case UntrustedKeyErrorException untrustedKeyErrorException -> 4; case UntrustedKeyErrorException _ -> 4;
case RateLimitErrorException rateLimitErrorException -> 5; case RateLimitErrorException _ -> 5;
case CaptchaRejectedErrorException captchaRejectedErrorException -> 6; case CaptchaRejectedErrorException _ -> 6;
case null -> 2; case null -> 2;
}; };
} }

View File

@ -1,5 +1,7 @@
package org.asamk.signal; package org.asamk.signal;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum OutputType { public enum OutputType {
PLAIN_TEXT { PLAIN_TEXT {
@Override @Override
@ -12,5 +14,16 @@ public enum OutputType {
public String toString() { public String toString() {
return "json"; return "json";
} }
}, };
@JsonCreator
public static OutputType fromString(String value) {
if (value == null) return null;
final var norm = value.trim().toLowerCase().replaceAll("[^a-z0-9]", "");
return switch (norm) {
case "plaintext" -> PLAIN_TEXT;
case "json" -> JSON;
default -> throw new IllegalArgumentException("Invalid output type: " + value);
};
}
} }

View File

@ -1,5 +1,7 @@
package org.asamk.signal; package org.asamk.signal;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum ServiceEnvironmentCli { public enum ServiceEnvironmentCli {
LIVE { LIVE {
@Override @Override
@ -12,5 +14,16 @@ public enum ServiceEnvironmentCli {
public String toString() { public String toString() {
return "staging"; return "staging";
} }
}, };
@JsonCreator
public static ServiceEnvironmentCli fromString(String value) {
if (value == null) return null;
final var norm = value.trim().toLowerCase();
return switch (norm) {
case "live" -> LIVE;
case "staging" -> STAGING;
default -> throw new IllegalArgumentException("Invalid service-environment: " + value);
};
}
} }

View File

@ -1,5 +1,7 @@
package org.asamk.signal; package org.asamk.signal;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum TrustNewIdentityCli { public enum TrustNewIdentityCli {
ALWAYS { ALWAYS {
@Override @Override
@ -18,5 +20,17 @@ public enum TrustNewIdentityCli {
public String toString() { public String toString() {
return "never"; return "never";
} }
}, };
@JsonCreator
public static TrustNewIdentityCli fromString(String value) {
if (value == null) return null;
final var norm = value.trim().toLowerCase().replaceAll("[^a-z0-9]", "");
return switch (norm) {
case "always" -> ALWAYS;
case "onfirstuse" -> ON_FIRST_USE;
case "never" -> NEVER;
default -> throw new IllegalArgumentException("Invalid trust-new-identities: " + value);
};
}
} }

View File

@ -1317,6 +1317,14 @@
} }
] ]
}, },
{
"type": "java.util.concurrent.CopyOnWriteArrayList",
"fields": [
{
"name": "lock"
}
]
},
{ {
"type": "java.util.concurrent.ForkJoinTask", "type": "java.util.concurrent.ForkJoinTask",
"fields": [ "fields": [
@ -1979,6 +1987,28 @@
{ {
"type": "org.asamk.SignalControl.Error.InvalidNumber" "type": "org.asamk.SignalControl.Error.InvalidNumber"
}, },
{
"type": "org.asamk.signal.GlobalConfig",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.Integer",
"java.lang.String",
"java.lang.Boolean",
"java.lang.String",
"java.lang.String",
"java.lang.Boolean",
"java.lang.Boolean",
"org.asamk.signal.OutputType",
"org.asamk.signal.ServiceEnvironmentCli",
"org.asamk.signal.TrustNewIdentityCli",
"java.lang.Boolean",
"java.lang.String"
]
}
]
},
{ {
"type": "org.asamk.signal.Main", "type": "org.asamk.signal.Main",
"jniAccessible": true, "jniAccessible": true,
@ -1995,6 +2025,31 @@
} }
] ]
}, },
{
"type": "org.asamk.signal.OutputType"
},
{
"type": "org.asamk.signal.ServiceEnvironmentCli",
"methods": [
{
"name": "fromString",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"type": "org.asamk.signal.TrustNewIdentityCli",
"methods": [
{
"name": "fromString",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{ {
"type": "org.asamk.signal.commands.AcceptCallCommand$JsonCallInfo", "type": "org.asamk.signal.commands.AcceptCallCommand$JsonCallInfo",
"allDeclaredFields": true, "allDeclaredFields": true,