Skip to content

Commit

Permalink
Add more details to listContacts command
Browse files Browse the repository at this point in the history
Fixes #1502
  • Loading branch information
AsamK committed Apr 17, 2024
1 parent 8aeaf92 commit e4c5144
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 25 deletions.
18 changes: 18 additions & 0 deletions graalvm-config-dir/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,24 @@
"queryAllDeclaredConstructors":true,
"methods":[{"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }, {"name":"type","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.json.JsonContact",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"color","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"internal","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isHidden","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"nickFamilyName","parameterTypes":[] }, {"name":"nickGivenName","parameterTypes":[] }, {"name":"nickName","parameterTypes":[] }, {"name":"note","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileSharing","parameterTypes":[] }, {"name":"unregistered","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.json.JsonContact$JsonInternal",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"capabilities","parameterTypes":[] }, {"name":"discoverableByPhonenumber","parameterTypes":[] }, {"name":"sharesPhoneNumber","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.json.JsonContact$JsonProfile",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"hasAvatar","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.json.JsonContactAddress",
"allDeclaredFields":true,
Expand Down
6 changes: 6 additions & 0 deletions man/signal-cli.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,12 @@ Specify if only blocked or unblocked contacts should be shown (default: all cont
*--name*::
Find contacts with the given contact or profile name.

*--detailed*::
List the contacts with more details. If output=json, then this is always set

*--internal*::
Include internal information that's normally not user visible

=== listIdentities

List all known identity keys and their trust status, fingerprint and safety number.
Expand Down
100 changes: 75 additions & 25 deletions src/main/java/org/asamk/signal/commands/ListContactsCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import net.sourceforge.argparse4j.inf.Subparser;

import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.json.JsonContact;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.PhoneNumberSharingMode;
import org.asamk.signal.manager.api.Profile;
import org.asamk.signal.output.JsonWriter;
import org.asamk.signal.output.OutputWriter;
Expand Down Expand Up @@ -35,6 +37,12 @@ public void attachToSubparser(final Subparser subparser) {
.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.");
subparser.addArgument("--detailed")
.action(Arguments.storeTrue())
.help("List the contacts with more details. If output=json, then this is always set");
subparser.addArgument("--internal")
.action(Arguments.storeTrue())
.help("Include internal information that's normally not user visible");
}

@Override
Expand All @@ -51,68 +59,110 @@ public void handleCommand(
recipientIdentifiers,
Optional.ofNullable(name));

final var detailed = Boolean.TRUE.equals(ns.getBoolean("detailed"));
final var internal = Boolean.TRUE.equals(ns.getBoolean("internal"));

switch (outputWriter) {
case PlainTextWriter writer -> {
for (var r : recipients) {
final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact();
final var profile = r.getProfile() == null ? Profile.newBuilder().build() : r.getProfile();
writer.println(
"Number: {} Name: {} Profile name: {} Username: {} Color: {} Blocked: {} Message expiration: {}",
r.getAddress().getLegacyIdentifier(),
"Number: {} ACI: {} Name: {} Profile name: {} Username: {} Color: {} Blocked: {} Message expiration: {}",
r.getAddress().number().orElse(""),
r.getAddress().aci().orElse(""),
contact.getName(),
profile.getDisplayName(),
r.getAddress().username().orElse(""),
contact.color(),
Optional.ofNullable(contact.color()).orElse(""),
contact.isBlocked(),
contact.messageExpirationTime() == 0 ? "disabled" : contact.messageExpirationTime() + "s");
if (detailed) {
writer.indentedWriter()
.println(
"PNI: {} Given name: {} Family name: {}, Nick name: {} Nick given name: {} Nick family name {} Note: {} Archived: {} Hidden: {} Profile sharing: {} About: {} About Emoji: {} Unregistered: {}",
r.getAddress().pni().orElse(""),
Optional.ofNullable(r.getContact().givenName()).orElse(""),
Optional.ofNullable(r.getContact().familyName()).orElse(""),
Optional.ofNullable(r.getContact().nickName()).orElse(""),
Optional.ofNullable(r.getContact().nickNameGivenName()).orElse(""),
Optional.ofNullable(r.getContact().nickNameFamilyName()).orElse(""),
Optional.ofNullable(r.getContact().note()).orElse(""),
r.getContact().isArchived(),
r.getContact().isHidden(),
r.getContact().isProfileSharingEnabled(),
Optional.ofNullable(r.getProfile().getAbout()).orElse(""),
Optional.ofNullable(r.getProfile().getAboutEmoji()).orElse(""),
r.getContact().unregisteredTimestamp() != null);
}
if (internal) {
writer.indentedWriter()
.println(
"Capabilities: {} Unidentified access mode: {} Shares number: {} Discoverable by number: {}",
r.getProfile().getCapabilities().stream().map(Enum::name).toList(),
Optional.ofNullable(r.getProfile().getUnidentifiedAccessMode()
== Profile.UnidentifiedAccessMode.UNKNOWN
? null
: r.getProfile().getUnidentifiedAccessMode().name()).orElse(""),
r.getProfile().getPhoneNumberSharingMode() == null
? ""
: String.valueOf(r.getProfile().getPhoneNumberSharingMode()
== PhoneNumberSharingMode.EVERYBODY),
r.getDiscoverable() == null ? "" : String.valueOf(r.getDiscoverable()));
}
}
}
case JsonWriter writer -> {
final var jsonContacts = recipients.stream().map(r -> {
final var address = r.getAddress();
final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact();
final var jsonInternal = !internal
? null
: new JsonContact.JsonInternal(r.getProfile()
.getCapabilities()
.stream()
.map(Enum::name)
.toList(),
r.getProfile().getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNKNOWN
? null
: r.getProfile().getUnidentifiedAccessMode().name(),
r.getProfile().getPhoneNumberSharingMode() == null
? null
: r.getProfile().getPhoneNumberSharingMode()
== PhoneNumberSharingMode.EVERYBODY,
r.getDiscoverable());
return new JsonContact(address.number().orElse(null),
address.uuid().map(UUID::toString).orElse(null),
address.username().orElse(null),
contact.getName(),
contact.givenName(),
contact.familyName(),
contact.nickName(),
contact.nickNameGivenName(),
contact.nickNameFamilyName(),
contact.note(),
contact.color(),
contact.isBlocked(),
contact.isHidden(),
contact.messageExpirationTime(),
r.getContact().isProfileSharingEnabled(),
r.getContact().unregisteredTimestamp() != null,
r.getProfile() == null
? null
: new JsonContact.JsonProfile(r.getProfile().getLastUpdateTimestamp(),
r.getProfile().getGivenName(),
r.getProfile().getFamilyName(),
r.getProfile().getAbout(),
r.getProfile().getAboutEmoji(),
r.getProfile().getAvatarUrlPath() != null,
r.getProfile().getMobileCoinAddress() == null
? null
: Base64.getEncoder()
.encodeToString(r.getProfile().getMobileCoinAddress())));
.encodeToString(r.getProfile().getMobileCoinAddress())),
jsonInternal);
}).toList();
writer.write(jsonContacts);
}
}
}

private record JsonContact(
String number,
String uuid,
String username,
String name,
String color,
boolean isBlocked,
int messageExpirationTime,
JsonProfile profile
) {

private record JsonProfile(
long lastUpdateTimestamp,
String givenName,
String familyName,
String about,
String aboutEmoji,
String mobileCoinAddress
) {}
}
}
44 changes: 44 additions & 0 deletions src/main/java/org/asamk/signal/json/JsonContact.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.asamk.signal.json;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.List;

public record JsonContact(
String number,
String uuid,
String username,
String name,
String givenName,
String familyName,
String nickName,
String nickGivenName,
String nickFamilyName,
String note,
String color,
boolean isBlocked,
boolean isHidden,
int messageExpirationTime,
boolean profileSharing,
boolean unregistered,
JsonProfile profile,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonInternal internal
) {

public record JsonProfile(
long lastUpdateTimestamp,
String givenName,
String familyName,
String about,
String aboutEmoji,
boolean hasAvatar,
String mobileCoinAddress
) {}

public record JsonInternal(
List<String> capabilities,
String unidentifiedAccessMode,
Boolean sharesPhoneNumber,
Boolean discoverableByPhonenumber
) {}
}

0 comments on commit e4c5144

Please sign in to comment.