Extend listContacts command with profiles and filtering

This commit is contained in:
AsamK 2022-05-20 11:46:03 +02:00
parent 496cd5e621
commit 5f941004f5
11 changed files with 196 additions and 83 deletions

View file

@ -527,6 +527,20 @@
"allDeclaredMethods":true, "allDeclaredMethods":true,
"allDeclaredConstructors":true "allDeclaredConstructors":true
}, },
{
"name":"org.asamk.signal.commands.ListContactsCommand$JsonContact$JsonProfile",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[
{"name":"about","parameterTypes":[] },
{"name":"aboutEmoji","parameterTypes":[] },
{"name":"familyName","parameterTypes":[] },
{"name":"givenName","parameterTypes":[] },
{"name":"lastUpdateTimestamp","parameterTypes":[] },
{"name":"paymentAddress","parameterTypes":[] }
]
},
{ {
"name":"org.asamk.signal.commands.ListDevicesCommand$JsonDevice", "name":"org.asamk.signal.commands.ListDevicesCommand$JsonDevice",
"allDeclaredFields":true, "allDeclaredFields":true,
@ -1797,6 +1811,17 @@
{"name":"userId_"} {"name":"userId_"}
] ]
}, },
{
"name":"org.signal.storageservice.protos.groups.GroupChanges",
"fields":[{"name":"groupChanges_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChanges$GroupChangeState",
"fields":[
{"name":"groupChange_"},
{"name":"groupState_"}
]
},
{ {
"name":"org.signal.storageservice.protos.groups.GroupInviteLink", "name":"org.signal.storageservice.protos.groups.GroupInviteLink",
"fields":[ "fields":[

View file

@ -73,6 +73,9 @@
{ {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E" "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E"
}, },
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK\\E"
},
{ {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EC\\E" "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EC\\E"
}, },

View file

@ -28,9 +28,8 @@ import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.Recipient;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.Closeable; import java.io.Closeable;
@ -215,7 +214,12 @@ public interface Manager extends Closeable {
void sendContacts() throws IOException; void sendContacts() throws IOException;
List<Pair<RecipientAddress, Contact>> getContacts(); List<Recipient> getRecipients(
boolean onlyContacts,
Optional<Boolean> blocked,
Collection<RecipientIdentifier.Single> address,
Optional<String> name
);
String getContactOrProfileName(RecipientIdentifier.Single recipient); String getContactOrProfileName(RecipientIdentifier.Single recipient);

View file

@ -51,9 +51,8 @@ import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.identities.IdentityInfo; import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.Recipient;
import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack; import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack;
import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore;
@ -84,6 +83,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -101,7 +101,6 @@ class ManagerImpl implements Manager {
private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class); private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class);
private SignalAccount account; private SignalAccount account;
private AccountFileUpdater accountFileUpdater;
private final SignalDependencies dependencies; private final SignalDependencies dependencies;
private final Context context; private final Context context;
@ -123,7 +122,6 @@ class ManagerImpl implements Manager {
String userAgent String userAgent
) { ) {
this.account = account; this.account = account;
this.accountFileUpdater = accountFileUpdater;
final var sessionLock = new SignalSessionLock() { final var sessionLock = new SignalSessionLock() {
private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
@ -337,7 +335,7 @@ class ManagerImpl implements Manager {
} }
@Override @Override
public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException { public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredRecipientException {
return context.getProfileHelper().getRecipientProfile(context.getRecipientHelper().resolveRecipient(recipient)); return context.getProfileHelper().getRecipientProfile(context.getRecipientHelper().resolveRecipient(recipient));
} }
@ -495,7 +493,7 @@ class ManagerImpl implements Manager {
@Override @Override
public SendMessageResults sendReadReceipt( public SendMessageResults sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException { ) {
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
messageIds, messageIds,
@ -507,7 +505,7 @@ class ManagerImpl implements Manager {
@Override @Override
public SendMessageResults sendViewedReceipt( public SendMessageResults sendViewedReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException { ) {
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED, var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
messageIds, messageIds,
@ -520,7 +518,7 @@ class ManagerImpl implements Manager {
final RecipientIdentifier.Single sender, final RecipientIdentifier.Single sender,
final long timestamp, final long timestamp,
final SignalServiceReceiptMessage receiptMessage final SignalServiceReceiptMessage receiptMessage
) throws IOException { ) {
try { try {
final var result = context.getSendHelper() final var result = context.getSendHelper()
.sendReceiptMessage(receiptMessage, context.getRecipientHelper().resolveRecipient(sender)); .sendReceiptMessage(receiptMessage, context.getRecipientHelper().resolveRecipient(sender));
@ -592,7 +590,7 @@ class ManagerImpl implements Manager {
} }
} }
private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws IOException, UnregisteredRecipientException { private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException {
final var mentions = new ArrayList<SignalServiceDataMessage.Mention>(); final var mentions = new ArrayList<SignalServiceDataMessage.Mention>();
for (final var m : mentionList) { for (final var m : mentionList) {
final var recipientId = context.getRecipientHelper().resolveRecipient(m.recipient()); final var recipientId = context.getRecipientHelper().resolveRecipient(m.recipient());
@ -676,7 +674,7 @@ class ManagerImpl implements Manager {
@Override @Override
public void setContactName( public void setContactName(
RecipientIdentifier.Single recipient, String name RecipientIdentifier.Single recipient, String name
) throws NotMasterDeviceException, IOException, UnregisteredRecipientException { ) throws NotMasterDeviceException, UnregisteredRecipientException {
if (!account.isMasterDevice()) { if (!account.isMasterDevice()) {
throw new NotMasterDeviceException(); throw new NotMasterDeviceException();
} }
@ -932,7 +930,7 @@ class ManagerImpl implements Manager {
final RecipientId recipientId; final RecipientId recipientId;
try { try {
recipientId = context.getRecipientHelper().resolveRecipient(recipient); recipientId = context.getRecipientHelper().resolveRecipient(recipient);
} catch (IOException | UnregisteredRecipientException e) { } catch (UnregisteredRecipientException e) {
return false; return false;
} }
return context.getContactHelper().isContactBlocked(recipientId); return context.getContactHelper().isContactBlocked(recipientId);
@ -944,12 +942,22 @@ class ManagerImpl implements Manager {
} }
@Override @Override
public List<Pair<RecipientAddress, Contact>> getContacts() { public List<Recipient> getRecipients(
return account.getContactStore() boolean onlyContacts,
.getContacts() Optional<Boolean> blocked,
.stream() Collection<RecipientIdentifier.Single> recipients,
.map(p -> new Pair<>(account.getRecipientStore().resolveRecipientAddress(p.first()), p.second())) Optional<String> name
.toList(); ) {
final var recipientIds = recipients.stream().map(a -> {
try {
return context.getRecipientHelper().resolveRecipient(a);
} catch (UnregisteredRecipientException e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toSet());
// refresh profiles of explicitly given recipients
context.getProfileHelper().refreshRecipientProfiles(recipientIds);
return account.getRecipientStore().getRecipients(onlyContacts, blocked, recipientIds, name);
} }
@Override @Override
@ -957,7 +965,7 @@ class ManagerImpl implements Manager {
final RecipientId recipientId; final RecipientId recipientId;
try { try {
recipientId = context.getRecipientHelper().resolveRecipient(recipient); recipientId = context.getRecipientHelper().resolveRecipient(recipient);
} catch (IOException | UnregisteredRecipientException e) { } catch (UnregisteredRecipientException e) {
return null; return null;
} }
@ -1007,7 +1015,7 @@ class ManagerImpl implements Manager {
try { try {
identity = account.getIdentityKeyStore() identity = account.getIdentityKeyStore()
.getIdentity(context.getRecipientHelper().resolveRecipient(recipient)); .getIdentity(context.getRecipientHelper().resolveRecipient(recipient));
} catch (IOException | UnregisteredRecipientException e) { } catch (UnregisteredRecipientException e) {
identity = null; identity = null;
} }
return identity == null ? List.of() : List.of(toIdentity(identity)); return identity == null ? List.of() : List.of(toIdentity(identity));
@ -1044,12 +1052,7 @@ class ManagerImpl implements Manager {
private boolean trustIdentity( private boolean trustIdentity(
RecipientIdentifier.Single recipient, Function<RecipientId, Boolean> trustMethod RecipientIdentifier.Single recipient, Function<RecipientId, Boolean> trustMethod
) throws UnregisteredRecipientException { ) throws UnregisteredRecipientException {
RecipientId recipientId; final var recipientId = context.getRecipientHelper().resolveRecipient(recipient);
try {
recipientId = context.getRecipientHelper().resolveRecipient(recipient);
} catch (IOException e) {
return false;
}
final var updated = trustMethod.apply(recipientId); final var updated = trustMethod.apply(recipientId);
if (updated && this.isReceiving()) { if (updated && this.isReceiving()) {
context.getReceiveHelper().setNeedsToRetryFailedMessages(true); context.getReceiveHelper().setNeedsToRetryFailedMessages(true);

View file

@ -36,6 +36,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Base64; import java.util.Base64;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -94,10 +95,18 @@ public final class ProfileHelper {
return getRecipientProfile(recipientId, false); return getRecipientProfile(recipientId, false);
} }
public List<Profile> getRecipientProfiles(Collection<RecipientId> recipientIds) {
return getRecipientProfiles(recipientIds, false);
}
public void refreshRecipientProfile(RecipientId recipientId) { public void refreshRecipientProfile(RecipientId recipientId) {
getRecipientProfile(recipientId, true); getRecipientProfile(recipientId, true);
} }
public void refreshRecipientProfiles(Collection<RecipientId> recipientIds) {
getRecipientProfiles(recipientIds, true);
}
public List<ProfileKeyCredential> getRecipientProfileKeyCredential(List<RecipientId> recipientIds) { public List<ProfileKeyCredential> getRecipientProfileKeyCredential(List<RecipientId> recipientIds) {
try { try {
account.getRecipientStore().setBulkUpdating(true); account.getRecipientStore().setBulkUpdating(true);
@ -216,11 +225,12 @@ public final class ProfileHelper {
return getRecipientProfile(account.getSelfRecipientId()); return getRecipientProfile(account.getSelfRecipientId());
} }
public List<Profile> getRecipientProfile(List<RecipientId> recipientIds) { private List<Profile> getRecipientProfiles(Collection<RecipientId> recipientIds, boolean force) {
final var profileStore = account.getProfileStore();
try { try {
account.getRecipientStore().setBulkUpdating(true); account.getRecipientStore().setBulkUpdating(true);
final var profileFetches = Flowable.fromIterable(recipientIds) final var profileFetches = Flowable.fromIterable(recipientIds)
.filter(recipientId -> isProfileRefreshRequired(account.getProfileStore().getProfile(recipientId))) .filter(recipientId -> force || isProfileRefreshRequired(profileStore.getProfile(recipientId)))
.map(recipientId -> retrieveProfile(recipientId, .map(recipientId -> retrieveProfile(recipientId,
SignalServiceProfile.RequestType.PROFILE).onErrorComplete()); SignalServiceProfile.RequestType.PROFILE).onErrorComplete());
Maybe.merge(profileFetches, 10).blockingSubscribe(); Maybe.merge(profileFetches, 10).blockingSubscribe();
@ -228,7 +238,7 @@ public final class ProfileHelper {
account.getRecipientStore().setBulkUpdating(false); account.getRecipientStore().setBulkUpdating(false);
} }
return recipientIds.stream().map(r -> account.getProfileStore().getProfile(r)).toList(); return recipientIds.stream().map(profileStore::getProfile).toList();
} }
private Profile getRecipientProfile(RecipientId recipientId, boolean force) { private Profile getRecipientProfile(RecipientId recipientId, boolean force) {

View file

@ -69,7 +69,7 @@ public class RecipientHelper {
return account.getRecipientStore().resolveRecipient(address); return account.getRecipientStore().resolveRecipient(address);
} }
public Set<RecipientId> resolveRecipients(Collection<RecipientIdentifier.Single> recipients) throws IOException, UnregisteredRecipientException { public Set<RecipientId> resolveRecipients(Collection<RecipientIdentifier.Single> recipients) throws UnregisteredRecipientException {
final var recipientIds = new HashSet<RecipientId>(recipients.size()); final var recipientIds = new HashSet<RecipientId>(recipients.size());
for (var number : recipients) { for (var number : recipients) {
final var recipientId = resolveRecipient(number); final var recipientId = resolveRecipient(number);
@ -78,7 +78,7 @@ public class RecipientHelper {
return recipientIds; return recipientIds;
} }
public RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException { public RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws UnregisteredRecipientException {
if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) {
return account.getRecipientStore().resolveRecipient(ServiceId.from(uuidRecipient.uuid())); return account.getRecipientStore().resolveRecipient(ServiceId.from(uuidRecipient.uuid()));
} else { } else {

View file

@ -475,7 +475,7 @@ public class SendHelper {
final var senderKeyTargets = new HashSet<RecipientId>(); final var senderKeyTargets = new HashSet<RecipientId>();
final var recipientList = new ArrayList<>(recipientIds); final var recipientList = new ArrayList<>(recipientIds);
final var profiles = context.getProfileHelper().getRecipientProfile(recipientList).iterator(); final var profiles = context.getProfileHelper().getRecipientProfiles(recipientList).iterator();
for (final var recipientId : recipientList) { for (final var recipientId : recipientList) {
final var profile = profiles.next(); final var profile = profiles.next();
if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) { if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) {

View file

@ -277,6 +277,24 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
.toList(); .toList();
} }
public List<Recipient> getRecipients(
boolean onlyContacts, Optional<Boolean> blocked, Set<RecipientId> recipientIds, Optional<String> name
) {
return recipients.values()
.stream()
.filter(r -> !onlyContacts || r.getContact() != null)
.filter(r -> blocked.isEmpty() || (
blocked.get() == (
r.getContact() != null && r.getContact().isBlocked()
)
))
.filter(r -> recipientIds.isEmpty() || (recipientIds.contains(r.getRecipientId())))
.filter(r -> name.isEmpty()
|| (r.getContact() != null && name.get().equals(r.getContact().getName()))
|| (r.getProfile() != null && name.get().equals(r.getProfile().getDisplayName())))
.toList();
}
@Override @Override
public void deleteContact(RecipientId recipientId) { public void deleteContact(RecipientId recipientId) {
synchronized (recipients) { synchronized (recipients) {

View file

@ -1,13 +1,20 @@
package org.asamk.signal.commands; package org.asamk.signal.commands;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.output.JsonWriter; import org.asamk.signal.output.JsonWriter;
import org.asamk.signal.output.OutputWriter; import org.asamk.signal.output.OutputWriter;
import org.asamk.signal.output.PlainTextWriter; import org.asamk.signal.output.PlainTextWriter;
import org.asamk.signal.util.CommandUtil;
import java.util.Base64;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class ListContactsCommand implements JsonRpcLocalCommand { public class ListContactsCommand implements JsonRpcLocalCommand {
@ -19,19 +26,39 @@ public class ListContactsCommand implements JsonRpcLocalCommand {
@Override @Override
public void attachToSubparser(final Subparser subparser) { public void attachToSubparser(final Subparser subparser) {
subparser.help("Show a list of known contacts with names."); subparser.help("Show a list of known contacts with names and profiles.");
subparser.addArgument("recipient").help("Specify one ore more phone numbers to show.").nargs("*");
subparser.addArgument("-a", "--all-recipients")
.action(Arguments.storeTrue())
.help("Include all known recipients, not only contacts.");
subparser.addArgument("--blocked")
.type(Boolean.class)
.help("Specify if only blocked or unblocked contacts should be shown (default: all contacts)");
subparser.addArgument("--name").help("Find contacts with the given contact or profile name.");
} }
@Override @Override
public void handleCommand(final Namespace ns, final Manager m, final OutputWriter outputWriter) { public void handleCommand(
var contacts = m.getContacts(); final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
final var allRecipients = Boolean.TRUE.equals(ns.getBoolean("all-recipients"));
final var blocked = ns.getBoolean("blocked");
final var recipientStrings = ns.<String>getList("recipient");
final var recipientIdentifiers = CommandUtil.getSingleRecipientIdentifiers(recipientStrings, m.getSelfNumber());
final var name = ns.getString("name");
final var recipients = m.getRecipients(!allRecipients,
Optional.ofNullable(blocked),
recipientIdentifiers,
Optional.ofNullable(name));
if (outputWriter instanceof PlainTextWriter writer) { if (outputWriter instanceof PlainTextWriter writer) {
for (var c : contacts) { for (var r : recipients) {
final var contact = c.second(); final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact();
writer.println("Number: {} Name: {} Blocked: {} Message expiration: {}", final var profile = r.getProfile() == null ? Profile.newBuilder().build() : r.getProfile();
c.first().getLegacyIdentifier(), writer.println("Number: {} Name: {} Profile name: {} Blocked: {} Message expiration: {}",
r.getAddress().getLegacyIdentifier(),
contact.getName(), contact.getName(),
profile.getDisplayName(),
contact.isBlocked(), contact.isBlocked(),
contact.getMessageExpirationTime() == 0 contact.getMessageExpirationTime() == 0
? "disabled" ? "disabled"
@ -39,19 +66,47 @@ public class ListContactsCommand implements JsonRpcLocalCommand {
} }
} else { } else {
final var writer = (JsonWriter) outputWriter; final var writer = (JsonWriter) outputWriter;
final var jsonContacts = contacts.stream().map(contactPair -> { final var jsonContacts = recipients.stream().map(r -> {
final var address = contactPair.first(); final var address = r.getAddress();
final var contact = contactPair.second(); final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact();
return new JsonContact(address.number().orElse(null), return new JsonContact(address.number().orElse(null),
address.uuid().map(UUID::toString).orElse(null), address.uuid().map(UUID::toString).orElse(null),
contact.getName(), contact.getName(),
contact.isBlocked(), contact.isBlocked(),
contact.getMessageExpirationTime()); contact.getMessageExpirationTime(),
r.getProfile() == null
? null
: new JsonContact.JsonProfile(r.getProfile().getLastUpdateTimestamp(),
r.getProfile().getGivenName(),
r.getProfile().getFamilyName(),
r.getProfile().getAbout(),
r.getProfile().getAboutEmoji(),
r.getProfile().getPaymentAddress() == null
? null
: Base64.getEncoder()
.encodeToString(r.getProfile().getPaymentAddress())));
}).toList(); }).toList();
writer.write(jsonContacts); writer.write(jsonContacts);
} }
} }
private record JsonContact(String number, String uuid, String name, boolean isBlocked, int messageExpirationTime) {} private record JsonContact(
String number,
String uuid,
String name,
boolean isBlocked,
int messageExpirationTime,
JsonProfile profile
) {
private record JsonProfile(
long lastUpdateTimestamp,
String givenName,
String familyName,
String about,
String aboutEmoji,
String paymentAddress
) {}
}
} }

View file

@ -32,6 +32,7 @@ import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Contact; import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.Recipient;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.freedesktop.dbus.DBusMap; import org.freedesktop.dbus.DBusMap;
import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.DBusPath;
@ -547,14 +548,32 @@ public class DbusManagerImpl implements Manager {
} }
@Override @Override
public List<Pair<RecipientAddress, Contact>> getContacts() { public List<Recipient> getRecipients(
return signal.listNumbers().stream().map(n -> { final boolean onlyContacts,
final var contactName = signal.getContactName(n); final Optional<Boolean> blocked,
if (contactName.length() == 0) { final Collection<RecipientIdentifier.Single> addresses,
final Optional<String> name
) {
final var numbers = addresses.stream()
.filter(s -> s instanceof RecipientIdentifier.Number)
.map(s -> ((RecipientIdentifier.Number) s).number())
.collect(Collectors.toSet());
return signal.listNumbers().stream().filter(n -> addresses.isEmpty() || numbers.contains(n)).map(n -> {
final var contactBlocked = signal.isContactBlocked(n);
if (blocked.isPresent() && blocked.get() != contactBlocked) {
return null; return null;
} }
return new Pair<>(new RecipientAddress(null, n), final var contactName = signal.getContactName(n);
new Contact(contactName, null, 0, signal.isContactBlocked(n), false, false)); if (onlyContacts && contactName.length() == 0) {
return null;
}
if (name.isPresent() && !name.get().equals(contactName)) {
return null;
}
return Recipient.newBuilder()
.withAddress(new RecipientAddress(null, n))
.withContact(new Contact(contactName, null, 0, contactBlocked, false, false))
.build();
}).filter(Objects::nonNull).toList(); }).filter(Objects::nonNull).toList();
} }

View file

@ -4,14 +4,12 @@ import org.asamk.Signal;
import org.asamk.signal.BaseConfig; import org.asamk.signal.BaseConfig;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.AttachmentInvalidException; import org.asamk.signal.manager.api.AttachmentInvalidException;
import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.InactiveGroupLinkException; import org.asamk.signal.manager.api.InactiveGroupLinkException;
import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.InvalidNumberException; import org.asamk.signal.manager.api.InvalidNumberException;
import org.asamk.signal.manager.api.InvalidStickerException; import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.NotMasterDeviceException; import org.asamk.signal.manager.api.NotMasterDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendMessageResult; import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.SendMessageResults;
@ -28,7 +26,6 @@ import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.util.SendMessageResultUtils; import org.asamk.signal.util.SendMessageResultUtils;
import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.DBusPath;
@ -55,7 +52,6 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DbusSignalImpl implements Signal { public class DbusSignalImpl implements Signal {
@ -719,9 +715,9 @@ public class DbusSignalImpl implements Signal {
// all numbers the system knows // all numbers the system knows
@Override @Override
public List<String> listNumbers() { public List<String> listNumbers() {
return Stream.concat(m.getIdentities().stream().map(Identity::recipient), return m.getRecipients(false, Optional.empty(), Set.of(), Optional.empty())
m.getContacts().stream().map(Pair::first)) .stream()
.map(a -> a.number().orElse(null)) .map(r -> r.getAddress().number().orElse(null))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.toList(); .toList();
@ -729,30 +725,10 @@ public class DbusSignalImpl implements Signal {
@Override @Override
public List<String> getContactNumber(final String name) { public List<String> getContactNumber(final String name) {
// Contact names have precedence. return m.getRecipients(false, Optional.empty(), Set.of(), Optional.of(name))
var numbers = new ArrayList<String>(); .stream()
var contacts = m.getContacts(); .map(r -> r.getAddress().getLegacyIdentifier())
for (var c : contacts) { .toList();
if (name.equals(c.second().getName())) {
numbers.add(c.first().getLegacyIdentifier());
}
}
// Try profiles if no contact name was found
for (var identity : m.getIdentities()) {
final var address = identity.recipient();
var number = address.number().orElse(null);
if (number != null) {
Profile profile = null;
try {
profile = m.getRecipientProfile(RecipientIdentifier.Single.fromAddress(address));
} catch (IOException | UnregisteredRecipientException ignored) {
}
if (profile != null && profile.getDisplayName().equals(name)) {
numbers.add(number);
}
}
}
return numbers;
} }
@Override @Override