mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Extend getUserStatus command for usernames
This commit is contained in:
parent
8b4f377cf1
commit
d356d92b5e
8 changed files with 126 additions and 30 deletions
|
@ -693,7 +693,7 @@
|
||||||
"allDeclaredFields":true,
|
"allDeclaredFields":true,
|
||||||
"allDeclaredMethods":true,
|
"allDeclaredMethods":true,
|
||||||
"allDeclaredConstructors":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",
|
"name":"org.asamk.signal.commands.ListAccountsCommand$JsonAccount",
|
||||||
|
|
|
@ -44,6 +44,7 @@ import org.asamk.signal.manager.api.UpdateGroup;
|
||||||
import org.asamk.signal.manager.api.UpdateProfile;
|
import org.asamk.signal.manager.api.UpdateProfile;
|
||||||
import org.asamk.signal.manager.api.UserStatus;
|
import org.asamk.signal.manager.api.UserStatus;
|
||||||
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
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.api.VerificationMethodNotAvailableException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -92,6 +93,8 @@ public interface Manager extends Closeable {
|
||||||
*/
|
*/
|
||||||
Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
|
Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
|
||||||
|
|
||||||
|
Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames);
|
||||||
|
|
||||||
void updateAccountAttributes(
|
void updateAccountAttributes(
|
||||||
String deviceName,
|
String deviceName,
|
||||||
Boolean unrestrictedUnidentifiedSender,
|
Boolean unrestrictedUnidentifiedSender,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record UsernameStatus(String username, UUID uuid, boolean unrestrictedUnidentifiedAccess) {}
|
|
@ -91,28 +91,51 @@ public class RecipientHelper {
|
||||||
});
|
});
|
||||||
} else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) {
|
} else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) {
|
||||||
var username = usernameRecipient.username();
|
var username = usernameRecipient.username();
|
||||||
try {
|
return resolveRecipientByUsernameOrLink(username, false);
|
||||||
UsernameLinkUrl usernameLinkUrl = UsernameLinkUrl.fromUri(username);
|
}
|
||||||
final var components = usernameLinkUrl.getComponents();
|
throw new AssertionError("Unexpected RecipientIdentifier: " + recipient);
|
||||||
final var encryptedUsername = dependencies.getAccountManager()
|
}
|
||||||
.getEncryptedUsernameFromLinkServerId(components.getServerId());
|
|
||||||
final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername);
|
|
||||||
|
|
||||||
username = Username.fromLink(link).getUsername();
|
public RecipientId resolveRecipientByUsernameOrLink(
|
||||||
} catch (UsernameLinkUrl.InvalidUsernameLinkException e) {
|
String username, boolean forceRefresh
|
||||||
|
) throws UnregisteredRecipientException {
|
||||||
|
final Username finalUsername;
|
||||||
|
try {
|
||||||
|
finalUsername = getUsernameFromUsernameOrLink(username);
|
||||||
} catch (IOException | BaseUsernameException e) {
|
} catch (IOException | BaseUsernameException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
final String finalUsername = username;
|
if (forceRefresh) {
|
||||||
return account.getRecipientStore().resolveRecipientByUsername(finalUsername, () -> {
|
|
||||||
try {
|
try {
|
||||||
return getRegisteredUserByUsername(finalUsername);
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> {
|
||||||
|
try {
|
||||||
|
return dependencies.getAccountManager().getAciByUsername(finalUsername);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new AssertionError("Unexpected RecipientIdentifier: " + recipient);
|
|
||||||
|
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) {
|
public Optional<RecipientId> resolveRecipientOptional(final RecipientIdentifier.Single recipient) {
|
||||||
|
@ -246,10 +269,6 @@ public class RecipientHelper {
|
||||||
return registeredUsers;
|
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 record RegisteredUser(Optional<ACI> aci, Optional<PNI> pni) {
|
||||||
|
|
||||||
public RegisteredUser {
|
public RegisteredUser {
|
||||||
|
|
|
@ -65,6 +65,7 @@ import org.asamk.signal.manager.api.UpdateGroup;
|
||||||
import org.asamk.signal.manager.api.UpdateProfile;
|
import org.asamk.signal.manager.api.UpdateProfile;
|
||||||
import org.asamk.signal.manager.api.UserStatus;
|
import org.asamk.signal.manager.api.UserStatus;
|
||||||
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
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.api.VerificationMethodNotAvailableException;
|
||||||
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
|
||||||
import org.asamk.signal.manager.helper.AccountFileUpdater;
|
import org.asamk.signal.manager.helper.AccountFileUpdater;
|
||||||
|
@ -280,6 +281,33 @@ public class ManagerImpl implements Manager {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public void updateAccountAttributes(
|
public void updateAccountAttributes(
|
||||||
String deviceName,
|
String deviceName,
|
||||||
|
|
|
@ -266,13 +266,16 @@ Use listDevices to see the deviceIds.
|
||||||
|
|
||||||
=== getUserStatus
|
=== 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.
|
Shows if they are registered on the Signal Servers or not.
|
||||||
In json mode this is outputted as a list of objects.
|
In json mode this is outputted as a list of objects.
|
||||||
|
|
||||||
[NUMBER [NUMBER ...]]::
|
[NUMBER [NUMBER ...]]::
|
||||||
One or more numbers to check.
|
One or more numbers to check.
|
||||||
|
|
||||||
|
[--username [USERNAME ...]]::
|
||||||
|
One or more usernames to check.
|
||||||
|
|
||||||
=== send
|
=== send
|
||||||
|
|
||||||
Send a message to another user or group.
|
Send a message to another user or group.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.asamk.signal.commands;
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
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.Manager;
|
||||||
import org.asamk.signal.manager.api.RateLimitException;
|
import org.asamk.signal.manager.api.RateLimitException;
|
||||||
import org.asamk.signal.manager.api.UserStatus;
|
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.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;
|
||||||
|
@ -19,6 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
||||||
|
|
||||||
|
@ -32,7 +36,8 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
||||||
@Override
|
@Override
|
||||||
public void attachToSubparser(final Subparser subparser) {
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
subparser.help("Check if the specified phone number/s have been registered");
|
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
|
@Override
|
||||||
|
@ -54,17 +59,31 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
||||||
+ ")", e);
|
+ ")", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final var usernames = ns.<String>getList("username");
|
||||||
|
final var registeredUsernames = usernames == null
|
||||||
|
? Map.<String, UsernameStatus>of()
|
||||||
|
: m.getUsernameStatus(new HashSet<>(usernames));
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
switch (outputWriter) {
|
switch (outputWriter) {
|
||||||
case JsonWriter writer -> {
|
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 number = entry.getValue().number();
|
||||||
final var uuid = entry.getValue().uuid();
|
final var uuid = entry.getValue().uuid();
|
||||||
return new JsonUserStatus(entry.getKey(),
|
return new JsonUserStatus(entry.getKey(),
|
||||||
number,
|
number,
|
||||||
|
null,
|
||||||
uuid == null ? null : uuid.toString(),
|
uuid == null ? null : uuid.toString(),
|
||||||
uuid != null);
|
uuid != null);
|
||||||
}).toList();
|
}), 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();
|
||||||
writer.write(jsonUserStatuses);
|
writer.write(jsonUserStatuses);
|
||||||
}
|
}
|
||||||
case PlainTextWriter writer -> {
|
case PlainTextWriter writer -> {
|
||||||
|
@ -75,9 +94,22 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand {
|
||||||
userStatus.uuid() != null,
|
userStatus.uuid() != null,
|
||||||
userStatus.unrestrictedUnidentifiedAccess() ? " (unrestricted sealed sender)" : "");
|
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
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.asamk.signal.manager.api.UpdateGroup;
|
||||||
import org.asamk.signal.manager.api.UpdateProfile;
|
import org.asamk.signal.manager.api.UpdateProfile;
|
||||||
import org.asamk.signal.manager.api.UserStatus;
|
import org.asamk.signal.manager.api.UserStatus;
|
||||||
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
||||||
|
import org.asamk.signal.manager.api.UsernameStatus;
|
||||||
import org.freedesktop.dbus.DBusMap;
|
import org.freedesktop.dbus.DBusMap;
|
||||||
import org.freedesktop.dbus.DBusPath;
|
import org.freedesktop.dbus.DBusPath;
|
||||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||||
|
@ -122,6 +123,11 @@ public class DbusManagerImpl implements Manager {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, UsernameStatus> getUsernameStatus(final Set<String> usernames) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAccountAttributes(
|
public void updateAccountAttributes(
|
||||||
final String deviceName,
|
final String deviceName,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue