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 da69a8da55..6153000b8e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -193,6 +193,8 @@ SendMessageResults sendPaymentNotificationMessage( SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; + void hideRecipient(RecipientIdentifier.Single recipient); + void deleteRecipient(RecipientIdentifier.Single recipient); void deleteContact(RecipientIdentifier.Single recipient); diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java index e6dd5f930b..f84c667b9d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -9,7 +9,8 @@ public record Contact( int messageExpirationTime, boolean isBlocked, boolean isArchived, - boolean isProfileSharingEnabled + boolean isProfileSharingEnabled, + boolean isHidden ) { private Contact(final Builder builder) { @@ -17,9 +18,10 @@ private Contact(final Builder builder) { builder.familyName, builder.color, builder.messageExpirationTime, - builder.blocked, - builder.archived, - builder.profileSharingEnabled); + builder.isBlocked, + builder.isArchived, + builder.isProfileSharingEnabled, + builder.isHidden); } public static Builder newBuilder() { @@ -32,9 +34,10 @@ public static Builder newBuilder(final Contact copy) { builder.familyName = copy.familyName(); builder.color = copy.color(); builder.messageExpirationTime = copy.messageExpirationTime(); - builder.blocked = copy.isBlocked(); - builder.archived = copy.isArchived(); - builder.profileSharingEnabled = copy.isProfileSharingEnabled(); + builder.isBlocked = copy.isBlocked(); + builder.isArchived = copy.isArchived(); + builder.isProfileSharingEnabled = copy.isProfileSharingEnabled(); + builder.isHidden = copy.isHidden(); return builder; } @@ -59,13 +62,18 @@ public static final class Builder { private String familyName; private String color; private int messageExpirationTime; - private boolean blocked; - private boolean archived; - private boolean profileSharingEnabled; + private boolean isBlocked; + private boolean isArchived; + private boolean isProfileSharingEnabled; + private boolean isHidden; private Builder() { } + public static Builder newBuilder() { + return new Builder(); + } + public Builder withGivenName(final String val) { givenName = val; return this; @@ -86,18 +94,23 @@ public Builder withMessageExpirationTime(final int val) { return this; } - public Builder withBlocked(final boolean val) { - blocked = val; + public Builder withIsBlocked(final boolean val) { + isBlocked = val; + return this; + } + + public Builder withIsArchived(final boolean val) { + isArchived = val; return this; } - public Builder withArchived(final boolean val) { - archived = val; + public Builder withIsProfileSharingEnabled(final boolean val) { + isProfileSharingEnabled = val; return this; } - public Builder withProfileSharingEnabled(final boolean val) { - profileSharingEnabled = val; + public Builder withIsHidden(final boolean val) { + isHidden = val; return this; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java index 4e96baccfb..609c2bcf77 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java @@ -20,6 +20,7 @@ public boolean isContactBlocked(final RecipientId recipientId) { public void setContactName(final RecipientId recipientId, final String givenName, final String familyName) { var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); + builder.withIsHidden(false); if (givenName != null) { builder.withGivenName(givenName); } @@ -43,8 +44,14 @@ public void setContactBlocked(RecipientId recipientId, boolean blocked) { var contact = account.getContactStore().getContact(recipientId); final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); if (blocked) { - builder.withProfileSharingEnabled(false); + builder.withIsProfileSharingEnabled(false); } - account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build()); + account.getContactStore().storeContact(recipientId, builder.withIsBlocked(blocked).build()); + } + + public void setContactHidden(RecipientId recipientId, boolean hidden) { + var contact = account.getContactStore().getContact(recipientId); + final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); + account.getContactStore().storeContact(recipientId, builder.withIsHidden(hidden).build()); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 236078157b..5ffb964634 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -78,9 +78,9 @@ public SendMessageResult sendMessage( Optional editTargetTimestamp ) { var contact = account.getContactStore().getContact(recipientId); - if (contact == null || !contact.isProfileSharingEnabled()) { + if (contact == null || !contact.isProfileSharingEnabled() || contact.isHidden()) { final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); - contact = contactBuilder.withProfileSharingEnabled(true).build(); + contact = contactBuilder.withIsProfileSharingEnabled(true).withIsHidden(false).build(); account.getContactStore().storeContact(recipientId, contact); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java index 35821c5b81..f251856842 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java @@ -116,11 +116,13 @@ private void readContactRecord(final SignalStorageRecord record) { final var blocked = contact != null && contact.isBlocked(); final var profileShared = contact != null && contact.isProfileSharingEnabled(); final var archived = contact != null && contact.isArchived(); + final var hidden = contact != null && contact.isHidden(); final var contactGivenName = contact == null ? null : contact.givenName(); final var contactFamilyName = contact == null ? null : contact.familyName(); if (blocked != contactRecord.isBlocked() || profileShared != contactRecord.isProfileSharingEnabled() || archived != contactRecord.isArchived() + || hidden != contactRecord.isHidden() || ( contactRecord.getSystemGivenName().isPresent() && !contactRecord.getSystemGivenName() .get() @@ -133,9 +135,10 @@ private void readContactRecord(final SignalStorageRecord record) { )) { logger.debug("Storing new or updated contact {}", recipientId); final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); - final var newContact = contactBuilder.withBlocked(contactRecord.isBlocked()) - .withProfileSharingEnabled(contactRecord.isProfileSharingEnabled()) - .withArchived(contactRecord.isArchived()); + final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked()) + .withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled()) + .withIsArchived(contactRecord.isArchived()) + .withIsHidden(contactRecord.isHidden()); if (contactRecord.getSystemGivenName().isPresent() || contactRecord.getSystemFamilyName().isPresent()) { newContact.withGivenName(contactRecord.getSystemGivenName().orElse(null)) .withFamilyName(contactRecord.getSystemFamilyName().orElse(null)); 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 0badc54498..f6392ea9e2 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 @@ -339,8 +339,8 @@ public void handleSyncDeviceContacts(final InputStream input) throws IOException if (c.getExpirationTimer().isPresent()) { builder.withMessageExpirationTime(c.getExpirationTimer().get()); } - builder.withBlocked(c.isBlocked()); - builder.withArchived(c.isArchived()); + builder.withIsBlocked(c.isBlocked()); + builder.withIsArchived(c.isArchived()); account.getContactStore().storeContact(recipientId, builder.build()); if (c.getAvatar().isPresent()) { 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 af2c6558c4..cc67229a94 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 @@ -840,6 +840,15 @@ public SendMessageResults sendEndSessionMessage(Set } } + @Override + public void hideRecipient(final RecipientIdentifier.Single recipient) { + final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient); + if (recipientIdOptional.isPresent()) { + context.getContactHelper().setContactHidden(recipientIdOptional.get(), true); + account.removeRecipient(recipientIdOptional.get()); + } + } + @Override public void deleteRecipient(final RecipientIdentifier.Single recipient) { final var recipientIdOptional = context.getRecipientHelper().resolveRecipientOptional(recipient); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index 94dff8fcb1..43a146a7a7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -32,7 +32,7 @@ public class AccountDatabase extends Database { private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); - private static final long DATABASE_VERSION = 18; + private static final long DATABASE_VERSION = 19; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -531,5 +531,13 @@ CREATE TABLE cdsi ( """); } } + if (oldVersion < 19) { + logger.debug("Updating database: Adding contact hidden column"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD COLUMN hidden INTEGER NOT NULL DEFAULT FALSE; + """); + } + } } } 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 dee71275bc..4b2e64032f 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 @@ -826,6 +826,7 @@ private void loadLegacyStores( contact.messageExpirationTime, contact.blocked, contact.archived, + false, false)); // Store profile keys only in profile store diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java index 2f714c6f27..55231cb7eb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java @@ -44,7 +44,8 @@ public static void migrate(File file, RecipientStore recipientStore) { r.contact.messageExpirationTime, r.contact.blocked, r.contact.archived, - r.contact.profileSharingEnabled); + r.contact.profileSharingEnabled, + false); } ProfileKey profileKey = null; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index c446e4680b..bc7475b74e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -70,6 +70,7 @@ CREATE TABLE recipient ( blocked INTEGER NOT NULL DEFAULT FALSE, archived INTEGER NOT NULL DEFAULT FALSE, profile_sharing INTEGER NOT NULL DEFAULT FALSE, + hidden INTEGER NOT NULL DEFAULT FALSE, profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0, profile_given_name TEXT, @@ -318,9 +319,9 @@ public Contact getContact(RecipientId recipientId) { public List> getContacts() { final var sql = ( """ - SELECT r._id, r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived + SELECT r._id, r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden FROM %s r - WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s + WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s AND r.hidden = FALSE """ ).formatted(TABLE_RECIPIENT, SQL_IS_CONTACT); try (final var connection = database.getConnection()) { @@ -342,6 +343,7 @@ public List getRecipients( final var sqlWhere = new ArrayList(); if (onlyContacts) { sqlWhere.add("(" + SQL_IS_CONTACT + ")"); + sqlWhere.add("r.hidden = FALSE"); } if (blocked.isPresent()) { sqlWhere.add("r.blocked = ?"); @@ -357,7 +359,7 @@ public List getRecipients( SELECT r._id, r.number, r.uuid, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, + r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities FROM %s r WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s @@ -962,7 +964,7 @@ private Set findAllByAddress( private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ - SELECT r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived + SELECT r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden FROM %s r WHERE r._id = ? AND (%s) """ @@ -1053,7 +1055,8 @@ private Contact getContactFromResultSet(ResultSet resultSet) throws SQLException resultSet.getInt("expiration_time"), resultSet.getBoolean("blocked"), resultSet.getBoolean("archived"), - resultSet.getBoolean("profile_sharing")); + resultSet.getBoolean("profile_sharing"), + resultSet.getBoolean("hidden")); } private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException { diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 3a28219a4c..4814d28a88 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -613,6 +613,9 @@ Remove the info of a given contact NUMBER:: Specify the contact phone number. +*--hide*:: +Hide the contact in the contact list, but keep the data. + *--forget*:: Delete all data associated with this contact, including identity keys and sessions. diff --git a/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java index c4a3d7d807..aca5277856 100644 --- a/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java @@ -20,7 +20,11 @@ public String getName() { public void attachToSubparser(final Subparser subparser) { subparser.help("Remove the details of a given contact"); subparser.addArgument("recipient").help("Contact number"); - subparser.addArgument("--forget") + final var mut = subparser.addMutuallyExclusiveGroup(); + mut.addArgument("--hide") + .action(Arguments.storeTrue()) + .help("Hide the contact in the contact list, but keep the data."); + mut.addArgument("--forget") .action(Arguments.storeTrue()) .help("Delete all data associated with this contact, including identity keys and sessions."); } @@ -32,8 +36,11 @@ public void handleCommand( var recipientString = ns.getString("recipient"); var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); + var hide = Boolean.TRUE == ns.getBoolean("hide"); var forget = Boolean.TRUE == ns.getBoolean("forget"); - if (forget) { + if (hide) { + m.hideRecipient(recipient); + } else if (forget) { m.deleteRecipient(recipient); } else { m.deleteContact(recipient); diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index ea09dba4a9..a3f1839379 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -452,6 +452,10 @@ public SendMessageResults sendEndSessionMessage(final Set getRecipients( } return Recipient.newBuilder() .withAddress(new RecipientAddress(null, n)) - .withContact(new Contact(contactName, null, null, 0, contactBlocked, false, false)) + .withContact(new Contact(contactName, null, null, 0, contactBlocked, false, false, false)) .build(); }).filter(Objects::nonNull).toList(); }