package org.asamk.signal.manager;
import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.storage.groups.GroupInfo;
-import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
public interface Manager extends Closeable {
static Manager init(
- String username,
+ String number,
File settingsPath,
ServiceEnvironment serviceEnvironment,
String userAgent,
) throws IOException, NotRegisteredException {
var pathConfig = PathConfig.createDefault(settingsPath);
- if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+ if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
throw new NotRegisteredException();
}
- var account = SignalAccount.load(pathConfig.getDataPath(), username, true, trustNewIdentity);
+ var account = SignalAccount.load(pathConfig.getDataPath(), number, true, trustNewIdentity);
if (!account.isRegistered()) {
throw new NotRegisteredException();
return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
}
- static List<String> getAllLocalUsernames(File settingsPath) {
+ static List<String> getAllLocalNumbers(File settingsPath) {
var pathConfig = PathConfig.createDefault(settingsPath);
final var dataPath = pathConfig.getDataPath();
final var files = dataPath.listFiles();
.collect(Collectors.toList());
}
- String getUsername();
-
- RecipientId getSelfRecipientId();
-
- int getDeviceId();
+ String getSelfNumber();
void checkAccountState() throws IOException;
void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException;
- Profile getRecipientProfile(RecipientId recipientId);
+ Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException;
- List<GroupInfo> getGroups();
+ List<Group> getGroups();
SendGroupMessageResults quitGroup(
GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
void sendContacts() throws IOException;
- List<Pair<RecipientId, Contact>> getContacts();
+ List<Pair<RecipientAddress, Contact>> getContacts();
- String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier);
+ String getContactOrProfileName(RecipientIdentifier.Single recipient);
- GroupInfo getGroup(GroupId groupId);
+ Group getGroup(GroupId groupId);
- List<IdentityInfo> getIdentities();
+ List<Identity> getIdentities();
- List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient);
+ List<Identity> getIdentities(RecipientIdentifier.Single recipient);
boolean trustIdentityVerified(RecipientIdentifier.Single recipient, byte[] fingerprint);
String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
- byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
-
SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address);
- SignalServiceAddress resolveSignalServiceAddress(UUID uuid);
-
- SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId);
-
@Override
void close() throws IOException;
import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.Sticker;
import org.asamk.signal.manager.storage.stickers.StickerPackId;
this::resolveSignalServiceAddress,
account.getRecipientStore(),
this::handleIdentityFailure,
- this::getGroup,
+ this::getGroupInfo,
this::refreshRegisteredUser);
this.groupHelper = new GroupHelper(account,
dependencies,
}
@Override
- public String getUsername() {
+ public String getSelfNumber() {
return account.getUsername();
}
- @Override
- public RecipientId getSelfRecipientId() {
- return account.getSelfRecipientId();
- }
-
- @Override
- public int getDeviceId() {
- return account.getDeviceId();
- }
-
@Override
public void checkAccountState() throws IOException {
if (account.getLastReceiveTimestamp() == 0) {
logger.debug("Failed to decrypt device name, maybe plain text?", e);
}
}
- return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
+ return new Device(d.getId(),
+ deviceName,
+ d.getCreated(),
+ d.getLastSeen(),
+ d.getId() == account.getDeviceId());
}).collect(Collectors.toList());
}
}
@Override
- public Profile getRecipientProfile(RecipientId recipientId) {
+ public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException {
+ return profileHelper.getRecipientProfile(resolveRecipient(recipient));
+ }
+
+ private Profile getRecipientProfile(RecipientId recipientId) {
return profileHelper.getRecipientProfile(recipientId);
}
@Override
- public List<GroupInfo> getGroups() {
- return account.getGroupStore().getGroups();
+ public List<Group> getGroups() {
+ return account.getGroupStore().getGroups().stream().map(this::toGroup).collect(Collectors.toList());
+ }
+
+ private Group toGroup(final GroupInfo groupInfo) {
+ if (groupInfo == null) {
+ return null;
+ }
+
+ return new Group(groupInfo.getGroupId(),
+ groupInfo.getTitle(),
+ groupInfo.getDescription(),
+ groupInfo.getGroupInviteLink(),
+ groupInfo.getMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getPendingMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getRequestingMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.getAdminMembers()
+ .stream()
+ .map(account.getRecipientStore()::resolveRecipientAddress)
+ .collect(Collectors.toSet()),
+ groupInfo.isBlocked(),
+ groupInfo.getMessageExpirationTime(),
+ groupInfo.isAnnouncementGroup(),
+ groupInfo.isMember(account.getSelfRecipientId()));
}
@Override
}
@Override
- public List<Pair<RecipientId, Contact>> getContacts() {
- return account.getContactStore().getContacts();
+ public List<Pair<RecipientAddress, Contact>> getContacts() {
+ return account.getContactStore()
+ .getContacts()
+ .stream()
+ .map(p -> new Pair<>(account.getRecipientStore().resolveRecipientAddress(p.first()), p.second()))
+ .collect(Collectors.toList());
}
@Override
- public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
+ public String getContactOrProfileName(RecipientIdentifier.Single recipient) {
final RecipientId recipientId;
try {
- recipientId = resolveRecipient(recipientIdentifier);
+ recipientId = resolveRecipient(recipient);
} catch (UnregisteredUserException e) {
return null;
}
}
@Override
- public GroupInfo getGroup(GroupId groupId) {
+ public Group getGroup(GroupId groupId) {
+ return toGroup(groupHelper.getGroup(groupId));
+ }
+
+ public GroupInfo getGroupInfo(GroupId groupId) {
return groupHelper.getGroup(groupId);
}
@Override
- public List<IdentityInfo> getIdentities() {
- return account.getIdentityKeyStore().getIdentities();
+ public List<Identity> getIdentities() {
+ return account.getIdentityKeyStore()
+ .getIdentities()
+ .stream()
+ .map(this::toIdentity)
+ .collect(Collectors.toList());
+ }
+
+ private Identity toIdentity(final IdentityInfo identityInfo) {
+ if (identityInfo == null) {
+ return null;
+ }
+
+ final var address = account.getRecipientStore().resolveRecipientAddress(identityInfo.getRecipientId());
+ return new Identity(address,
+ identityInfo.getIdentityKey(),
+ computeSafetyNumber(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
+ computeSafetyNumberForScanning(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
+ identityInfo.getTrustLevel(),
+ identityInfo.getDateAdded());
}
@Override
- public List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient) {
+ public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
IdentityInfo identity;
try {
identity = account.getIdentityKeyStore().getIdentity(resolveRecipient(recipient));
} catch (UnregisteredUserException e) {
identity = null;
}
- return identity == null ? List.of() : List.of(identity);
+ return identity == null ? List.of() : List.of(toIdentity(identity));
}
/**
return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
}
- @Override
- public byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
+ private byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized();
}
return resolveSignalServiceAddress(resolveRecipient(address));
}
- @Override
- public SignalServiceAddress resolveSignalServiceAddress(UUID uuid) {
- return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
- }
-
- @Override
- public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
+ private SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
final var address = account.getRecipientStore().resolveRecipientAddress(recipientId);
if (address.getUuid().isPresent()) {
return address.toSignalServiceAddress();
// Address in recipient store doesn't have a uuid, this shouldn't happen
// Try to retrieve the uuid from the server
final var number = address.getNumber().get();
+ final UUID uuid;
try {
- return resolveSignalServiceAddress(getRegisteredUser(number));
+ uuid = getRegisteredUser(number);
} catch (IOException e) {
logger.warn("Failed to get uuid for e164 number: {}", number, e);
// Return SignalServiceAddress with unknown UUID
return address.toSignalServiceAddress();
}
+ return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
}
private Set<RecipientId> resolveRecipients(Collection<RecipientIdentifier.Single> recipients) throws UnregisteredUserException {
}
public static RegistrationManager init(
- String username, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
+ String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
) throws IOException {
var pathConfig = PathConfig.createDefault(settingsPath);
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
- if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+ if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
var identityKey = KeyUtils.generateIdentityKeyPair();
var registrationId = KeyHelper.generateRegistrationId(false);
var profileKey = KeyUtils.createProfileKey();
var account = SignalAccount.create(pathConfig.getDataPath(),
- username,
+ number,
identityKey,
registrationId,
profileKey,
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
- var account = SignalAccount.load(pathConfig.getDataPath(), username, true, TrustNewIdentity.ON_FIRST_USE);
+ var account = SignalAccount.load(pathConfig.getDataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
public class UserAlreadyExists extends Exception {
- private final String username;
+ private final String number;
private final File fileName;
- public UserAlreadyExists(String username, File fileName) {
- this.username = username;
+ public UserAlreadyExists(String number, File fileName) {
+ this.number = number;
this.fileName = fileName;
}
- public String getUsername() {
- return username;
+ public String getNumber() {
+ return number;
}
public File getFileName() {
private final String name;
private final long created;
private final long lastSeen;
+ private final boolean thisDevice;
- public Device(long id, String name, long created, long lastSeen) {
+ public Device(long id, String name, long created, long lastSeen, final boolean thisDevice) {
this.id = id;
this.name = name;
this.created = created;
this.lastSeen = lastSeen;
+ this.thisDevice = thisDevice;
}
public long getId() {
public long getLastSeen() {
return lastSeen;
}
+
+ public boolean isThisDevice() {
+ return thisDevice;
+ }
}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+
+import java.util.Set;
+
+public class Group {
+
+ private final GroupId groupId;
+ private final String title;
+ private final String description;
+ private final GroupInviteLinkUrl groupInviteLinkUrl;
+ private final Set<RecipientAddress> members;
+ private final Set<RecipientAddress> pendingMembers;
+ private final Set<RecipientAddress> requestingMembers;
+ private final Set<RecipientAddress> adminMembers;
+ private final boolean isBlocked;
+ private final int messageExpirationTime;
+ private final boolean isAnnouncementGroup;
+ private final boolean isMember;
+
+ public Group(
+ final GroupId groupId,
+ final String title,
+ final String description,
+ final GroupInviteLinkUrl groupInviteLinkUrl,
+ final Set<RecipientAddress> members,
+ final Set<RecipientAddress> pendingMembers,
+ final Set<RecipientAddress> requestingMembers,
+ final Set<RecipientAddress> adminMembers,
+ final boolean isBlocked,
+ final int messageExpirationTime,
+ final boolean isAnnouncementGroup,
+ final boolean isMember
+ ) {
+ this.groupId = groupId;
+ this.title = title;
+ this.description = description;
+ this.groupInviteLinkUrl = groupInviteLinkUrl;
+ this.members = members;
+ this.pendingMembers = pendingMembers;
+ this.requestingMembers = requestingMembers;
+ this.adminMembers = adminMembers;
+ this.isBlocked = isBlocked;
+ this.messageExpirationTime = messageExpirationTime;
+ this.isAnnouncementGroup = isAnnouncementGroup;
+ this.isMember = isMember;
+ }
+
+ public GroupId getGroupId() {
+ return groupId;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public GroupInviteLinkUrl getGroupInviteLinkUrl() {
+ return groupInviteLinkUrl;
+ }
+
+ public Set<RecipientAddress> getMembers() {
+ return members;
+ }
+
+ public Set<RecipientAddress> getPendingMembers() {
+ return pendingMembers;
+ }
+
+ public Set<RecipientAddress> getRequestingMembers() {
+ return requestingMembers;
+ }
+
+ public Set<RecipientAddress> getAdminMembers() {
+ return adminMembers;
+ }
+
+ public boolean isBlocked() {
+ return isBlocked;
+ }
+
+ public int getMessageExpirationTime() {
+ return messageExpirationTime;
+ }
+
+ public boolean isAnnouncementGroup() {
+ return isAnnouncementGroup;
+ }
+
+ public boolean isMember() {
+ return isMember;
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.asamk.signal.manager.TrustLevel;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.whispersystems.libsignal.IdentityKey;
+
+import java.util.Date;
+
+public class Identity {
+
+ private final RecipientAddress recipient;
+ private final IdentityKey identityKey;
+ private final String safetyNumber;
+ private final byte[] scannableSafetyNumber;
+ private final TrustLevel trustLevel;
+ private final Date dateAdded;
+
+ public Identity(
+ final RecipientAddress recipient,
+ final IdentityKey identityKey,
+ final String safetyNumber,
+ final byte[] scannableSafetyNumber,
+ final TrustLevel trustLevel,
+ final Date dateAdded
+ ) {
+ this.recipient = recipient;
+ this.identityKey = identityKey;
+ this.safetyNumber = safetyNumber;
+ this.scannableSafetyNumber = scannableSafetyNumber;
+ this.trustLevel = trustLevel;
+ this.dateAdded = dateAdded;
+ }
+
+ public RecipientAddress getRecipient() {
+ return recipient;
+ }
+
+ public IdentityKey getIdentityKey() {
+ return this.identityKey;
+ }
+
+ public TrustLevel getTrustLevel() {
+ return this.trustLevel;
+ }
+
+ boolean isTrusted() {
+ return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
+ }
+
+ public Date getDateAdded() {
+ return this.dateAdded;
+ }
+
+ public byte[] getFingerprint() {
+ return identityKey.getPublicKey().serialize();
+ }
+
+ public String getSafetyNumber() {
+ return safetyNumber;
+ }
+
+ public byte[] getScannableSafetyNumber() {
+ return scannableSafetyNumber;
+ }
+}
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
public static Single fromAddress(SignalServiceAddress address) {
return new Uuid(address.getUuid());
}
+
+ public static Single fromAddress(RecipientAddress address) {
+ if (address.getNumber().isPresent()) {
+ return new Number(address.getNumber().get());
+ } else if (address.getUuid().isPresent()) {
+ return new Uuid(address.getUuid().get());
+ }
+ throw new AssertionError("RecipientAddress without identifier");
+ }
+
+ public abstract String getIdentifier();
}
public static class Uuid extends Single {
public int hashCode() {
return uuid.hashCode();
}
+
+ @Override
+ public String getIdentifier() {
+ return uuid.toString();
+ }
}
public static class Number extends Single {
public int hashCode() {
return number.hashCode();
}
+
+ @Override
+ public String getIdentifier() {
+ return number;
+ }
}
public static class Group extends RecipientIdentifier {
}
}
+ public String getLegacyIdentifier() {
+ if (e164.isPresent()) {
+ return e164.get();
+ } else if (uuid.isPresent()) {
+ return uuid.get().toString();
+ } else {
+ throw new AssertionError("Given the checks in the constructor, this should not be possible.");
+ }
+ }
+
public boolean matches(RecipientAddress other) {
return (uuid.isPresent() && other.uuid.isPresent() && uuid.get().equals(other.uuid.get())) || (
e164.isPresent() && other.e164.isPresent() && e164.get().equals(other.e164.get())
*/
public interface Signal extends DBusInterface {
+ String getNumber();
+
long sendMessage(
String message, List<String> attachments, String recipient
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity;
) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity;
void sendReadReceipt(
- String recipient, List<Long> targetSentTimestamp
+ String recipient, List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity;
long sendRemoteDeleteMessage(
}
if (username == null) {
- var usernames = Manager.getAllLocalUsernames(dataPath);
+ var usernames = Manager.getAllLocalNumbers(dataPath);
if (command instanceof MultiLocalCommand) {
handleMultiLocalCommand((MultiLocalCommand) command,
final var recipientName = getLegacyIdentifier(m.resolveSignalServiceAddress(e.getSender()));
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",
- m.getUsername(),
+ m.getSelfNumber(),
recipientName,
- m.getUsername(),
+ m.getSelfNumber(),
recipientName);
writer.println(
"If you don't care about security, use 'signal-cli -u {} trust -a {}' to trust it without verification",
- m.getUsername(),
+ m.getSelfNumber(),
recipientName);
} else {
writer.println("Exception: {} ({})", exception.getMessage(), exception.getClass().getSimpleName());
private void printMention(
PlainTextWriter writer, SignalServiceDataMessage.Mention mention
) {
- final var address = m.resolveSignalServiceAddress(mention.getUuid());
+ final var address = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid()));
writer.println("- {}: {} (length: {})", formatContact(address), mention.getStart(), mention.getLength());
}
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
final var contacts = ns.<String>getList("recipient");
- for (var contact : CommandUtil.getSingleRecipientIdentifiers(contacts, m.getUsername())) {
+ for (var contact : CommandUtil.getSingleRecipientIdentifiers(contacts, m.getSelfNumber())) {
try {
m.setContactBlocked(contact, true);
} catch (NotMasterDeviceException e) {
try (var conn = DBusConnection.getConnection(busType)) {
final var signalControl = new DbusSignalControlImpl(c, m -> {
try {
- final var objectPath = DbusConfig.getObjectPath(m.getUsername());
+ final var objectPath = DbusConfig.getObjectPath(m.getSelfNumber());
return run(conn, objectPath, m, outputWriter, ignoreAttachments);
} catch (DBusException e) {
logger.error("Failed to export object", e);
var newGroupId = results.first();
if (outputWriter instanceof JsonWriter) {
final var writer = (JsonWriter) outputWriter;
- if (!m.getGroup(newGroupId).isMember(m.getSelfRecipientId())) {
+ if (!m.getGroup(newGroupId).isMember()) {
writer.write(Map.of("groupId", newGroupId.toBase64(), "onlyRequested", true));
} else {
writer.write(Map.of("groupId", newGroupId.toBase64()));
}
} else {
final var writer = (PlainTextWriter) outputWriter;
- if (!m.getGroup(newGroupId).isMember(m.getSelfRecipientId())) {
+ if (!m.getGroup(newGroupId).isMember()) {
writer.println("Requested to join group \"{}\"", newGroupId.toBase64());
} else {
writer.println("Joined group \"{}\"", newGroupId.toBase64());
try {
writer.println("{}", m.getDeviceLinkUri());
try (var manager = m.finishDeviceLink(deviceName)) {
- writer.println("Associated with: {}", manager.getUsername());
+ writer.println("Associated with: {}", manager.getSelfNumber());
}
} catch (TimeoutException e) {
throw new UserErrorException("Link request timed out, please try again.");
throw new IOErrorException("Link request error: " + e.getMessage(), e);
} catch (UserAlreadyExists e) {
throw new UserErrorException("The user "
- + e.getUsername()
+ + e.getNumber()
+ " already exists\nDelete \""
+ e.getFileName()
+ "\" before trying again.");
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.manager.Manager;
+import java.util.UUID;
import java.util.stream.Collectors;
-import static org.asamk.signal.util.Util.getLegacyIdentifier;
-
public class ListContactsCommand implements JsonRpcLocalCommand {
@Override
for (var c : contacts) {
final var contact = c.second();
writer.println("Number: {} Name: {} Blocked: {} Message expiration: {}",
- getLegacyIdentifier(m.resolveSignalServiceAddress(c.first())),
+ c.first().getLegacyIdentifier(),
contact.getName(),
contact.isBlocked(),
contact.getMessageExpirationTime() == 0
} else {
final var writer = (JsonWriter) outputWriter;
final var jsonContacts = contacts.stream().map(contactPair -> {
- final var address = m.resolveSignalServiceAddress(contactPair.first());
+ final var address = contactPair.first();
final var contact = contactPair.second();
- return new JsonContact(address.getNumber().orNull(),
- address.getUuid().toString(),
+ return new JsonContact(address.getNumber().orElse(null),
+ address.getUuid().map(UUID::toString).orElse(null),
contact.getName(),
contact.isBlocked(),
contact.getMessageExpirationTime());
if (outputWriter instanceof PlainTextWriter) {
final var writer = (PlainTextWriter) outputWriter;
for (var d : devices) {
- writer.println("- Device {}{}:", d.getId(), (d.getId() == m.getDeviceId() ? " (this device)" : ""));
+ writer.println("- Device {}{}:", d.getId(), (d.isThisDevice() ? " (this device)" : ""));
writer.indent(w -> {
w.println("Name: {}", d.getName());
w.println("Created: {}", DateUtils.formatTimestamp(d.getCreated()));
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.storage.groups.GroupInfo;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.util.Util;
+import org.asamk.signal.manager.api.Group;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
public class ListGroupsCommand implements JsonRpcLocalCommand {
.help("List the members and group invite links of each group. If output=json, then this is always set");
}
- private static Set<String> resolveMembers(Manager m, Set<RecipientId> addresses) {
- return addresses.stream()
- .map(m::resolveSignalServiceAddress)
- .map(Util::getLegacyIdentifier)
- .collect(Collectors.toSet());
+ private static Set<String> resolveMembers(Set<RecipientAddress> addresses) {
+ return addresses.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toSet());
}
- private static Set<JsonGroupMember> resolveJsonMembers(Manager m, Set<RecipientId> addresses) {
+ private static Set<JsonGroupMember> resolveJsonMembers(Set<RecipientAddress> addresses) {
return addresses.stream()
- .map(m::resolveSignalServiceAddress)
- .map(address -> new JsonGroupMember(address.getNumber().orNull(), address.getUuid().toString()))
+ .map(address -> new JsonGroupMember(address.getNumber().orElse(null),
+ address.getUuid().map(UUID::toString).orElse(null)))
.collect(Collectors.toSet());
}
private static void printGroupPlainText(
- PlainTextWriter writer, Manager m, GroupInfo group, boolean detailed
+ PlainTextWriter writer, Group group, boolean detailed
) {
if (detailed) {
- final var groupInviteLink = group.getGroupInviteLink();
+ final var groupInviteLink = group.getGroupInviteLinkUrl();
writer.println(
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Admins: {} Message expiration: {} Link: {}",
group.getGroupId().toBase64(),
group.getTitle(),
group.getDescription(),
- group.isMember(m.getSelfRecipientId()),
+ group.isMember(),
group.isBlocked(),
- resolveMembers(m, group.getMembers()),
- resolveMembers(m, group.getPendingMembers()),
- resolveMembers(m, group.getRequestingMembers()),
- resolveMembers(m, group.getAdminMembers()),
+ resolveMembers(group.getMembers()),
+ resolveMembers(group.getPendingMembers()),
+ resolveMembers(group.getRequestingMembers()),
+ resolveMembers(group.getAdminMembers()),
group.getMessageExpirationTime() == 0 ? "disabled" : group.getMessageExpirationTime() + "s",
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
} else {
writer.println("Id: {} Name: {} Active: {} Blocked: {}",
group.getGroupId().toBase64(),
group.getTitle(),
- group.isMember(m.getSelfRecipientId()),
+ group.isMember(),
group.isBlocked());
}
}
final var jsonWriter = (JsonWriter) outputWriter;
var jsonGroups = groups.stream().map(group -> {
- final var groupInviteLink = group.getGroupInviteLink();
+ final var groupInviteLink = group.getGroupInviteLinkUrl();
return new JsonGroup(group.getGroupId().toBase64(),
group.getTitle(),
group.getDescription(),
- group.isMember(m.getSelfRecipientId()),
+ group.isMember(),
group.isBlocked(),
group.getMessageExpirationTime(),
- resolveJsonMembers(m, group.getMembers()),
- resolveJsonMembers(m, group.getPendingMembers()),
- resolveJsonMembers(m, group.getRequestingMembers()),
- resolveJsonMembers(m, group.getAdminMembers()),
+ resolveJsonMembers(group.getMembers()),
+ resolveJsonMembers(group.getPendingMembers()),
+ resolveJsonMembers(group.getRequestingMembers()),
+ resolveJsonMembers(group.getAdminMembers()),
groupInviteLink == null ? null : groupInviteLink.getUrl());
}).collect(Collectors.toList());
final var writer = (PlainTextWriter) outputWriter;
boolean detailed = ns.getBoolean("detailed");
for (var group : groups) {
- printGroupPlainText(writer, m, group, detailed);
+ printGroupPlainText(writer, group, detailed);
}
}
}
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.storage.identities.IdentityInfo;
+import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.Hex;
import org.asamk.signal.util.Util;
return "listIdentities";
}
- private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) {
- final SignalServiceAddress address = m.resolveSignalServiceAddress(theirId.getRecipientId());
- var digits = Util.formatSafetyNumber(m.computeSafetyNumber(address, theirId.getIdentityKey()));
+ private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, Identity theirId) {
+ final SignalServiceAddress address = theirId.getRecipient().toSignalServiceAddress();
+ var digits = Util.formatSafetyNumber(theirId.getSafetyNumber());
writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
address.getNumber().orNull(),
theirId.getTrustLevel(),
) throws CommandException {
var number = ns.getString("number");
- List<IdentityInfo> identities;
+ List<Identity> identities;
if (number == null) {
identities = m.getIdentities();
} else {
- identities = m.getIdentities(CommandUtil.getSingleRecipientIdentifier(number, m.getUsername()));
+ identities = m.getIdentities(CommandUtil.getSingleRecipientIdentifier(number, m.getSelfNumber()));
}
if (outputWriter instanceof PlainTextWriter) {
} else {
final var writer = (JsonWriter) outputWriter;
final var jsonIdentities = identities.stream().map(id -> {
- final var address = m.resolveSignalServiceAddress(id.getRecipientId());
- var safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(address, id.getIdentityKey()));
- var scannableSafetyNumber = m.computeSafetyNumberForScanning(address, id.getIdentityKey());
+ final var address = id.getRecipient().toSignalServiceAddress();
+ var safetyNumber = Util.formatSafetyNumber(id.getSafetyNumber());
+ var scannableSafetyNumber = id.getScannableSafetyNumber();
return new JsonIdentity(address.getNumber().orNull(),
address.getUuid().toString(),
Hex.toString(id.getFingerprint()),
) throws CommandException {
final var groupId = CommandUtil.getGroupId(ns.getString("group-id"));
- var groupAdmins = CommandUtil.getSingleRecipientIdentifiers(ns.getList("admin"), m.getUsername());
+ var groupAdmins = CommandUtil.getSingleRecipientIdentifiers(ns.getList("admin"), m.getSelfNumber());
try {
try {
try {
final var results = m.sendMessageReaction(emoji,
isRemove,
- CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
+ CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetTimestamp,
recipientIdentifiers);
outputResult(outputWriter, results.getTimestamp());
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
final var recipientString = ns.getString("recipient");
- final var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getUsername());
+ final var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber());
final var targetTimestamps = ns.<Long>getList("target-timestamp");
final var type = ns.getString("type");
final var recipientIdentifiers = new HashSet<RecipientIdentifier>();
if (recipientStrings != null) {
- final var localNumber = m.getUsername();
+ final var localNumber = m.getSelfNumber();
recipientIdentifiers.addAll(CommandUtil.getSingleRecipientIdentifiers(recipientStrings, localNumber));
}
if (groupIdStrings != null) {
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
var recipentString = ns.getString("recipient");
- var recipient = CommandUtil.getSingleRecipientIdentifier(recipentString, m.getUsername());
+ var recipient = CommandUtil.getSingleRecipientIdentifier(recipentString, m.getSelfNumber());
if (ns.getBoolean("trust-all-known-keys")) {
boolean res = m.trustIdentityAllKeys(recipient);
if (!res) {
public void handleCommand(
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
- for (var contactNumber : CommandUtil.getSingleRecipientIdentifiers(ns.getList("recipient"), m.getUsername())) {
+ for (var contactNumber : CommandUtil.getSingleRecipientIdentifiers(ns.getList("recipient"),
+ m.getSelfNumber())) {
try {
m.setContactBlocked(contactNumber, false);
} catch (NotMasterDeviceException e) {
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
var recipientString = ns.getString("recipient");
- var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getUsername());
+ var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber());
try {
var expiration = ns.getInt("expiration");
final var groupIdString = ns.getString("group-id");
var groupId = CommandUtil.getGroupId(groupIdString);
- final var localNumber = m.getUsername();
+ final var localNumber = m.getSelfNumber();
var groupName = ns.getString("name");
var groupDescription = ns.getString("description");
synchronized (receiveThreads) {
return receiveThreads.stream()
.map(Pair::first)
- .map(Manager::getUsername)
+ .map(Manager::getSelfNumber)
.map(u -> new DBusPath(DbusConfig.getObjectPath(u)))
.collect(Collectors.toList());
}
import org.asamk.signal.manager.StickerPackInvalidException;
import org.asamk.signal.manager.UntrustedIdentityException;
import org.asamk.signal.manager.api.Device;
+import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.manager.storage.identities.IdentityInfo;
+import org.asamk.signal.manager.storage.recipients.Profile;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.util.ErrorUtils;
-import org.asamk.signal.util.Util;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static org.asamk.signal.util.Util.getLegacyIdentifier;
-
public class DbusSignalImpl implements Signal {
private final Manager m;
return objectPath;
}
+ @Override
+ public String getNumber() {
+ return m.getSelfNumber();
+ }
+
@Override
public void addDevice(String uri) {
try {
public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
try {
final var results = m.sendMessage(new Message(message, attachments),
- getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
) {
try {
final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
- getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
checkSendMessageResults(results.getTimestamp(), results.getResults());
try {
final var results = m.sendMessageReaction(emoji,
remove,
- getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
+ getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetSentTimestamp,
- getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
checkSendMessageResults(results.getTimestamp(), results.getResults());
var recipients = new ArrayList<String>(1);
recipients.add(recipient);
m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
- getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
+ getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
} catch (IOException e) {
@Override
public void sendReadReceipt(
- final String recipient, final List<Long> timestamps
+ final String recipient, final List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity {
try {
- m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getUsername()), timestamps);
+ m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (UntrustedIdentityException e) {
@Override
public void sendEndSessionMessage(final List<String> recipients) {
try {
- final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getUsername()));
+ final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
checkSendMessageResults(results.getTimestamp(), results.getResults());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
try {
final var results = m.sendMessageReaction(emoji,
remove,
- getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
+ getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetSentTimestamp,
Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
checkSendMessageResults(results.getTimestamp(), results.getResults());
// the profile name
@Override
public String getContactName(final String number) {
- return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getUsername()));
+ return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber()));
}
@Override
public void setContactName(final String number, final String name) {
try {
- m.setContactName(getSingleRecipientIdentifier(number, m.getUsername()), name);
+ m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name);
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (UnregisteredUserException e) {
@Override
public void setExpirationTimer(final String number, final int expiration) {
try {
- m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getUsername()), expiration);
+ m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getSelfNumber()), expiration);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
@Override
public void setContactBlocked(final String number, final boolean blocked) {
try {
- m.setContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()), blocked);
+ m.setContactBlocked(getSingleRecipientIdentifier(number, m.getSelfNumber()), blocked);
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
} catch (IOException e) {
if (group == null) {
return List.of();
} else {
- return group.getMembers()
- .stream()
- .map(m::resolveSignalServiceAddress)
- .map(Util::getLegacyIdentifier)
- .collect(Collectors.toList());
+ return group.getMembers().stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList());
}
}
if (avatar.isEmpty()) {
avatar = null;
}
- final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getUsername());
+ final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber());
if (groupId == null) {
final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar));
checkSendMessageResults(results.second().getTimestamp(), results.second().getResults());
// all numbers the system knows
@Override
public List<String> listNumbers() {
- return Stream.concat(m.getIdentities().stream().map(IdentityInfo::getRecipientId),
+ return Stream.concat(m.getIdentities().stream().map(Identity::getRecipient),
m.getContacts().stream().map(Pair::first))
- .map(m::resolveSignalServiceAddress)
- .map(a -> a.getNumber().orNull())
+ .map(a -> a.getNumber().orElse(null))
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
var contacts = m.getContacts();
for (var c : contacts) {
if (name.equals(c.second().getName())) {
- numbers.add(getLegacyIdentifier(m.resolveSignalServiceAddress(c.first())));
+ numbers.add(c.first().getLegacyIdentifier());
}
}
// Try profiles if no contact name was found
for (var identity : m.getIdentities()) {
- final var recipientId = identity.getRecipientId();
- final var address = m.resolveSignalServiceAddress(recipientId);
- var number = address.getNumber().orNull();
+ final var address = identity.getRecipient();
+ var number = address.getNumber().orElse(null);
if (number != null) {
- var profile = m.getRecipientProfile(recipientId);
+ Profile profile = null;
+ try {
+ profile = m.getRecipientProfile(RecipientIdentifier.Single.fromAddress(address));
+ } catch (UnregisteredUserException ignored) {
+ }
if (profile != null && profile.getDisplayName().equals(name)) {
numbers.add(number);
}
@Override
public boolean isContactBlocked(final String number) {
- return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()));
+ return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getSelfNumber()));
}
@Override
if (group == null) {
return false;
} else {
- return group.isMember(m.getSelfRecipientId());
+ return group.isMember();
}
}
import org.asamk.signal.manager.Manager;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
final int length;
JsonMention(SignalServiceDataMessage.Mention mention, Manager m) {
- final var address = m.resolveSignalServiceAddress(mention.getUuid());
+ final var address = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid()));
this.name = getLegacyIdentifier(address);
this.number = address.getNumber().orNull();
this.uuid = address.getUuid().toString();
}
String name;
try {
- name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(this.source, m.getUsername()));
+ name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(this.source, m.getSelfNumber()));
} catch (InvalidNumberException | NullPointerException e) {
name = null;
}
recipientIdentifiers.add(RecipientIdentifier.NoteToSelf.INSTANCE);
}
if (recipientStrings != null) {
- final var localNumber = m.getUsername();
+ final var localNumber = m.getSelfNumber();
recipientIdentifiers.addAll(CommandUtil.getSingleRecipientIdentifiers(recipientStrings, localNumber));
}
if (groupIdStrings != null) {