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<File> avatar
) throws IOException;
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
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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);
+ }
+}
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;
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.");
}
}
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;
}
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;
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;
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;
private RecipientStore recipientStore;
private StickerStore stickerStore;
private StickerStore.Storage stickerStoreStorage;
+ private ConfigurationStore configurationStore;
+ private ConfigurationStore.Storage configurationStoreStorage;
private MessageCache messageCache;
account.recipientStore,
account::saveGroupStore);
account.stickerStore = new StickerStore(account::saveStickerStore);
+ account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
account.registered = false;
account.recipientStore,
account::saveGroupStore);
account.stickerStore = new StickerStore(account::saveStickerStore);
+ account.configurationStore = new ConfigurationStore(account::saveConfigurationStore);
account.recipientStore.resolveRecipientTrusted(account.getSelfAddress());
account.migrateLegacyConfigs();
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) {
save();
}
+ private void saveConfigurationStore(ConfigurationStore.Storage storage) {
+ this.configurationStoreStorage = storage;
+ save();
+ }
+
private void save() {
synchronized (fileChannel) {
var rootNode = jsonProcessor.createObjectNode();
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
return senderKeyStore;
}
+ public ConfigurationStore getConfigurationStore() {
+ return configurationStore;
+ }
+
public MessageCache getMessageCache() {
return messageCache;
}
*-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.
addCommand(new UnblockCommand());
addCommand(new UnregisterCommand());
addCommand(new UpdateAccountCommand());
+ addCommand(new UpdateConfigurationCommand());
addCommand(new UpdateContactCommand());
addCommand(new UpdateGroupCommand());
addCommand(new UpdateProfileCommand());
--- /dev/null
+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.");
+ }
+ }
+}
}
}
+ @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,