### Changed
- libzkgroup dependency is no longer required
- Renamed `-u` and `--username` flags to `-a` and `--account` to prevent confusion with upcoming Signal usernames. The old flags are also still supported for now.
-- Respect phone number sharing mode and unlisted state set by main device
+- Respect phone number sharing mode and unlisted state set by primary device
- Adapt register command to reactivate account if possible.
- dbus-java now uses Java 16 native unix sockets, which should provide better cross-platform compatibility
- If sending to a recipient fails (e.g. unregistered) signal-cli now exits with a success exit code and prints additional information about the failure.
### Added
- New global parameter `--trust-new-identities=always` to allow trusting any new identity key without verification
-- New parameter `--device-name` for `updateAccount` command to change the device name (also works for the main device)
+- New parameter `--device-name` for `updateAccount` command to change the device name (also works for the primary device)
- New SignalControl DBus interface, to register/verify/link new accounts
- New `jsonRpc` command that provides a JSON-RPC based API on stdout/stdin
- Support for announcement groups
### Added
- A manual page for the DBus interface (Thanks @bublath, @exquo)
- Remote message delete command (Thanks @adaptivegarage)
-- sendSyncRequest command to request complete contact/group list from master device
+- sendSyncRequest command to request complete contact/group list from primary device
- New `--delete-account` argument for unregister (Dangerous)
- New `--family-name` argument for updateProfile
- Sending reaction to group (Thanks @adaptivegarage)
- Displaying of address for messages from untrusted identities
- Handling of recipient number or uuid changes (e.g. after account deletions)
-- Only respond to sync requests from master device
+- Only respond to sync requests from primary device
- Display of quit group messages
### Changed
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
Configuration getConfiguration();
- void updateConfiguration(Configuration configuration) throws IOException, NotMasterDeviceException;
+ void updateConfiguration(Configuration configuration) throws IOException, NotPrimaryDeviceException;
/**
* Update the user's profile.
void addDeviceLink(URI linkUri) throws IOException, InvalidDeviceLinkException;
- void setRegistrationLockPin(Optional<String> pin) throws IOException, NotMasterDeviceException;
+ void setRegistrationLockPin(Optional<String> pin) throws IOException, NotPrimaryDeviceException;
Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
void setContactName(
RecipientIdentifier.Single recipient, String name
- ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException;
+ ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
void setContactsBlocked(
Collection<RecipientIdentifier.Single> recipient, boolean blocked
- ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException;
+ ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
void setGroupsBlocked(
Collection<GroupId> groupId, boolean blocked
- ) throws GroupNotFoundException, IOException, NotMasterDeviceException;
+ ) throws GroupNotFoundException, IOException, NotPrimaryDeviceException;
/**
* Change the expiration timer for a contact
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.Message;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
@Override
public void updateConfiguration(
Configuration configuration
- ) throws NotMasterDeviceException {
- if (!account.isMasterDevice()) {
- throw new NotMasterDeviceException();
+ ) throws NotPrimaryDeviceException {
+ if (!account.isPrimaryDevice()) {
+ throw new NotPrimaryDeviceException();
}
final var configurationStore = account.getConfigurationStore();
}
@Override
- public void setRegistrationLockPin(Optional<String> pin) throws IOException, NotMasterDeviceException {
- if (!account.isMasterDevice()) {
- throw new NotMasterDeviceException();
+ public void setRegistrationLockPin(Optional<String> pin) throws IOException, NotPrimaryDeviceException {
+ if (!account.isPrimaryDevice()) {
+ throw new NotPrimaryDeviceException();
}
if (pin.isPresent()) {
context.getAccountHelper().setRegistrationPin(pin.get());
@Override
public void setContactName(
RecipientIdentifier.Single recipient, String name
- ) throws NotMasterDeviceException, UnregisteredRecipientException {
- if (!account.isMasterDevice()) {
- throw new NotMasterDeviceException();
+ ) throws NotPrimaryDeviceException, UnregisteredRecipientException {
+ if (!account.isPrimaryDevice()) {
+ throw new NotPrimaryDeviceException();
}
context.getContactHelper().setContactName(context.getRecipientHelper().resolveRecipient(recipient), name);
}
@Override
public void setContactsBlocked(
Collection<RecipientIdentifier.Single> recipients, boolean blocked
- ) throws NotMasterDeviceException, IOException, UnregisteredRecipientException {
- if (!account.isMasterDevice()) {
- throw new NotMasterDeviceException();
+ ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException {
+ if (!account.isPrimaryDevice()) {
+ throw new NotPrimaryDeviceException();
}
if (recipients.size() == 0) {
return;
@Override
public void setGroupsBlocked(
final Collection<GroupId> groupIds, final boolean blocked
- ) throws GroupNotFoundException, NotMasterDeviceException, IOException {
- if (!account.isMasterDevice()) {
- throw new NotMasterDeviceException();
+ ) throws GroupNotFoundException, NotPrimaryDeviceException, IOException {
+ if (!account.isPrimaryDevice()) {
+ throw new NotPrimaryDeviceException();
}
if (groupIds.size() == 0) {
return;
}
try (signalAccount) {
- if (signalAccount.isMasterDevice()) {
- logger.debug("Account is a master device.");
+ if (signalAccount.isPrimaryDevice()) {
+ logger.debug("Account is a primary device.");
return false;
}
if (signalAccount.isRegistered()
public void execute(Context context) throws Throwable {
if (context.getAccount().getStorageKey() != null) {
context.getStorageHelper().readDataFromStorage();
- } else if (!context.getAccount().isMasterDevice()) {
+ } else if (!context.getAccount().isPrimaryDevice()) {
context.getSyncHelper().requestSyncKeys();
}
}
package org.asamk.signal.manager.api;
-public class NotMasterDeviceException extends Exception {
+public class NotPrimaryDeviceException extends Exception {
- public NotMasterDeviceException() {
+ public NotPrimaryDeviceException() {
super("This function is not supported for linked devices.");
}
}
if (account.getAci() == null || account.getPni() == null) {
checkWhoAmiI();
}
- if (!account.isMasterDevice() && account.getPniIdentityKeyPair() == null) {
+ if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
context.getSyncHelper().requestSyncPniIdentity();
}
updateAccountAttributes();
public void unregister() throws IOException {
// When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
- // If this is the master device, other users can't send messages to this number anymore.
+ // If this is the primary device, other users can't send messages to this number anymore.
// If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
dependencies.getAccountManager().setGcmId(Optional.empty());
ignoreAttachments));
}
}
- if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
+ if (syncMessage.getRequest().isPresent() && account.isPrimaryDevice()) {
var rm = syncMessage.getRequest().get();
if (rm.isContactsRequest()) {
actions.add(SendSyncContactsAction.create());
}
save();
}
- if (isMasterDevice() && getPniIdentityKeyPair() == null) {
+ if (isPrimaryDevice() && getPniIdentityKeyPair() == null) {
setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
}
}
return deviceId;
}
- public boolean isMasterDevice() {
+ public boolean isPrimaryDevice() {
return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
}
synchronized (cachedSessions) {
return getKeysLocked(recipientId).stream()
- // get all sessions for recipient except main device session
+ // get all sessions for recipient except primary device session
.filter(key -> key.deviceId() != 1 && key.recipientId().equals(recipientId))
.map(Key::deviceId)
.toList();
getContactName(number<s>) -> name<s>::
* number : Phone number
-* name : Contact's name in local storage (from the master device for a linked account, or the one set with setContactName); if not set, contact's profile name is used
+* name : Contact's name in local storage (from the primary device for a linked account, or the one set with setContactName); if not set, contact's profile name is used
Exceptions: None
=== unregister
Disable push support for this device, i.e. this device won't receive any more messages.
-If this is the master device, other users can't send messages to this number anymore.
+If this is the primary device, other users can't send messages to this number anymore.
Use "updateAccount" to undo this.
-To remove a linked device, use "removeDevice" from the master device.
+To remove a linked device, use "removeDevice" from the primary device.
*--delete-account*::
Delete account completely from server.
Can fix problems with receiving messages.
*-n* NAME, *--device-name* NAME::
-Set a new device name for the main or linked device
+Set a new device name for the primary or linked device
=== updateConfiguration
Update signal configs and sync them to linked devices.
-This command only works on the main devices.
+This command only works on the primary devices.
*--read-receipts* {true,false}::
Indicates if Signal should send read receipts.
=== addDevice
Link another device to this device.
-Only works, if this is the master device.
+Only works, if this is the primary device.
*--uri* URI::
Specify the uri contained in the QR code shown by the new device.
=== removeDevice
Remove a linked device.
-Only works, if this is the master device.
+Only works, if this is the primary device.
*-d* DEVICE_ID, *--device-id* DEVICE_ID::
Specify the device you want to remove.
=== sendContacts
Send a synchronization message with the local contacts list to all linked devices.
-This command should only be used if this is the master device.
+This command should only be used if this is the primary device.
=== sendSyncRequest
-Send a synchronization request message to the master device (for group, contacts, ...).
-The master device will respond with synchronization messages with full contact and group lists.
+Send a synchronization request message to the primary device (for group, contacts, ...).
+The primary device will respond with synchronization messages with full contact and group lists.
=== uploadStickerPack
@Override
public void attachToSubparser(final Subparser subparser) {
- subparser.help("Link another device to this device. Only works, if this is the master device.");
+ subparser.help("Link another device to this device. Only works, if this is the primary device.");
subparser.addArgument("--uri")
.required(true)
.help("Specify the uri contained in the QR code shown by the new device.");
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.output.OutputWriter;
final var recipients = CommandUtil.getSingleRecipientIdentifiers(contacts, m.getSelfNumber());
try {
m.setContactsBlocked(recipients, true);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync block to linked devices: " + e.getMessage(), e);
final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
try {
m.setGroupsBlocked(groupIds, true);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
logger.warn("Unknown group id: {}", e.getMessage());
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
m.setRegistrationLockPin(Optional.empty());
} catch (IOException e) {
throw new IOErrorException("Remove pin error: " + e.getMessage(), e);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
}
}
@Override
public void attachToSubparser(final Subparser subparser) {
- subparser.help("Send a synchronization request message to master device (for group, contacts, ...).");
+ subparser.help("Send a synchronization request message to primary device (for group, contacts, ...).");
}
@Override
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
m.setRegistrationLockPin(Optional.of(registrationLockPin));
} catch (IOException e) {
throw new IOErrorException("Set pin error: " + e.getMessage(), e);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
}
}
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.output.OutputWriter;
final var recipients = CommandUtil.getSingleRecipientIdentifiers(contacts, m.getSelfNumber());
try {
m.setContactsBlocked(recipients, false);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync unblock to linked devices: " + e.getMessage(), e);
final var groupIds = CommandUtil.getGroupIds(groupIdStrings);
try {
m.setGroupsBlocked(groupIds, false);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
logger.warn("Unknown group id: {}", e.getMessage());
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.Configuration;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
Optional.ofNullable(linkPreviews)));
} catch (IOException e) {
throw new IOErrorException("UpdateAccount error: " + e.getMessage(), e);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
}
}
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.output.OutputWriter;
import org.asamk.signal.util.CommandUtil;
}
} catch (IOException e) {
throw new IOErrorException("Update contact error: " + e.getMessage(), e);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (UnregisteredRecipientException e) {
throw new UserErrorException("The user " + e.getSender().getIdentifier() + " is not registered.");
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
@Override
public void setContactName(
final RecipientIdentifier.Single recipient, final String name
- ) throws NotMasterDeviceException {
+ ) throws NotPrimaryDeviceException {
signal.setContactName(recipient.getIdentifier(), name);
}
@Override
public void setContactsBlocked(
final Collection<RecipientIdentifier.Single> recipients, final boolean blocked
- ) throws NotMasterDeviceException, IOException {
+ ) throws NotPrimaryDeviceException, IOException {
for (final var recipient : recipients) {
signal.setContactBlocked(recipient.getIdentifier(), blocked);
}
import org.asamk.signal.manager.api.InvalidNumberException;
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.Message;
-import org.asamk.signal.manager.api.NotMasterDeviceException;
+import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.api.SendMessageResults;
public void setContactName(final String number, final String name) {
try {
m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new Error.Failure("Contact is not registered.");
public void setContactBlocked(final String number, final boolean blocked) {
try {
m.setContactsBlocked(List.of(getSingleRecipientIdentifier(number, m.getSelfNumber())), blocked);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
try {
m.setGroupsBlocked(List.of(getGroupId(groupId)), blocked);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage());
m.setRegistrationLockPin(Optional.empty());
} catch (IOException e) {
throw new Error.Failure("Remove pin error: " + e.getMessage());
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
}
m.setRegistrationLockPin(Optional.of(registrationLockPin));
} catch (IOException e) {
throw new Error.Failure("Set pin error: " + e.getMessage());
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
}
Optional.ofNullable(linkPreviews)));
} catch (IOException e) {
throw new Error.Failure("UpdateAccount error: " + e.getMessage());
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
}
private void setIsBlocked(final boolean isBlocked) {
try {
m.setGroupsBlocked(List.of(groupId), isBlocked);
- } catch (NotMasterDeviceException e) {
+ } catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage());