diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 16ffbe0c..d335319a 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2426,6 +2426,13 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] }, +{ + "name":"org.whispersystems.signalservice.api.link.SetDeviceNameRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"getDeviceName","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage", "allDeclaredFields":true, @@ -3136,7 +3143,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, - "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { 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 abb22ef4..2875ee15 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -154,6 +154,8 @@ public interface Manager extends Closeable { List getLinkedDevices() throws IOException; + void updateLinkedDevice(int deviceId, String name) throws IOException, NotPrimaryDeviceException; + void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException; void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException; diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveDeviceNameAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveDeviceNameAction.java new file mode 100644 index 00000000..a099c584 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RetrieveDeviceNameAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.helper.Context; + +public class RetrieveDeviceNameAction implements HandleAction { + + private static final RetrieveDeviceNameAction INSTANCE = new RetrieveDeviceNameAction(); + + public static RetrieveDeviceNameAction create() { + return INSTANCE; + } + + private RetrieveDeviceNameAction() { + } + + @Override + public void execute(Context context) throws Throwable { + context.getAccountHelper().refreshDeviceName(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index c2d0a08b..82fb7d5a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -522,6 +522,22 @@ public class AccountHelper { account.setEncryptedDeviceName(encryptedDeviceName); } + public void setDeviceName(int deviceId, String deviceName) throws IOException { + final var privateKey = account.getAciIdentityKeyPair().getPrivateKey(); + final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey); + handleResponseException(dependencies.getLinkDeviceApi().setDeviceName(encryptedDeviceName, deviceId)); + context.getSyncHelper().sendDeviceNameChange(deviceId); + } + + public void refreshDeviceName() throws IOException { + final var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices()); + final var deviceId = account.getDeviceId(); + final var device = devices.stream().filter(d -> d.id == deviceId).findFirst(); + if (device.isPresent()) { + account.setEncryptedDeviceName(device.get().name); + } + } + public void updateAccountAttributes() throws IOException { handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null))); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 79d24b2a..de824b0c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -5,6 +5,7 @@ import org.asamk.signal.manager.actions.HandleAction; import org.asamk.signal.manager.actions.RefreshPreKeysAction; import org.asamk.signal.manager.actions.RenewSessionAction; import org.asamk.signal.manager.actions.ResendMessageAction; +import org.asamk.signal.manager.actions.RetrieveDeviceNameAction; import org.asamk.signal.manager.actions.RetrieveProfileAction; import org.asamk.signal.manager.actions.SendGroupInfoAction; import org.asamk.signal.manager.actions.SendGroupInfoRequestAction; @@ -633,6 +634,12 @@ public final class IncomingMessageHandler { context.getAccountHelper().handlePniChangeNumberMessage(pniChangeNumber, updatedPni); } } + if (syncMessage.getDeviceNameChange().isPresent()) { + final var deviceNameChange = syncMessage.getDeviceNameChange().get(); + if (deviceNameChange.deviceId != null && deviceNameChange.deviceId == account.getDeviceId()) { + actions.add(RetrieveDeviceNameAction.create()); + } + } return actions; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 1911b8bb..7d553120 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -52,6 +52,8 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import okio.ByteString; + public class SyncHelper { private static final Logger logger = LoggerFactory.getLogger(SyncHelper.class); @@ -406,6 +408,11 @@ public class SyncHelper { return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response)); } + public SendMessageResult sendDeviceNameChange(final int deviceId) { + final var deviceNameChange = new SyncMessage.DeviceNameChange(deviceId, ByteString.EMPTY); + return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forDeviceNameChange(deviceNameChange)); + } + private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) { var r = new SyncMessage.Request.Builder().type(type).build(); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); 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 dca5a624..4eced7af 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 @@ -487,6 +487,21 @@ public class ManagerImpl implements Manager { }).toList(); } + @Override + public void updateLinkedDevice( + final int deviceId, + final String name + ) throws IOException, NotPrimaryDeviceException { + if (deviceId == account.getDeviceId()) { + context.getAccountHelper().setDeviceName(name); + } else { + if (!account.isPrimaryDevice()) { + throw new NotPrimaryDeviceException(); + } + context.getAccountHelper().setDeviceName(deviceId, name); + } + } + private Long getPlaintextCreatedAt(DeviceInfo d) { final var DECRYPTION_INFO = "deviceCreatedAt"; var identityKey = account.getAciIdentityKeyPair().getPrivateKey(); diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index 52d48077..052de562 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -58,6 +58,7 @@ public class Commands { addCommand(new UpdateAccountCommand()); addCommand(new UpdateConfigurationCommand()); addCommand(new UpdateContactCommand()); + addCommand(new UpdateDeviceCommand()); addCommand(new UpdateGroupCommand()); addCommand(new UpdateProfileCommand()); addCommand(new UploadStickerPackCommand()); diff --git a/src/main/java/org/asamk/signal/commands/UpdateDeviceCommand.java b/src/main/java/org/asamk/signal/commands/UpdateDeviceCommand.java new file mode 100644 index 00000000..c26c146f --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/UpdateDeviceCommand.java @@ -0,0 +1,50 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; + +import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.IOErrorException; +import org.asamk.signal.commands.exceptions.UserErrorException; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.NotPrimaryDeviceException; +import org.asamk.signal.output.OutputWriter; + +import java.io.IOException; + +public class UpdateDeviceCommand implements JsonRpcLocalCommand { + + @Override + public String getName() { + return "updateDevice"; + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help("Update a linked device."); + subparser.addArgument("-d", "--device-id", "--deviceId") + .type(int.class) + .required(true) + .help("Specify the device you want to update. Use listDevices to see the deviceIds."); + subparser.addArgument("-n", "--device-name") + .required(true) + .help("Specify a name to describe the given device."); + } + + @Override + public void handleCommand( + final Namespace ns, + final Manager m, + final OutputWriter outputWriter + ) throws CommandException { + try { + final var deviceId = ns.getInt("device-id"); + final var deviceName = ns.getString("device-name"); + m.updateLinkedDevice(deviceId, deviceName); + } catch (NotPrimaryDeviceException e) { + throw new UserErrorException("This command doesn't work on linked devices."); + } catch (IOException e) { + throw new IOErrorException("Error while updating device: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index cc0f308b..6428ee2e 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -246,6 +246,14 @@ public class DbusManagerImpl implements Manager { }).toList(); } + @Override + public void updateLinkedDevice( + final int deviceId, + final String name + ) throws IOException, NotPrimaryDeviceException { + throw new UnsupportedOperationException(); + } + @Override public void removeLinkedDevices(final int deviceId) throws IOException { final var devicePath = signal.getDevice(deviceId);