From: AsamK Date: Wed, 29 Sep 2021 17:38:31 +0000 (+0200) Subject: Implement configuration handling X-Git-Tag: v0.9.1~35 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/6f5e72119e0c996f1efefecda11e33422d44a171?hp=c9f5550d1821ee99879c75db124baf46642fd846 Implement configuration handling Closes #747 --- 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 cba438f8..cd7b0335 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -98,6 +98,13 @@ public interface Manager extends Closeable { void updateAccountAttributes(String deviceName) throws IOException; + void updateConfiguration( + final Boolean readReceipts, + final Boolean unidentifiedDeliveryIndicators, + final Boolean typingIndicators, + final Boolean linkPreviews + ) throws IOException, NotMasterDeviceException; + void setProfile( String givenName, String familyName, String about, String aboutEmoji, Optional avatar ) throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index de60fa50..36c131db 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -320,6 +320,31 @@ public class ManagerImpl implements Manager { account.isDiscoverableByPhoneNumber()); } + @Override + public void updateConfiguration( + final Boolean readReceipts, + final Boolean unidentifiedDeliveryIndicators, + final Boolean typingIndicators, + final Boolean linkPreviews + ) throws IOException, NotMasterDeviceException { + if (!account.isMasterDevice()) { + throw new NotMasterDeviceException(); + } + if (readReceipts != null) { + account.getConfigurationStore().setReadReceipts(readReceipts); + } + if (unidentifiedDeliveryIndicators != null) { + account.getConfigurationStore().setUnidentifiedDeliveryIndicators(unidentifiedDeliveryIndicators); + } + if (typingIndicators != null) { + account.getConfigurationStore().setTypingIndicators(typingIndicators); + } + if (linkPreviews != null) { + account.getConfigurationStore().setLinkPreviews(linkPreviews); + } + syncHelper.sendConfigurationMessage(); + } + /** * @param givenName if null, the previous givenName will be kept * @param familyName if null, the previous familyName will be kept diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java new file mode 100644 index 00000000..0e050f0a --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendSyncConfigurationAction.java @@ -0,0 +1,20 @@ +package org.asamk.signal.manager.actions; + +import org.asamk.signal.manager.jobs.Context; + +public class SendSyncConfigurationAction implements HandleAction { + + private static final SendSyncConfigurationAction INSTANCE = new SendSyncConfigurationAction(); + + private SendSyncConfigurationAction() { + } + + public static SendSyncConfigurationAction create() { + return INSTANCE; + } + + @Override + public void execute(Context context) throws Throwable { + context.getSyncHelper().sendConfigurationMessage(); + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/configuration/ConfigurationStore.java b/lib/src/main/java/org/asamk/signal/manager/configuration/ConfigurationStore.java new file mode 100644 index 00000000..e7e1b5f5 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/configuration/ConfigurationStore.java @@ -0,0 +1,93 @@ +package org.asamk.signal.manager.configuration; + +public class ConfigurationStore { + + private final Saver saver; + + private Boolean readReceipts; + private Boolean unidentifiedDeliveryIndicators; + private Boolean typingIndicators; + private Boolean linkPreviews; + + public ConfigurationStore(final Saver saver) { + this.saver = saver; + } + + public static ConfigurationStore fromStorage(Storage storage, Saver saver) { + final var store = new ConfigurationStore(saver); + store.readReceipts = storage.readReceipts; + store.unidentifiedDeliveryIndicators = storage.unidentifiedDeliveryIndicators; + store.typingIndicators = storage.typingIndicators; + store.linkPreviews = storage.linkPreviews; + return store; + } + + public Boolean getReadReceipts() { + return readReceipts; + } + + public void setReadReceipts(final boolean readReceipts) { + this.readReceipts = readReceipts; + saver.save(toStorage()); + } + + public Boolean getUnidentifiedDeliveryIndicators() { + return unidentifiedDeliveryIndicators; + } + + public void setUnidentifiedDeliveryIndicators(final boolean unidentifiedDeliveryIndicators) { + this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators; + saver.save(toStorage()); + } + + public Boolean getTypingIndicators() { + return typingIndicators; + } + + public void setTypingIndicators(final boolean typingIndicators) { + this.typingIndicators = typingIndicators; + saver.save(toStorage()); + } + + public Boolean getLinkPreviews() { + return linkPreviews; + } + + public void setLinkPreviews(final boolean linkPreviews) { + this.linkPreviews = linkPreviews; + saver.save(toStorage()); + } + + private Storage toStorage() { + return new Storage(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + } + + public static final class Storage { + + public Boolean readReceipts; + public Boolean unidentifiedDeliveryIndicators; + public Boolean typingIndicators; + public Boolean linkPreviews; + + // For deserialization + private Storage() { + } + + public Storage( + final Boolean readReceipts, + final Boolean unidentifiedDeliveryIndicators, + final Boolean typingIndicators, + final Boolean linkPreviews + ) { + this.readReceipts = readReceipts; + this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators; + this.typingIndicators = typingIndicators; + this.linkPreviews = linkPreviews; + } + } + + public interface Saver { + + void save(Storage storage); + } +} 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 64e16857..dead91b0 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 @@ -15,6 +15,7 @@ import org.asamk.signal.manager.actions.SendGroupInfoRequestAction; import org.asamk.signal.manager.actions.SendReceiptAction; import org.asamk.signal.manager.actions.SendRetryMessageRequestAction; import org.asamk.signal.manager.actions.SendSyncBlockedListAction; +import org.asamk.signal.manager.actions.SendSyncConfigurationAction; import org.asamk.signal.manager.actions.SendSyncContactsAction; import org.asamk.signal.manager.actions.SendSyncGroupsAction; import org.asamk.signal.manager.actions.SendSyncKeysAction; @@ -271,7 +272,9 @@ public final class IncomingMessageHandler { if (rm.isKeysRequest()) { actions.add(SendSyncKeysAction.create()); } - // TODO Handle rm.isConfigurationRequest(); + if (rm.isConfigurationRequest()) { + actions.add(SendSyncConfigurationAction.create()); + } } if (syncMessage.getGroups().isPresent()) { logger.warn("Received a group v1 sync message, that can't be handled anymore, ignoring."); @@ -353,7 +356,13 @@ public final class IncomingMessageHandler { } } if (syncMessage.getConfiguration().isPresent()) { - // TODO + final var configurationMessage = syncMessage.getConfiguration().get(); + account.getConfigurationStore().setReadReceipts(configurationMessage.getReadReceipts().orNull()); + account.getConfigurationStore().setLinkPreviews(configurationMessage.getLinkPreviews().orNull()); + account.getConfigurationStore().setTypingIndicators(configurationMessage.getTypingIndicators().orNull()); + account.getConfigurationStore() + .setUnidentifiedDeliveryIndicators(configurationMessage.getUnidentifiedDeliveryIndicators() + .orNull()); } 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 6db1ca7d..e3fc7fc2 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 @@ -14,6 +14,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream; @@ -221,6 +222,15 @@ public class SyncHelper { sendHelper.sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage)); } + public void sendConfigurationMessage() throws IOException { + final var config = account.getConfigurationStore(); + var configurationMessage = new ConfigurationMessage(Optional.fromNullable(config.getReadReceipts()), + Optional.fromNullable(config.getUnidentifiedDeliveryIndicators()), + Optional.fromNullable(config.getTypingIndicators()), + Optional.fromNullable(config.getLinkPreviews())); + sendHelper.sendSyncMessage(SignalServiceSyncMessage.forConfiguration(configurationMessage)); + } + public void handleSyncDeviceContacts(final InputStream input) throws IOException { final var s = new DeviceContactsInputStream(input); DeviceContact c; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index fd4ec597..9c51017c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.signal.manager.TrustLevel; +import org.asamk.signal.manager.configuration.ConfigurationStore; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.storage.contacts.ContactsStore; import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore; @@ -103,6 +104,8 @@ public class SignalAccount implements Closeable { private RecipientStore recipientStore; private StickerStore stickerStore; private StickerStore.Storage stickerStoreStorage; + private ConfigurationStore configurationStore; + private ConfigurationStore.Storage configurationStoreStorage; private MessageCache messageCache; @@ -159,6 +162,7 @@ public class SignalAccount implements Closeable { account.recipientStore, account::saveGroupStore); account.stickerStore = new StickerStore(account::saveStickerStore); + account.configurationStore = new ConfigurationStore(account::saveConfigurationStore); account.registered = false; @@ -267,6 +271,7 @@ public class SignalAccount implements Closeable { account.recipientStore, account::saveGroupStore); account.stickerStore = new StickerStore(account::saveStickerStore); + account.configurationStore = new ConfigurationStore(account::saveConfigurationStore); account.recipientStore.resolveRecipientTrusted(account.getSelfAddress()); account.migrateLegacyConfigs(); @@ -491,6 +496,15 @@ public class SignalAccount implements Closeable { stickerStore = new StickerStore(this::saveStickerStore); } + if (rootNode.hasNonNull("configurationStore")) { + configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"), + ConfigurationStore.Storage.class); + configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage, + this::saveConfigurationStore); + } else { + configurationStore = new ConfigurationStore(this::saveConfigurationStore); + } + migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig; if (migratedLegacyConfig) { @@ -677,6 +691,11 @@ public class SignalAccount implements Closeable { save(); } + private void saveConfigurationStore(ConfigurationStore.Storage storage) { + this.configurationStoreStorage = storage; + save(); + } + private void save() { synchronized (fileChannel) { var rootNode = jsonProcessor.createObjectNode(); @@ -707,7 +726,8 @@ public class SignalAccount implements Closeable { profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize())) .put("registered", registered) .putPOJO("groupStore", groupStoreStorage) - .putPOJO("stickerStore", stickerStoreStorage); + .putPOJO("stickerStore", stickerStoreStorage) + .putPOJO("configurationStore", configurationStoreStorage); try { try (var output = new ByteArrayOutputStream()) { // Write to memory first to prevent corrupting the file in case of serialization errors @@ -797,6 +817,10 @@ public class SignalAccount implements Closeable { return senderKeyStore; } + public ConfigurationStore getConfigurationStore() { + return configurationStore; + } + public MessageCache getMessageCache() { return messageCache; } diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 573ade7c..9829fe00 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -113,6 +113,23 @@ Can fix problems with receiving messages. *-n* NAME, *--device-name* NAME:: Set a new device name for the main or linked device +=== updateConfiguration + +Update signal configs and sync them to linked devices. +This command only works on the main devices. + +*--read-receipts* {true,false}:: +Indicates if Signal should send read receipts. + +*--unidentified-delivery-indicators* {true,false}:: +Indicates if Signal should show unidentified delivery indicators. + +*--typing-indicators* {true,false}:: +Indicates if Signal should send/show typing indicators. + +*--link-previews* {true,false}:: +Indicates if Signal should generate link previews. + === setPin Set a registration lock pin, to prevent others from registering this number. diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index 5d637eee..1d6dd26d 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -39,6 +39,7 @@ public class Commands { addCommand(new UnblockCommand()); addCommand(new UnregisterCommand()); addCommand(new UpdateAccountCommand()); + addCommand(new UpdateConfigurationCommand()); addCommand(new UpdateContactCommand()); addCommand(new UpdateGroupCommand()); addCommand(new UpdateProfileCommand()); diff --git a/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java new file mode 100644 index 00000000..9ca126d0 --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java @@ -0,0 +1,55 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; + +import org.asamk.signal.OutputWriter; +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.NotMasterDeviceException; + +import java.io.IOException; + +public class UpdateConfigurationCommand implements JsonRpcLocalCommand { + + @Override + public String getName() { + return "updateConfiguration"; + } + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help("Update signal configs and sync them to linked devices."); + subparser.addArgument("--read-receipts") + .type(Boolean.class) + .help("Indicates if Signal should send read receipts."); + subparser.addArgument("--unidentified-delivery-indicators") + .type(Boolean.class) + .help("Indicates if Signal should show unidentified delivery indicators."); + subparser.addArgument("--typing-indicators") + .type(Boolean.class) + .help("Indicates if Signal should send/show typing indicators."); + subparser.addArgument("--link-previews") + .type(Boolean.class) + .help("Indicates if Signal should generate link previews."); + } + + @Override + public void handleCommand( + final Namespace ns, final Manager m, final OutputWriter outputWriter + ) throws CommandException { + final var readReceipts = ns.getBoolean("read-receipts"); + final var unidentifiedDeliveryIndicators = ns.getBoolean("unidentified-delivery-indicators"); + final var typingIndicators = ns.getBoolean("typing-indicators"); + final var linkPreviews = ns.getBoolean("link-previews"); + try { + m.updateConfiguration(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews); + } catch (IOException e) { + throw new IOErrorException("UpdateAccount error: " + e.getMessage(), e); + } catch (NotMasterDeviceException e) { + throw new UserErrorException("This command doesn't work on linked devices."); + } + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index b9f5ae11..ea776797 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -93,6 +93,16 @@ public class DbusManagerImpl implements Manager { } } + @Override + public void updateConfiguration( + final Boolean readReceipts, + final Boolean unidentifiedDeliveryIndicators, + final Boolean typingIndicators, + final Boolean linkPreviews + ) throws IOException { + throw new UnsupportedOperationException(); + } + @Override public void setProfile( final String givenName,