From: AsamK Date: Fri, 22 Mar 2024 09:54:42 +0000 (+0100) Subject: Extend getUserStatus command for usernames X-Git-Tag: v0.13.2~3 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/d356d92b5eb24f7340055f766455b943c274bc50?ds=sidebyside Extend getUserStatus command for usernames --- diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 96961e4c..e3b47e86 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -693,7 +693,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"isRegistered","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"recipient","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] + "methods":[{"name":"isRegistered","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"recipient","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] }, { "name":"org.asamk.signal.commands.ListAccountsCommand$JsonAccount", diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index d6cf01d8..736f4e08 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -44,6 +44,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,6 +93,8 @@ public interface Manager extends Closeable { */ Map getUserStatus(Set numbers) throws IOException, RateLimitException; + Map getUsernameStatus(Set usernames); + void updateAccountAttributes( String deviceName, Boolean unrestrictedUnidentifiedSender, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java b/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java new file mode 100644 index 00000000..570d5511 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java @@ -0,0 +1,5 @@ +package org.asamk.signal.manager.api; + +import java.util.UUID; + +public record UsernameStatus(String username, UUID uuid, boolean unrestrictedUnidentifiedAccess) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 905bbd5a..1581c1a5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -91,28 +91,51 @@ public class RecipientHelper { }); } else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) { var username = usernameRecipient.username(); - try { - UsernameLinkUrl usernameLinkUrl = UsernameLinkUrl.fromUri(username); - final var components = usernameLinkUrl.getComponents(); - final var encryptedUsername = dependencies.getAccountManager() - .getEncryptedUsernameFromLinkServerId(components.getServerId()); - final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername); + return resolveRecipientByUsernameOrLink(username, false); + } + throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); + } - username = Username.fromLink(link).getUsername(); - } catch (UsernameLinkUrl.InvalidUsernameLinkException e) { - } catch (IOException | BaseUsernameException e) { - throw new RuntimeException(e); + public RecipientId resolveRecipientByUsernameOrLink( + String username, boolean forceRefresh + ) throws UnregisteredRecipientException { + final Username finalUsername; + try { + finalUsername = getUsernameFromUsernameOrLink(username); + } catch (IOException | BaseUsernameException e) { + throw new RuntimeException(e); + } + if (forceRefresh) { + try { + final var aci = dependencies.getAccountManager().getAciByUsername(finalUsername); + return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername()); + } catch (IOException e) { + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, + username)); } - final String finalUsername = username; - return account.getRecipientStore().resolveRecipientByUsername(finalUsername, () -> { - try { - return getRegisteredUserByUsername(finalUsername); - } catch (Exception e) { - return null; - } - }); } - throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); + return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> { + try { + return dependencies.getAccountManager().getAciByUsername(finalUsername); + } catch (Exception e) { + return null; + } + }); + } + + private Username getUsernameFromUsernameOrLink(String username) throws BaseUsernameException, IOException { + try { + final var usernameLinkUrl = UsernameLinkUrl.fromUri(username); + final var components = usernameLinkUrl.getComponents(); + final var encryptedUsername = dependencies.getAccountManager() + .getEncryptedUsernameFromLinkServerId(components.getServerId()); + final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername); + + return Username.fromLink(link); + } catch (UsernameLinkUrl.InvalidUsernameLinkException e) { + return new Username(username); + } } public Optional resolveRecipientOptional(final RecipientIdentifier.Single recipient) { @@ -246,10 +269,6 @@ public class RecipientHelper { return registeredUsers; } - private ACI getRegisteredUserByUsername(String username) throws IOException, BaseUsernameException { - return dependencies.getAccountManager().getAciByUsername(new Username(username)); - } - public record RegisteredUser(Optional aci, Optional pni) { public RegisteredUser { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 47a5bc03..3f99d8e6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -65,6 +65,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; @@ -280,6 +281,33 @@ public class ManagerImpl implements Manager { })); } + @Override + public Map getUsernameStatus(Set usernames) { + final var registeredUsers = new HashMap(); + for (final var username : usernames) { + try { + final var recipientId = context.getRecipientHelper().resolveRecipientByUsernameOrLink(username, true); + final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); + registeredUsers.put(username, address); + } catch (UnregisteredRecipientException e) { + // ignore + } + } + + return usernames.stream().collect(Collectors.toMap(n -> n, username -> { + final var user = registeredUsers.get(username); + final var serviceId = user == null ? null : user.serviceId().orElse(null); + final var profile = serviceId == null + ? null + : context.getProfileHelper() + .getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId)); + return new UsernameStatus(username, + serviceId == null ? null : serviceId.getRawUuid(), + profile != null + && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED); + })); + } + @Override public void updateAccountAttributes( String deviceName, diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 80d4c37e..8739e493 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -266,13 +266,16 @@ Use listDevices to see the deviceIds. === getUserStatus -Uses a list of phone numbers to determine the statuses of those users. +Uses a list of phone numbers or usernames to determine the statuses of those users. Shows if they are registered on the Signal Servers or not. In json mode this is outputted as a list of objects. [NUMBER [NUMBER ...]]:: One or more numbers to check. +[--username [USERNAME ...]]:: +One or more usernames to check. + === send Send a message to another user or group. diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 7cf78ec8..1691fac8 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -1,5 +1,7 @@ package org.asamk.signal.commands; +import com.fasterxml.jackson.annotation.JsonInclude; + import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; @@ -9,6 +11,7 @@ import org.asamk.signal.commands.exceptions.RateLimitErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UserStatus; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.output.JsonWriter; import org.asamk.signal.output.OutputWriter; import org.asamk.signal.output.PlainTextWriter; @@ -19,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashSet; import java.util.Map; +import java.util.stream.Stream; public class GetUserStatusCommand implements JsonRpcLocalCommand { @@ -32,7 +36,8 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { @Override public void attachToSubparser(final Subparser subparser) { subparser.help("Check if the specified phone number/s have been registered"); - subparser.addArgument("recipient").help("Phone number").nargs("+"); + subparser.addArgument("recipient").help("Phone number").nargs("*"); + subparser.addArgument("--username").help("Specify the recipient username or username link.").nargs("*"); } @Override @@ -54,17 +59,31 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { + ")", e); } + final var usernames = ns.getList("username"); + final var registeredUsernames = usernames == null + ? Map.of() + : m.getUsernameStatus(new HashSet<>(usernames)); + // Output switch (outputWriter) { case JsonWriter writer -> { - var jsonUserStatuses = registered.entrySet().stream().map(entry -> { + var jsonUserStatuses = Stream.concat(registered.entrySet().stream().map(entry -> { final var number = entry.getValue().number(); final var uuid = entry.getValue().uuid(); return new JsonUserStatus(entry.getKey(), number, + null, + uuid == null ? null : uuid.toString(), + uuid != null); + }), registeredUsernames.entrySet().stream().map(entry -> { + final var username = entry.getValue().username(); + final var uuid = entry.getValue().uuid(); + return new JsonUserStatus(entry.getKey(), + null, + username, uuid == null ? null : uuid.toString(), uuid != null); - }).toList(); + })).toList(); writer.write(jsonUserStatuses); } case PlainTextWriter writer -> { @@ -75,9 +94,22 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { userStatus.uuid() != null, userStatus.unrestrictedUnidentifiedAccess() ? " (unrestricted sealed sender)" : ""); } + for (var entry : registeredUsernames.entrySet()) { + final var userStatus = entry.getValue(); + writer.println("{}: {}{}", + entry.getKey(), + userStatus.uuid() != null, + userStatus.unrestrictedUnidentifiedAccess() ? " (unrestricted sealed sender)" : ""); + } } } } - private record JsonUserStatus(String recipient, String number, String uuid, boolean isRegistered) {} + private record JsonUserStatus( + String recipient, + @JsonInclude(JsonInclude.Include.NON_NULL) String number, + @JsonInclude(JsonInclude.Include.NON_NULL) String username, + String uuid, + boolean isRegistered + ) {} } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 522bb3f6..cd65be51 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -47,6 +47,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.freedesktop.dbus.DBusMap; import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.connections.impl.DBusConnection; @@ -122,6 +123,11 @@ public class DbusManagerImpl implements Manager { return result; } + @Override + public Map getUsernameStatus(final Set usernames) { + throw new UnsupportedOperationException(); + } + @Override public void updateAccountAttributes( final String deviceName,