"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",
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;
*/
Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
+ Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames);
+
void updateAccountAttributes(
String deviceName,
Boolean unrestrictedUnidentifiedSender,
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import java.util.UUID;
+
+public record UsernameStatus(String username, UUID uuid, boolean unrestrictedUnidentifiedAccess) {}
});
} 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<RecipientId> resolveRecipientOptional(final RecipientIdentifier.Single recipient) {
return registeredUsers;
}
- private ACI getRegisteredUserByUsername(String username) throws IOException, BaseUsernameException {
- return dependencies.getAccountManager().getAciByUsername(new Username(username));
- }
-
public record RegisteredUser(Optional<ACI> aci, Optional<PNI> pni) {
public RegisteredUser {
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;
}));
}
+ @Override
+ public Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames) {
+ final var registeredUsers = new HashMap<String, RecipientAddress>();
+ 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,
=== 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.
package org.asamk.signal.commands;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
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;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
+import java.util.stream.Stream;
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
+ ")", e);
}
+ final var usernames = ns.<String>getList("username");
+ final var registeredUsernames = usernames == null
+ ? Map.<String, UsernameStatus>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 -> {
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
+ ) {}
}
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;
return result;
}
+ @Override
+ public Map<String, UsernameStatus> getUsernameStatus(final Set<String> usernames) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void updateAccountAttributes(
final String deviceName,