mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-28 18:10:38 +00:00
Rename username to account
This commit is contained in:
parent
398cddaf29
commit
8aab644db9
14 changed files with 245 additions and 218 deletions
78
README.md
78
README.md
|
@ -1,65 +1,88 @@
|
||||||
# signal-cli
|
# signal-cli
|
||||||
|
|
||||||
signal-cli is a commandline interface for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering, verifying, sending and receiving messages.
|
signal-cli is a commandline interface
|
||||||
To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21).
|
for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering,
|
||||||
For registering you need a phone number where you can receive SMS or incoming calls.
|
verifying, sending and receiving messages. To be able to link to an existing Signal-Android/signal-cli instance,
|
||||||
signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings.
|
signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because
|
||||||
It also has a JSON-RPC based interface, see the [documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service) for more information.
|
libsignal-service-java does not yet
|
||||||
|
support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). For
|
||||||
|
registering you need a phone number where you can receive SMS or incoming calls. signal-cli is primarily intended to be
|
||||||
|
used on servers to notify admins of important events. For this use-case, it has a dbus
|
||||||
|
interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to
|
||||||
|
send messages from any programming language that has dbus bindings. It also has a JSON-RPC based interface, see
|
||||||
|
the [documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service) for more information.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well.
|
You can [build signal-cli](#building) yourself, or use
|
||||||
|
the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and
|
||||||
|
Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is
|
||||||
|
a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well.
|
||||||
|
|
||||||
System requirements:
|
System requirements:
|
||||||
|
|
||||||
- at least Java Runtime Environment (JRE) 17
|
- at least Java Runtime Environment (JRE) 17
|
||||||
- native libraries: libzkgroup, libsignal-client
|
- native libraries: libzkgroup, libsignal-client
|
||||||
|
|
||||||
Those are bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
|
Those are bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures
|
||||||
|
see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
|
||||||
|
|
||||||
### Install system-wide on Linux
|
### Install system-wide on Linux
|
||||||
|
|
||||||
See [latest version](https://github.com/AsamK/signal-cli/releases).
|
See [latest version](https://github.com/AsamK/signal-cli/releases).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export VERSION=<latest version, format "x.y.z">
|
export VERSION=<latest version, format "x.y.z">
|
||||||
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
|
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
|
||||||
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
|
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
|
||||||
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
|
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find further instructions on the Wiki:
|
You can find further instructions on the Wiki:
|
||||||
|
|
||||||
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
|
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
|
||||||
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
|
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
For a complete usage overview please read the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and the [wiki](https://github.com/AsamK/signal-cli/wiki).
|
For a complete usage overview please read
|
||||||
|
the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and
|
||||||
|
the [wiki](https://github.com/AsamK/signal-cli/wiki).
|
||||||
|
|
||||||
Important: The USERNAME is your phone number in international format and must include the country calling code. Hence it should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list of all country codes.)
|
Important: The ACCOUNT is your phone number in international format and must include the country calling code. Hence it
|
||||||
|
should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list
|
||||||
|
of all country codes.)
|
||||||
|
|
||||||
* Register a number (with SMS verification)
|
* Register a number (with SMS verification)
|
||||||
|
|
||||||
signal-cli -u USERNAME register
|
signal-cli -a ACCOUNT register
|
||||||
|
|
||||||
You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly to the voice call verification by adding the `--voice` switch at the end of above register command.
|
You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly
|
||||||
|
to the voice call verification by adding the `--voice` switch at the end of above register command.
|
||||||
|
|
||||||
Registering may require solving a CAPTCHA challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
|
Registering may require solving a CAPTCHA
|
||||||
|
challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
|
||||||
|
|
||||||
* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code to your account
|
* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code
|
||||||
|
to your account
|
||||||
|
|
||||||
signal-cli -u USERNAME verify CODE
|
signal-cli -a ACCOUNT verify CODE
|
||||||
|
|
||||||
* Send a message
|
* Send a message
|
||||||
|
|
||||||
signal-cli -u USERNAME send -m "This is a message" RECIPIENT
|
signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
|
||||||
|
|
||||||
* Pipe the message content from another process.
|
* Pipe the message content from another process.
|
||||||
|
|
||||||
uname -a | signal-cli -u USERNAME send RECIPIENT
|
uname -a | signal-cli -a ACCOUNT send RECIPIENT
|
||||||
|
|
||||||
* Receive messages
|
* Receive messages
|
||||||
|
|
||||||
signal-cli -u USERNAME receive
|
signal-cli -a ACCOUNT receive
|
||||||
|
|
||||||
**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive` command).
|
**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive`
|
||||||
This is required for the encryption to work efficiently and for getting updates to groups, expiration timer and other features.
|
command). This is required for the encryption to work efficiently and for getting updates to groups, expiration timer
|
||||||
|
and other features.
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
|
@ -70,8 +93,8 @@ The password and cryptographic keys are created when registering and stored in t
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
This project uses [Gradle](http://gradle.org) for building and maintaining
|
This project uses [Gradle](http://gradle.org) for building and maintaining dependencies. If you have a recent gradle
|
||||||
dependencies. If you have a recent gradle version installed, you can replace `./gradlew` with `gradle` in the following steps.
|
version installed, you can replace `./gradlew` with `gradle` in the following steps.
|
||||||
|
|
||||||
1. Checkout the source somewhere on your filesystem with
|
1. Checkout the source somewhere on your filesystem with
|
||||||
|
|
||||||
|
@ -81,26 +104,26 @@ dependencies. If you have a recent gradle version installed, you can replace `./
|
||||||
|
|
||||||
./gradlew build
|
./gradlew build
|
||||||
|
|
||||||
2a. Create shell wrapper in *build/install/signal-cli/bin*:
|
2a. Create shell wrapper in *build/install/signal-cli/bin*:
|
||||||
|
|
||||||
./gradlew installDist
|
./gradlew installDist
|
||||||
|
|
||||||
2b. Create tar file in *build/distributions*:
|
2b. Create tar file in *build/distributions*:
|
||||||
|
|
||||||
./gradlew distTar
|
./gradlew distTar
|
||||||
|
|
||||||
2c. Create a fat tar file in *build/libs/signal-cli-fat*:
|
2c. Create a fat tar file in *build/libs/signal-cli-fat*:
|
||||||
|
|
||||||
./gradlew fatJar
|
./gradlew fatJar
|
||||||
|
|
||||||
2d. Compile and run signal-cli:
|
2d. Compile and run signal-cli:
|
||||||
|
|
||||||
./gradlew run --args="--help"
|
./gradlew run --args="--help"
|
||||||
|
|
||||||
### Building a native binary with GraalVM (EXPERIMENTAL)
|
### Building a native binary with GraalVM (EXPERIMENTAL)
|
||||||
|
|
||||||
It is possible to build a native binary with [GraalVM](https://www.graalvm.org).
|
It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not
|
||||||
This is still experimental and will not work in all situations.
|
work in all situations.
|
||||||
|
|
||||||
1. [Install GraalVM and setup the enviroment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
|
1. [Install GraalVM and setup the enviroment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
|
||||||
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
|
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
|
||||||
|
@ -111,6 +134,7 @@ This is still experimental and will not work in all situations.
|
||||||
The binary is available at *build/native/nativeCompile/signal-cli*
|
The binary is available at *build/native/nativeCompile/signal-cli*
|
||||||
|
|
||||||
## FAQ and Troubleshooting
|
## FAQ and Troubleshooting
|
||||||
|
|
||||||
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
|
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -8,7 +8,7 @@ After=network-online.target
|
||||||
[Service]
|
[Service]
|
||||||
Type=dbus
|
Type=dbus
|
||||||
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
||||||
ExecStart=%dir%/bin/signal-cli -u %I --config /var/lib/signal-cli daemon --system
|
ExecStart=%dir%/bin/signal-cli -a %I --config /var/lib/signal-cli daemon --system
|
||||||
User=signal-cli
|
User=signal-cli
|
||||||
BusName=org.asamk.Signal
|
BusName=org.asamk.Signal
|
||||||
# JVM always exits with 143 in reaction to SIGTERM signal
|
# JVM always exits with 143 in reaction to SIGTERM signal
|
||||||
|
|
|
@ -64,6 +64,9 @@
|
||||||
{
|
{
|
||||||
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E"
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MO\\E"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E"
|
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E"
|
||||||
},
|
},
|
||||||
|
|
|
@ -156,7 +156,7 @@ public class ManagerImpl implements Manager {
|
||||||
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
|
||||||
|
|
||||||
final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(),
|
final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(),
|
||||||
account.getUsername(),
|
account.getAccount(),
|
||||||
account.getPassword(),
|
account.getPassword(),
|
||||||
account.getDeviceId());
|
account.getDeviceId());
|
||||||
final var sessionLock = new SignalSessionLock() {
|
final var sessionLock = new SignalSessionLock() {
|
||||||
|
@ -251,7 +251,7 @@ public class ManagerImpl implements Manager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSelfNumber() {
|
public String getSelfNumber() {
|
||||||
return account.getUsername();
|
return account.getAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -285,7 +285,7 @@ public class ManagerImpl implements Manager {
|
||||||
public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
|
public Map<String, Pair<String, UUID>> areUsersRegistered(Set<String> numbers) throws IOException {
|
||||||
Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
|
Map<String, String> canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> {
|
||||||
try {
|
try {
|
||||||
return PhoneNumberFormatter.formatNumber(n, account.getUsername());
|
return PhoneNumberFormatter.formatNumber(n, account.getAccount());
|
||||||
} catch (InvalidNumberException e) {
|
} catch (InvalidNumberException e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -1265,7 +1265,7 @@ public class ManagerImpl implements Manager {
|
||||||
/**
|
/**
|
||||||
* Trust this the identity with this fingerprint
|
* Trust this the identity with this fingerprint
|
||||||
*
|
*
|
||||||
* @param recipient username of the identity
|
* @param recipient account of the identity
|
||||||
* @param fingerprint Fingerprint
|
* @param fingerprint Fingerprint
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -1282,7 +1282,7 @@ public class ManagerImpl implements Manager {
|
||||||
/**
|
/**
|
||||||
* Trust this the identity with this safety number
|
* Trust this the identity with this safety number
|
||||||
*
|
*
|
||||||
* @param recipient username of the identity
|
* @param recipient account of the identity
|
||||||
* @param safetyNumber Safety number
|
* @param safetyNumber Safety number
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -1299,7 +1299,7 @@ public class ManagerImpl implements Manager {
|
||||||
/**
|
/**
|
||||||
* Trust this the identity with this scannable safety number
|
* Trust this the identity with this scannable safety number
|
||||||
*
|
*
|
||||||
* @param recipient username of the identity
|
* @param recipient account of the identity
|
||||||
* @param safetyNumber Scannable safety number
|
* @param safetyNumber Scannable safety number
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -1316,7 +1316,7 @@ public class ManagerImpl implements Manager {
|
||||||
/**
|
/**
|
||||||
* Trust all keys of this identity without verification
|
* Trust all keys of this identity without verification
|
||||||
*
|
*
|
||||||
* @param recipient username of the identity
|
* @param recipient account of the identity
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
|
public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ public interface MultiAccountManager extends AutoCloseable {
|
||||||
|
|
||||||
ProvisioningManager getNewProvisioningManager();
|
ProvisioningManager getNewProvisioningManager();
|
||||||
|
|
||||||
RegistrationManager getNewRegistrationManager(String username) throws IOException;
|
RegistrationManager getNewRegistrationManager(String account) throws IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void close();
|
void close();
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class RegistrationManager implements Closeable {
|
||||||
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
new DynamicCredentialsProvider(
|
new DynamicCredentialsProvider(
|
||||||
// Using empty UUID, because registering doesn't work otherwise
|
// Using empty UUID, because registering doesn't work otherwise
|
||||||
null, account.getUsername(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
|
null, account.getAccount(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
|
||||||
userAgent,
|
userAgent,
|
||||||
groupsV2Operations,
|
groupsV2Operations,
|
||||||
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
|
||||||
|
@ -142,7 +142,7 @@ public class RegistrationManager implements Closeable {
|
||||||
try {
|
try {
|
||||||
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
|
||||||
new DynamicCredentialsProvider(account.getAci(),
|
new DynamicCredentialsProvider(account.getAci(),
|
||||||
account.getUsername(),
|
account.getAccount(),
|
||||||
account.getPassword(),
|
account.getPassword(),
|
||||||
account.getDeviceId()),
|
account.getDeviceId()),
|
||||||
userAgent,
|
userAgent,
|
||||||
|
|
|
@ -195,7 +195,7 @@ public class StorageHelper {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accountRecord.getE164().equals(account.getUsername())) {
|
if (!accountRecord.getE164().equals(account.getAccount())) {
|
||||||
// TODO implement changed number handling
|
// TODO implement changed number handling
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class SignalAccount implements Closeable {
|
||||||
private final FileChannel fileChannel;
|
private final FileChannel fileChannel;
|
||||||
private final FileLock lock;
|
private final FileLock lock;
|
||||||
|
|
||||||
private String username;
|
private String account;
|
||||||
private ACI aci;
|
private ACI aci;
|
||||||
private String encryptedDeviceName;
|
private String encryptedDeviceName;
|
||||||
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
||||||
|
@ -116,21 +116,21 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalAccount load(
|
public static SignalAccount load(
|
||||||
File dataPath, String username, boolean waitForLock, final TrustNewIdentity trustNewIdentity
|
File dataPath, String account, boolean waitForLock, final TrustNewIdentity trustNewIdentity
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
final var fileName = getFileName(dataPath, username);
|
final var fileName = getFileName(dataPath, account);
|
||||||
final var pair = openFileChannel(fileName, waitForLock);
|
final var pair = openFileChannel(fileName, waitForLock);
|
||||||
try {
|
try {
|
||||||
var account = new SignalAccount(pair.first(), pair.second());
|
var signalAccount = new SignalAccount(pair.first(), pair.second());
|
||||||
account.load(dataPath, trustNewIdentity);
|
signalAccount.load(dataPath, trustNewIdentity);
|
||||||
account.migrateLegacyConfigs();
|
signalAccount.migrateLegacyConfigs();
|
||||||
|
|
||||||
if (!username.equals(account.getUsername())) {
|
if (!account.equals(signalAccount.getAccount())) {
|
||||||
throw new IOException("Username in account file doesn't match expected number: "
|
throw new IOException("Number in account file doesn't match expected number: "
|
||||||
+ account.getUsername());
|
+ signalAccount.getAccount());
|
||||||
}
|
}
|
||||||
|
|
||||||
return account;
|
return signalAccount;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
pair.second().close();
|
pair.second().close();
|
||||||
pair.first().close();
|
pair.first().close();
|
||||||
|
@ -140,37 +140,37 @@ public class SignalAccount implements Closeable {
|
||||||
|
|
||||||
public static SignalAccount create(
|
public static SignalAccount create(
|
||||||
File dataPath,
|
File dataPath,
|
||||||
String username,
|
String account,
|
||||||
IdentityKeyPair identityKey,
|
IdentityKeyPair identityKey,
|
||||||
int registrationId,
|
int registrationId,
|
||||||
ProfileKey profileKey,
|
ProfileKey profileKey,
|
||||||
final TrustNewIdentity trustNewIdentity
|
final TrustNewIdentity trustNewIdentity
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
IOUtils.createPrivateDirectories(dataPath);
|
IOUtils.createPrivateDirectories(dataPath);
|
||||||
var fileName = getFileName(dataPath, username);
|
var fileName = getFileName(dataPath, account);
|
||||||
if (!fileName.exists()) {
|
if (!fileName.exists()) {
|
||||||
IOUtils.createPrivateFile(fileName);
|
IOUtils.createPrivateFile(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var pair = openFileChannel(fileName, true);
|
final var pair = openFileChannel(fileName, true);
|
||||||
var account = new SignalAccount(pair.first(), pair.second());
|
var signalAccount = new SignalAccount(pair.first(), pair.second());
|
||||||
|
|
||||||
account.username = username;
|
signalAccount.account = account;
|
||||||
account.profileKey = profileKey;
|
signalAccount.profileKey = profileKey;
|
||||||
|
|
||||||
account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
|
signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
|
||||||
account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
|
signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
|
||||||
account.recipientStore,
|
signalAccount.recipientStore,
|
||||||
account::saveGroupStore);
|
signalAccount::saveGroupStore);
|
||||||
account.stickerStore = new StickerStore(account::saveStickerStore);
|
signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
|
||||||
account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
|
signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
|
||||||
|
|
||||||
account.registered = false;
|
signalAccount.registered = false;
|
||||||
|
|
||||||
account.migrateLegacyConfigs();
|
signalAccount.migrateLegacyConfigs();
|
||||||
account.save();
|
signalAccount.save();
|
||||||
|
|
||||||
return account;
|
return signalAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initStores(
|
private void initStores(
|
||||||
|
@ -179,18 +179,18 @@ public class SignalAccount implements Closeable {
|
||||||
final int registrationId,
|
final int registrationId,
|
||||||
final TrustNewIdentity trustNewIdentity
|
final TrustNewIdentity trustNewIdentity
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, username), this::mergeRecipients);
|
recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account), this::mergeRecipients);
|
||||||
|
|
||||||
preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, username));
|
preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account));
|
||||||
signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, username));
|
signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account));
|
||||||
sessionStore = new SessionStore(getSessionsPath(dataPath, username), recipientStore);
|
sessionStore = new SessionStore(getSessionsPath(dataPath, account), recipientStore);
|
||||||
identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, username),
|
identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
|
||||||
recipientStore,
|
recipientStore,
|
||||||
identityKey,
|
identityKey,
|
||||||
registrationId,
|
registrationId,
|
||||||
trustNewIdentity);
|
trustNewIdentity);
|
||||||
senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, username),
|
senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
|
||||||
getSenderKeysPath(dataPath, username),
|
getSenderKeysPath(dataPath, account),
|
||||||
recipientStore::resolveRecipientAddress,
|
recipientStore::resolveRecipientAddress,
|
||||||
recipientStore);
|
recipientStore);
|
||||||
signalProtocolStore = new SignalProtocolStore(preKeyStore,
|
signalProtocolStore = new SignalProtocolStore(preKeyStore,
|
||||||
|
@ -200,12 +200,12 @@ public class SignalAccount implements Closeable {
|
||||||
senderKeyStore,
|
senderKeyStore,
|
||||||
this::isMultiDevice);
|
this::isMultiDevice);
|
||||||
|
|
||||||
messageCache = new MessageCache(getMessageCachePath(dataPath, username));
|
messageCache = new MessageCache(getMessageCachePath(dataPath, account));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalAccount createOrUpdateLinkedAccount(
|
public static SignalAccount createOrUpdateLinkedAccount(
|
||||||
File dataPath,
|
File dataPath,
|
||||||
String username,
|
String account,
|
||||||
ACI aci,
|
ACI aci,
|
||||||
String password,
|
String password,
|
||||||
String encryptedDeviceName,
|
String encryptedDeviceName,
|
||||||
|
@ -216,10 +216,10 @@ public class SignalAccount implements Closeable {
|
||||||
final TrustNewIdentity trustNewIdentity
|
final TrustNewIdentity trustNewIdentity
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
IOUtils.createPrivateDirectories(dataPath);
|
IOUtils.createPrivateDirectories(dataPath);
|
||||||
var fileName = getFileName(dataPath, username);
|
var fileName = getFileName(dataPath, account);
|
||||||
if (!fileName.exists()) {
|
if (!fileName.exists()) {
|
||||||
return createLinkedAccount(dataPath,
|
return createLinkedAccount(dataPath,
|
||||||
username,
|
account,
|
||||||
aci,
|
aci,
|
||||||
password,
|
password,
|
||||||
encryptedDeviceName,
|
encryptedDeviceName,
|
||||||
|
@ -230,13 +230,13 @@ public class SignalAccount implements Closeable {
|
||||||
trustNewIdentity);
|
trustNewIdentity);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var account = load(dataPath, username, true, trustNewIdentity);
|
final var signalAccount = load(dataPath, account, true, trustNewIdentity);
|
||||||
account.setProvisioningData(username, aci, password, encryptedDeviceName, deviceId, profileKey);
|
signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
|
||||||
account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
|
signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
|
||||||
account.sessionStore.archiveAllSessions();
|
signalAccount.sessionStore.archiveAllSessions();
|
||||||
account.senderKeyStore.deleteAll();
|
signalAccount.senderKeyStore.deleteAll();
|
||||||
account.clearAllPreKeys();
|
signalAccount.clearAllPreKeys();
|
||||||
return account;
|
return signalAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearAllPreKeys() {
|
private void clearAllPreKeys() {
|
||||||
|
@ -249,7 +249,7 @@ public class SignalAccount implements Closeable {
|
||||||
|
|
||||||
private static SignalAccount createLinkedAccount(
|
private static SignalAccount createLinkedAccount(
|
||||||
File dataPath,
|
File dataPath,
|
||||||
String username,
|
String account,
|
||||||
ACI aci,
|
ACI aci,
|
||||||
String password,
|
String password,
|
||||||
String encryptedDeviceName,
|
String encryptedDeviceName,
|
||||||
|
@ -259,37 +259,37 @@ public class SignalAccount implements Closeable {
|
||||||
ProfileKey profileKey,
|
ProfileKey profileKey,
|
||||||
final TrustNewIdentity trustNewIdentity
|
final TrustNewIdentity trustNewIdentity
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
var fileName = getFileName(dataPath, username);
|
var fileName = getFileName(dataPath, account);
|
||||||
IOUtils.createPrivateFile(fileName);
|
IOUtils.createPrivateFile(fileName);
|
||||||
|
|
||||||
final var pair = openFileChannel(fileName, true);
|
final var pair = openFileChannel(fileName, true);
|
||||||
var account = new SignalAccount(pair.first(), pair.second());
|
var signalAccount = new SignalAccount(pair.first(), pair.second());
|
||||||
|
|
||||||
account.setProvisioningData(username, aci, password, encryptedDeviceName, deviceId, profileKey);
|
signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
|
||||||
|
|
||||||
account.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
|
signalAccount.initStores(dataPath, identityKey, registrationId, trustNewIdentity);
|
||||||
account.groupStore = new GroupStore(getGroupCachePath(dataPath, username),
|
signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
|
||||||
account.recipientStore,
|
signalAccount.recipientStore,
|
||||||
account::saveGroupStore);
|
signalAccount::saveGroupStore);
|
||||||
account.stickerStore = new StickerStore(account::saveStickerStore);
|
signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
|
||||||
account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
|
signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
|
||||||
|
|
||||||
account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
|
signalAccount.recipientStore.resolveRecipientTrusted(signalAccount.getSelfAddress());
|
||||||
account.migrateLegacyConfigs();
|
signalAccount.migrateLegacyConfigs();
|
||||||
account.save();
|
signalAccount.save();
|
||||||
|
|
||||||
return account;
|
return signalAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setProvisioningData(
|
private void setProvisioningData(
|
||||||
final String username,
|
final String account,
|
||||||
final ACI aci,
|
final ACI aci,
|
||||||
final String password,
|
final String password,
|
||||||
final String encryptedDeviceName,
|
final String encryptedDeviceName,
|
||||||
final int deviceId,
|
final int deviceId,
|
||||||
final ProfileKey profileKey
|
final ProfileKey profileKey
|
||||||
) {
|
) {
|
||||||
this.username = username;
|
this.account = account;
|
||||||
this.aci = aci;
|
this.aci = aci;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.profileKey = profileKey;
|
this.profileKey = profileKey;
|
||||||
|
@ -324,12 +324,12 @@ public class SignalAccount implements Closeable {
|
||||||
senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
senderKeyStore.mergeRecipients(recipientId, toBeMergedRecipientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getFileName(File dataPath, String username) {
|
public static File getFileName(File dataPath, String account) {
|
||||||
return new File(dataPath, username);
|
return new File(dataPath, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getUserPath(final File dataPath, final String username) {
|
private static File getUserPath(final File dataPath, final String account) {
|
||||||
final var path = new File(dataPath, username + ".d");
|
final var path = new File(dataPath, account + ".d");
|
||||||
try {
|
try {
|
||||||
IOUtils.createPrivateDirectories(path);
|
IOUtils.createPrivateDirectories(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -338,47 +338,47 @@ public class SignalAccount implements Closeable {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getMessageCachePath(File dataPath, String username) {
|
private static File getMessageCachePath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "msg-cache");
|
return new File(getUserPath(dataPath, account), "msg-cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getGroupCachePath(File dataPath, String username) {
|
private static File getGroupCachePath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "group-cache");
|
return new File(getUserPath(dataPath, account), "group-cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getPreKeysPath(File dataPath, String username) {
|
private static File getPreKeysPath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "pre-keys");
|
return new File(getUserPath(dataPath, account), "pre-keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSignedPreKeysPath(File dataPath, String username) {
|
private static File getSignedPreKeysPath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "signed-pre-keys");
|
return new File(getUserPath(dataPath, account), "signed-pre-keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getIdentitiesPath(File dataPath, String username) {
|
private static File getIdentitiesPath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "identities");
|
return new File(getUserPath(dataPath, account), "identities");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSessionsPath(File dataPath, String username) {
|
private static File getSessionsPath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "sessions");
|
return new File(getUserPath(dataPath, account), "sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSenderKeysPath(File dataPath, String username) {
|
private static File getSenderKeysPath(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "sender-keys");
|
return new File(getUserPath(dataPath, account), "sender-keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getSharedSenderKeysFile(File dataPath, String username) {
|
private static File getSharedSenderKeysFile(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "shared-sender-keys-store");
|
return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getRecipientsStoreFile(File dataPath, String username) {
|
private static File getRecipientsStoreFile(File dataPath, String account) {
|
||||||
return new File(getUserPath(dataPath, username), "recipients-store");
|
return new File(getUserPath(dataPath, account), "recipients-store");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean userExists(File dataPath, String username) {
|
public static boolean userExists(File dataPath, String account) {
|
||||||
if (username == null) {
|
if (account == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var f = getFileName(dataPath, username);
|
var f = getFileName(dataPath, account);
|
||||||
return !(!f.exists() || f.isDirectory());
|
return !(!f.exists() || f.isDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
username = Utils.getNotNullNode(rootNode, "username").asText();
|
account = Utils.getNotNullNode(rootNode, "username").asText();
|
||||||
password = Utils.getNotNullNode(rootNode, "password").asText();
|
password = Utils.getNotNullNode(rootNode, "password").asText();
|
||||||
registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
|
registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
|
||||||
if (rootNode.hasNonNull("uuid")) {
|
if (rootNode.hasNonNull("uuid")) {
|
||||||
|
@ -483,11 +483,11 @@ public class SignalAccount implements Closeable {
|
||||||
if (rootNode.hasNonNull("groupStore")) {
|
if (rootNode.hasNonNull("groupStore")) {
|
||||||
groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
|
groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
|
||||||
groupStore = GroupStore.fromStorage(groupStoreStorage,
|
groupStore = GroupStore.fromStorage(groupStoreStorage,
|
||||||
getGroupCachePath(dataPath, username),
|
getGroupCachePath(dataPath, account),
|
||||||
recipientStore,
|
recipientStore,
|
||||||
this::saveGroupStore);
|
this::saveGroupStore);
|
||||||
} else {
|
} else {
|
||||||
groupStore = new GroupStore(getGroupCachePath(dataPath, username), recipientStore, this::saveGroupStore);
|
groupStore = new GroupStore(getGroupCachePath(dataPath, account), recipientStore, this::saveGroupStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootNode.hasNonNull("stickerStore")) {
|
if (rootNode.hasNonNull("stickerStore")) {
|
||||||
|
@ -702,7 +702,7 @@ public class SignalAccount implements Closeable {
|
||||||
synchronized (fileChannel) {
|
synchronized (fileChannel) {
|
||||||
var rootNode = jsonProcessor.createObjectNode();
|
var rootNode = jsonProcessor.createObjectNode();
|
||||||
rootNode.put("version", CURRENT_STORAGE_VERSION)
|
rootNode.put("version", CURRENT_STORAGE_VERSION)
|
||||||
.put("username", username)
|
.put("username", account)
|
||||||
.put("uuid", aci == null ? null : aci.toString())
|
.put("uuid", aci == null ? null : aci.toString())
|
||||||
.put("deviceName", encryptedDeviceName)
|
.put("deviceName", encryptedDeviceName)
|
||||||
.put("deviceId", deviceId)
|
.put("deviceId", deviceId)
|
||||||
|
@ -827,8 +827,8 @@ public class SignalAccount implements Closeable {
|
||||||
return messageCache;
|
return messageCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getAccount() {
|
||||||
return username;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ACI getAci() {
|
public ACI getAci() {
|
||||||
|
@ -841,11 +841,11 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceAddress getSelfAddress() {
|
public SignalServiceAddress getSelfAddress() {
|
||||||
return new SignalServiceAddress(aci, username);
|
return new SignalServiceAddress(aci, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecipientId getSelfRecipientId() {
|
public RecipientId getSelfRecipientId() {
|
||||||
return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), username));
|
return recipientStore.resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(), account));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEncryptedDeviceName() {
|
public String getEncryptedDeviceName() {
|
||||||
|
|
|
@ -11,11 +11,11 @@ DBus API for signal-cli - A commandline and dbus interface for the Signal messen
|
||||||
|
|
||||||
== Synopsis
|
== Synopsis
|
||||||
|
|
||||||
*signal-cli* [--verbose] [--config CONFIG] [-u USERNAME] [-o {plain-text,json}] daemon [--system]
|
*signal-cli* [--verbose] [--config CONFIG] [-a ACCOUNT] [-o {plain-text,json}] daemon [--system]
|
||||||
|
|
||||||
*dbus-send* [--system | --session] [--print-reply] --type=method_call --dest="org.asamk.Signal" /org/asamk/Signal[/_<phonenum>] 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[/_<phonenum>] org.asamk.Signal.<method> [string:<string argument>] [array:<type>:<array argument>]
|
||||||
|
|
||||||
Note: when daemon was started without explicit `-u USERNAME`, the `dbus-send` command requires adding the phone number in `/org/asamk/Signal/_<phonenum>`.
|
Note: when daemon was started without explicit `-a ACCOUNT`, the `dbus-send` command requires adding the phone number in `/org/asamk/Signal/_<phonenum>`.
|
||||||
|
|
||||||
== Description
|
== Description
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ Phone numbers always have the format +<countrycode><regional number>
|
||||||
== Methods
|
== Methods
|
||||||
|
|
||||||
=== Control methods
|
=== Control methods
|
||||||
These methods are available if the daemon is started anonymously (without an explicit `-u USERNAME`).
|
These methods are available if the daemon is started anonymously (without an explicit `-a ACCOUNT`).
|
||||||
Requests are sent to `/org/asamk/Signal`; requests related to individual accounts are sent to
|
Requests are sent to `/org/asamk/Signal`; requests related to individual accounts are sent to
|
||||||
`/org/asamk/Signal/_441234567890` where the + dialing code is replaced by an underscore (_).
|
`/org/asamk/Signal/_441234567890` where the + dialing code is replaced by an underscore (_).
|
||||||
Only `version()` is activated in single-account mode; the rest are disabled.
|
Only `version()` is activated in single-account mode; the rest are disabled.
|
||||||
|
@ -596,7 +596,7 @@ dbus-send --print-reply --type=method_call --dest="org.asamk.Signal" /org/asamk/
|
||||||
Send a group message::
|
Send a group message::
|
||||||
dbus-send --session --print-reply --type=method_call --dest=org.asamk.Signal /org/asamk/Signal org.asamk.Signal.sendGroupMessage string:'The message goes here' array:string:'/path/to/attachmnt1','/path/to/attachmnt2' array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
|
dbus-send --session --print-reply --type=method_call --dest=org.asamk.Signal /org/asamk/Signal org.asamk.Signal.sendGroupMessage string:'The message goes here' array:string:'/path/to/attachmnt1','/path/to/attachmnt2' array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
|
||||||
|
|
||||||
Print the group name corresponding to a groupId; the daemon runs on system bus, and was started without an explicit `-u USERNAME`::
|
Print the group name corresponding to a groupId; the daemon runs on system bus, and was started without an explicit `-a ACCOUNT`::
|
||||||
dbus-send --system --print-reply --type=method_call --dest='org.asamk.Signal' /org/asamk/Signal/_1234567890 org.asamk.Signal.getGroupName array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
|
dbus-send --system --print-reply --type=method_call --dest='org.asamk.Signal' /org/asamk/Signal/_1234567890 org.asamk.Signal.getGroupName array:byte:139,22,72,247,116,32,170,104,205,164,207,21,248,77,185
|
||||||
|
|
||||||
== Authors
|
== Authors
|
||||||
|
|
|
@ -11,7 +11,7 @@ signal-cli - A commandline and dbus interface for the Signal messenger
|
||||||
|
|
||||||
== Synopsis
|
== Synopsis
|
||||||
|
|
||||||
*signal-cli* [--config CONFIG] [-h | -v | -u USERNAME | --dbus | --dbus-system] command [command-options]
|
*signal-cli* [--config CONFIG] [-h | -v | -a ACCOUNT | --dbus | --dbus-system] command [command-options]
|
||||||
|
|
||||||
== Description
|
== Description
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ Set the path, where to store the config.
|
||||||
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`))
|
||||||
|
|
||||||
*-u* USERNAME, *--username* USERNAME::
|
*-a* ACCOUNT, *--account* ACCOUNT::
|
||||||
Specify your phone number, that will be your identifier.
|
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.
|
The phone number must include the country calling code, i.e. the number must start with a "+" sign.
|
||||||
|
|
||||||
|
@ -495,7 +495,7 @@ The path of the manifest.json or a zip file containing the sticker pack you wish
|
||||||
=== daemon
|
=== daemon
|
||||||
|
|
||||||
signal-cli can run in daemon mode and provides an experimental dbus interface.
|
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
|
If no `-a` account is given, all local accounts will be exported as separate dbus
|
||||||
objects under the same bus name.
|
objects under the same bus name.
|
||||||
|
|
||||||
*--system*::
|
*--system*::
|
||||||
|
@ -506,37 +506,37 @@ Don’t download attachments of received messages.
|
||||||
== Examples
|
== Examples
|
||||||
|
|
||||||
Register a number (with SMS verification)::
|
Register a number (with SMS verification)::
|
||||||
signal-cli -u USERNAME register
|
signal-cli -a ACCOUNT register
|
||||||
|
|
||||||
Verify the number using the code received via SMS or voice::
|
Verify the number using the code received via SMS or voice::
|
||||||
signal-cli -u USERNAME verify CODE
|
signal-cli -a ACCOUNT verify CODE
|
||||||
|
|
||||||
Send a message to one or more recipients::
|
Send a message to one or more recipients::
|
||||||
signal-cli -u USERNAME send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]]
|
signal-cli -a ACCOUNT send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]]
|
||||||
|
|
||||||
Pipe the message content from another process::
|
Pipe the message content from another process::
|
||||||
uname -a | signal-cli -u USERNAME send [RECIPIENT [RECIPIENT ...]]
|
uname -a | signal-cli -a ACCOUNT send [RECIPIENT [RECIPIENT ...]]
|
||||||
|
|
||||||
Create a group::
|
Create a group::
|
||||||
signal-cli -u USERNAME updateGroup -n "Group name" -m [MEMBER [MEMBER ...]]
|
signal-cli -a ACCOUNT updateGroup -n "Group name" -m [MEMBER [MEMBER ...]]
|
||||||
|
|
||||||
Add member to a group::
|
Add member to a group::
|
||||||
signal-cli -u USERNAME updateGroup -g GROUP_ID -m "NEW_MEMBER"
|
signal-cli -a ACCOUNT updateGroup -g GROUP_ID -m "NEW_MEMBER"
|
||||||
|
|
||||||
Accept a group invitation::
|
Accept a group invitation::
|
||||||
signal-cli -u USERNAME updateGroup -g GROUP_ID
|
signal-cli -a ACCOUNT updateGroup -g GROUP_ID
|
||||||
|
|
||||||
Leave a group::
|
Leave a group::
|
||||||
signal-cli -u USERNAME quitGroup -g GROUP_ID
|
signal-cli -a ACCOUNT quitGroup -g GROUP_ID
|
||||||
|
|
||||||
Send a message to a group::
|
Send a message to a group::
|
||||||
signal-cli -u USERNAME send -m "This is a message" -g GROUP_ID
|
signal-cli -a ACCOUNT send -m "This is a message" -g GROUP_ID
|
||||||
|
|
||||||
Trust new key, after having verified it::
|
Trust new key, after having verified it::
|
||||||
signal-cli -u USERNAME trust -v SAFETY_NUMBER NUMBER
|
signal-cli -a ACCOUNT trust -v SAFETY_NUMBER NUMBER
|
||||||
|
|
||||||
Trust new key, without having verified it. Only use this if you don't care about security::
|
Trust new key, without having verified it. Only use this if you don't care about security::
|
||||||
signal-cli -u USERNAME trust -a NUMBER
|
signal-cli -a ACCOUNT trust -a NUMBER
|
||||||
|
|
||||||
== Exit codes
|
== Exit codes
|
||||||
* *1*: Error is probably caused and fixable by the user
|
* *1*: Error is probably caused and fixable by the user
|
||||||
|
|
126
run_tests.sh
126
run_tests.sh
|
@ -47,13 +47,13 @@ register() {
|
||||||
PIN=$2
|
PIN=$2
|
||||||
echo -n "Enter a captcha token (https://signalcaptchas.org/staging/challenge/generate.html): "
|
echo -n "Enter a captcha token (https://signalcaptchas.org/staging/challenge/generate.html): "
|
||||||
read CAPTCHA
|
read CAPTCHA
|
||||||
run_main -u "$NUMBER" register --captcha "$CAPTCHA"
|
run_main -a "$NUMBER" register --captcha "$CAPTCHA"
|
||||||
echo -n "Enter validation code for ${NUMBER}: "
|
echo -n "Enter validation code for ${NUMBER}: "
|
||||||
read CODE
|
read CODE
|
||||||
if [ -z "$PIN" ]; then
|
if [ -z "$PIN" ]; then
|
||||||
run_main -u "$NUMBER" verify "$CODE"
|
run_main -a "$NUMBER" verify "$CODE"
|
||||||
else
|
else
|
||||||
run_main -u "$NUMBER" verify "$CODE" --pin "$PIN"
|
run_main -a "$NUMBER" verify "$CODE" --pin "$PIN"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,12 +64,12 @@ link() {
|
||||||
mkfifo "$LINK_CODE_FILE"
|
mkfifo "$LINK_CODE_FILE"
|
||||||
run_linked link -n "test-device" >"$LINK_CODE_FILE" &
|
run_linked link -n "test-device" >"$LINK_CODE_FILE" &
|
||||||
read LINK_CODE <"$LINK_CODE_FILE"
|
read LINK_CODE <"$LINK_CODE_FILE"
|
||||||
run_main -u "$NUMBER" addDevice --uri "$LINK_CODE"
|
run_main -a "$NUMBER" addDevice --uri "$LINK_CODE"
|
||||||
wait
|
wait
|
||||||
run_linked -u "$NUMBER" send --note-to-self -m hi
|
run_linked -a "$NUMBER" send --note-to-self -m hi
|
||||||
run_main -u "$NUMBER" receive
|
run_main -a "$NUMBER" receive
|
||||||
run_linked -u "$NUMBER" receive
|
run_linked -a "$NUMBER" receive
|
||||||
run_main -u "$NUMBER" receive
|
run_main -a "$NUMBER" receive
|
||||||
}
|
}
|
||||||
|
|
||||||
run_main --version
|
run_main --version
|
||||||
|
@ -83,12 +83,12 @@ sleep 5
|
||||||
|
|
||||||
|
|
||||||
## DBus
|
## DBus
|
||||||
#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running || true
|
#run_main -a "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running || true
|
||||||
#run_main daemon &
|
#run_main daemon &
|
||||||
#DAEMON_PID=$!
|
#DAEMON_PID=$!
|
||||||
#sleep 10
|
#sleep 10
|
||||||
#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
|
#run_main -a "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
|
||||||
#run_main -u "$NUMBER_2" --dbus receive
|
#run_main -a "$NUMBER_2" --dbus receive
|
||||||
#kill "$DAEMON_PID"
|
#kill "$DAEMON_PID"
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ FIFO_FILE="${PATH_MAIN}/dbus-fifo"
|
||||||
rm -f "$FIFO_FILE"
|
rm -f "$FIFO_FILE"
|
||||||
mkfifo "$FIFO_FILE"
|
mkfifo "$FIFO_FILE"
|
||||||
|
|
||||||
run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
|
run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
|
||||||
run_main -u "$NUMBER_2" jsonRpc < "$FIFO_FILE" &
|
run_main -a "$NUMBER_2" jsonRpc < "$FIFO_FILE" &
|
||||||
|
|
||||||
exec 3<> "$FIFO_FILE"
|
exec 3<> "$FIFO_FILE"
|
||||||
echo '{"jsonrpc":"2.0","id":"id","method":"updateContact","params":{"recipient":"'"$NUMBER_1"'","name":"NUMBER_1","expiration":10}}' >&3
|
echo '{"jsonrpc":"2.0","id":"id","method":"updateContact","params":{"recipient":"'"$NUMBER_1"'","name":"NUMBER_1","expiration":10}}' >&3
|
||||||
|
@ -126,75 +126,75 @@ exec 3>&-
|
||||||
|
|
||||||
wait
|
wait
|
||||||
|
|
||||||
run_main -u "$NUMBER_1" setPin "$TEST_PIN_1"
|
run_main -a "$NUMBER_1" setPin "$TEST_PIN_1"
|
||||||
run_main -u "$NUMBER_2" removePin
|
run_main -a "$NUMBER_2" removePin
|
||||||
|
|
||||||
## Contacts
|
## Contacts
|
||||||
run_main -u "$NUMBER_2" updateContact "$NUMBER_1" -n NUMBER_1 -e 10
|
run_main -a "$NUMBER_2" updateContact "$NUMBER_1" -n NUMBER_1 -e 10
|
||||||
run_main -u "$NUMBER_2" block "$NUMBER_1"
|
run_main -a "$NUMBER_2" block "$NUMBER_1"
|
||||||
run_main -u "$NUMBER_2" unblock "$NUMBER_1"
|
run_main -a "$NUMBER_2" unblock "$NUMBER_1"
|
||||||
run_main -u "$NUMBER_2" listContacts
|
run_main -a "$NUMBER_2" listContacts
|
||||||
|
|
||||||
run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
|
run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
|
||||||
run_main -u "$NUMBER_2" receive
|
run_main -a "$NUMBER_2" receive
|
||||||
run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
|
run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
|
||||||
run_main -u "$NUMBER_1" receive
|
run_main -a "$NUMBER_1" receive
|
||||||
run_main -u "$NUMBER_2" receive
|
run_main -a "$NUMBER_2" receive
|
||||||
## Groups
|
## Groups
|
||||||
GROUP_ID=$(run_main -u "$NUMBER_1" updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | grep -oP '(?<=").+(?=")')
|
GROUP_ID=$(run_main -a "$NUMBER_1" updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | grep -oP '(?<=").+(?=")')
|
||||||
run_main -u "$NUMBER_1" send "$NUMBER_2" -m first
|
run_main -a "$NUMBER_1" send "$NUMBER_2" -m first
|
||||||
run_main -u "$NUMBER_1" updateGroup -g "$GROUP_ID" -n GRUPPE_UMB -m "$NUMBER_2" --admin "$NUMBER_2" --remove-admin "$NUMBER_2" --description DESCRIPTION --link=enabled-with-approval --set-permission-add-member=only-admins --set-permission-edit-details=only-admins -e 42
|
run_main -a "$NUMBER_1" updateGroup -g "$GROUP_ID" -n GRUPPE_UMB -m "$NUMBER_2" --admin "$NUMBER_2" --remove-admin "$NUMBER_2" --description DESCRIPTION --link=enabled-with-approval --set-permission-add-member=only-admins --set-permission-edit-details=only-admins -e 42
|
||||||
run_main -u "$NUMBER_1" listGroups -d
|
run_main -a "$NUMBER_1" listGroups -d
|
||||||
run_main -u "$NUMBER_1" --output=json listGroups -d
|
run_main -a "$NUMBER_1" --output=json listGroups -d
|
||||||
run_main -u "$NUMBER_2" --verbose receive
|
run_main -a "$NUMBER_2" --verbose receive
|
||||||
run_main -u "$NUMBER_2" quitGroup -g "$GROUP_ID"
|
run_main -a "$NUMBER_2" quitGroup -g "$GROUP_ID"
|
||||||
run_main -u "$NUMBER_2" listGroups -d
|
run_main -a "$NUMBER_2" listGroups -d
|
||||||
run_main -u "$NUMBER_2" --output=json listGroups -d
|
run_main -a "$NUMBER_2" --output=json listGroups -d
|
||||||
run_main -u "$NUMBER_1" receive
|
run_main -a "$NUMBER_1" receive
|
||||||
run_main -u "$NUMBER_1" updateGroup -g "$GROUP_ID" -m "$NUMBER_2"
|
run_main -a "$NUMBER_1" updateGroup -g "$GROUP_ID" -m "$NUMBER_2"
|
||||||
run_main -u "$NUMBER_1" --verbose block -g "$GROUP_ID"
|
run_main -a "$NUMBER_1" --verbose block -g "$GROUP_ID"
|
||||||
run_main -u "$NUMBER_1" --verbose unblock -g "$GROUP_ID"
|
run_main -a "$NUMBER_1" --verbose unblock -g "$GROUP_ID"
|
||||||
|
|
||||||
## Identities
|
## Identities
|
||||||
run_main -u "$NUMBER_1" listIdentities
|
run_main -a "$NUMBER_1" listIdentities
|
||||||
run_main -u "$NUMBER_2" listIdentities
|
run_main -a "$NUMBER_2" listIdentities
|
||||||
run_main -u "$NUMBER_2" trust "$NUMBER_1" -a
|
run_main -a "$NUMBER_2" trust "$NUMBER_1" -a
|
||||||
|
|
||||||
## Basic send/receive
|
## Basic send/receive
|
||||||
for OUTPUT in "plain-text" "json"; do
|
for OUTPUT in "plain-text" "json"; do
|
||||||
run_main -u "$NUMBER_1" --output="$OUTPUT" getUserStatus "$NUMBER_1" "$NUMBER_2" "+111111111"
|
run_main -a "$NUMBER_1" --output="$OUTPUT" getUserStatus "$NUMBER_1" "$NUMBER_2" "+111111111"
|
||||||
run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
|
run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
|
||||||
run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
|
run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
|
||||||
run_main -u "$NUMBER_1" send -g "$GROUP_ID" -m hi -a LICENSE
|
run_main -a "$NUMBER_1" send -g "$GROUP_ID" -m hi -a LICENSE
|
||||||
TIMESTAMP=$(uname -a | run_main -u "$NUMBER_1" send "$NUMBER_2")
|
TIMESTAMP=$(uname -a | run_main -a "$NUMBER_1" send "$NUMBER_2")
|
||||||
run_main -u "$NUMBER_2" sendReaction "$NUMBER_1" -e 🍀 -a "$NUMBER_1" -t "$TIMESTAMP"
|
run_main -a "$NUMBER_2" sendReaction "$NUMBER_1" -e 🍀 -a "$NUMBER_1" -t "$TIMESTAMP"
|
||||||
run_main -u "$NUMBER_1" remoteDelete "$NUMBER_2" -t "$TIMESTAMP"
|
run_main -a "$NUMBER_1" remoteDelete "$NUMBER_2" -t "$TIMESTAMP"
|
||||||
run_main -u "$NUMBER_2" --output="$OUTPUT" receive
|
run_main -a "$NUMBER_2" --output="$OUTPUT" receive
|
||||||
run_main -u "$NUMBER_1" --output="$OUTPUT" receive
|
run_main -a "$NUMBER_1" --output="$OUTPUT" receive
|
||||||
run_main -u "$NUMBER_1" send -e "$NUMBER_2"
|
run_main -a "$NUMBER_1" send -e "$NUMBER_2"
|
||||||
run_main -u "$NUMBER_2" --output="$OUTPUT" receive
|
run_main -a "$NUMBER_2" --output="$OUTPUT" receive
|
||||||
done
|
done
|
||||||
|
|
||||||
## Profile
|
## Profile
|
||||||
run_main -u "$NUMBER_1" updateProfile --given-name=GIVEN --family-name=FAMILY --about=ABOUT --about-emoji=EMOJI --avatar=LICENSE
|
run_main -a "$NUMBER_1" updateProfile --given-name=GIVEN --family-name=FAMILY --about=ABOUT --about-emoji=EMOJI --avatar=LICENSE
|
||||||
|
|
||||||
## Provisioning
|
## Provisioning
|
||||||
link "$NUMBER_1"
|
link "$NUMBER_1"
|
||||||
link "$NUMBER_2"
|
link "$NUMBER_2"
|
||||||
run_main -u "$NUMBER_1" listDevices
|
run_main -a "$NUMBER_1" listDevices
|
||||||
run_linked -u "$NUMBER_1" sendSyncRequest
|
run_linked -a "$NUMBER_1" sendSyncRequest
|
||||||
run_main -u "$NUMBER_1" sendContacts
|
run_main -a "$NUMBER_1" sendContacts
|
||||||
|
|
||||||
for OUTPUT in "plain-text" "json"; do
|
for OUTPUT in "plain-text" "json"; do
|
||||||
run_main -u "$NUMBER_1" send "$NUMBER_2" -m hi
|
run_main -a "$NUMBER_1" send "$NUMBER_2" -m hi
|
||||||
run_main -u "$NUMBER_2" send "$NUMBER_1" -m hi
|
run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi
|
||||||
run_main -u "$NUMBER_2" --output="$OUTPUT" receive
|
run_main -a "$NUMBER_2" --output="$OUTPUT" receive
|
||||||
run_main -u "$NUMBER_1" --output="$OUTPUT" receive
|
run_main -a "$NUMBER_1" --output="$OUTPUT" receive
|
||||||
run_linked -u "$NUMBER_1" --output="$OUTPUT" receive
|
run_linked -a "$NUMBER_1" --output="$OUTPUT" receive
|
||||||
done
|
done
|
||||||
|
|
||||||
run_main -u "$NUMBER_1" removeDevice -d 2
|
run_main -a "$NUMBER_1" removeDevice -d 2
|
||||||
|
|
||||||
## Unregister
|
## Unregister
|
||||||
run_main -u "$NUMBER_1" unregister
|
run_main -a "$NUMBER_1" unregister
|
||||||
run_main -u "$NUMBER_2" unregister --delete-account
|
run_main -a "$NUMBER_2" unregister --delete-account
|
||||||
|
|
|
@ -13,11 +13,11 @@ public class DbusConfig {
|
||||||
return getObjectPath(null);
|
return getObjectPath(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getObjectPath(String username) {
|
public static String getObjectPath(String account) {
|
||||||
if (username == null) {
|
if (account == null) {
|
||||||
return SIGNAL_OBJECT_BASE_PATH;
|
return SIGNAL_OBJECT_BASE_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SIGNAL_OBJECT_BASE_PATH + "/" + username.replace('+', '_');
|
return SIGNAL_OBJECT_BASE_PATH + "/" + account.replace('+', '_');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,13 +49,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||||
"The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
|
"The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
|
||||||
final var recipientName = e.getSender().getLegacyIdentifier();
|
final var recipientName = e.getSender().getLegacyIdentifier();
|
||||||
writer.println(
|
writer.println(
|
||||||
"Use 'signal-cli -u {} listIdentities -n {}', verify the key and run 'signal-cli -u {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
|
"Use 'signal-cli -a {} listIdentities -n {}', verify the key and run 'signal-cli -a {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
|
||||||
m.getSelfNumber(),
|
m.getSelfNumber(),
|
||||||
recipientName,
|
recipientName,
|
||||||
m.getSelfNumber(),
|
m.getSelfNumber(),
|
||||||
recipientName);
|
recipientName);
|
||||||
writer.println(
|
writer.println(
|
||||||
"If you don't care about security, use 'signal-cli -u {} trust -a {}' to trust it without verification",
|
"If you don't care about security, use 'signal-cli -a {} trust -a {}' to trust it without verification",
|
||||||
m.getSelfNumber(),
|
m.getSelfNumber(),
|
||||||
recipientName);
|
recipientName);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl {
|
||||||
) throws Error.Failure, Error.InvalidNumber {
|
) throws Error.Failure, Error.InvalidNumber {
|
||||||
if (!Manager.isValidNumber(number, null)) {
|
if (!Manager.isValidNumber(number, null)) {
|
||||||
throw new SignalControl.Error.InvalidNumber(
|
throw new SignalControl.Error.InvalidNumber(
|
||||||
"Invalid username (phone number), make sure you include the country code.");
|
"Invalid account (phone number), make sure you include the country code.");
|
||||||
}
|
}
|
||||||
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
|
try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) {
|
||||||
registrationManager.register(voiceVerification, captcha);
|
registrationManager.register(voiceVerification, captcha);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue