mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +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,
|
||||
"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",
|
||||
|
|
|
@ -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<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
|
||||
|
||||
Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames);
|
||||
|
||||
void updateAccountAttributes(
|
||||
String deviceName,
|
||||
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,30 +91,53 @@ 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);
|
||||
|
||||
username = Username.fromLink(link).getUsername();
|
||||
} catch (UsernameLinkUrl.InvalidUsernameLinkException e) {
|
||||
} catch (IOException | BaseUsernameException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
final String finalUsername = username;
|
||||
return account.getRecipientStore().resolveRecipientByUsername(finalUsername, () -> {
|
||||
try {
|
||||
return getRegisteredUserByUsername(finalUsername);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return resolveRecipientByUsernameOrLink(username, false);
|
||||
}
|
||||
throw new AssertionError("Unexpected RecipientIdentifier: " + recipient);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
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) {
|
||||
try {
|
||||
return Optional.of(resolveRecipient(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> aci, Optional<PNI> pni) {
|
||||
|
||||
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.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<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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.<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);
|
||||
}).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);
|
||||
}
|
||||
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
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -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<String, UsernameStatus> getUsernameStatus(final Set<String> usernames) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAccountAttributes(
|
||||
final String deviceName,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue