]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/Manager.java
Use System.currentTimeMillis
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
index cd0ce561a4b9ce08edc0fb91fa4281ca35f87fc5..0f75f9093806e4e2f3470252c699234736c0b305 100644 (file)
@@ -23,8 +23,11 @@ import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupIdV1;
 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupLinkState;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.GroupPermission;
 import org.asamk.signal.manager.groups.GroupUtils;
+import org.asamk.signal.manager.groups.LastGroupAdminException;
 import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.manager.helper.GroupV2Helper;
 import org.asamk.signal.manager.helper.PinHelper;
@@ -327,6 +330,17 @@ public class Manager implements Closeable {
     }
 
     public void checkAccountState() throws IOException {
+        if (account.getLastReceiveTimestamp() == 0) {
+            logger.warn("The Signal protocol expects that incoming messages are regularly received.");
+        } else {
+            var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
+            long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
+            if (days > 7) {
+                logger.warn(
+                        "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
+                        days);
+            }
+        }
         if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
             refreshPreKeys();
         }
@@ -571,7 +585,7 @@ public class Manager implements Closeable {
     ) {
         var profile = account.getProfileStore().getProfile(recipientId);
 
-        var now = new Date().getTime();
+        var now = System.currentTimeMillis();
         // Profiles are cached for 24h before retrieving them again, unless forced
         if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
             return profile;
@@ -597,7 +611,7 @@ public class Manager implements Closeable {
 
         var profileKey = account.getProfileStore().getProfileKey(recipientId);
         if (profileKey == null) {
-            profile = new Profile(new Date().getTime(),
+            profile = new Profile(System.currentTimeMillis(),
                     null,
                     null,
                     null,
@@ -638,7 +652,7 @@ public class Manager implements Closeable {
             }
         } catch (InvalidKeyException ignored) {
             logger.warn("Got invalid identity key in profile for {}",
-                    resolveSignalServiceAddress(recipientId).getLegacyIdentifier());
+                    resolveSignalServiceAddress(recipientId).getIdentifier());
         }
         return profileAndCredential;
     }
@@ -761,7 +775,9 @@ public class Manager implements Closeable {
         return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
     }
 
-    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+    public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
+            GroupId groupId, Set<String> groupAdmins
+    ) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
         SignalServiceDataMessage.Builder messageBuilder;
 
         final var g = getGroupForUpdating(groupId);
@@ -773,7 +789,18 @@ public class Manager implements Closeable {
             account.getGroupStore().updateGroup(groupInfoV1);
         } else {
             final var groupInfoV2 = (GroupInfoV2) g;
-            final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2);
+            final var currentAdmins = g.getAdminMembers();
+            final var newAdmins = getSignalServiceAddresses(groupAdmins);
+            newAdmins.removeAll(currentAdmins);
+            newAdmins.retainAll(g.getMembers());
+            if (currentAdmins.contains(getSelfRecipientId())
+                    && currentAdmins.size() == 1
+                    && g.getMembers().size() > 1
+                    && newAdmins.size() == 0) {
+                // Last admin can't leave the group, unless she's also the last member
+                throw new LastGroupAdminException(g.getGroupId(), g.getTitle());
+            }
+            final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
             groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
             messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
             account.getGroupStore().updateGroup(groupInfoV2);
@@ -831,31 +858,69 @@ public class Manager implements Closeable {
             String description,
             List<String> members,
             List<String> removeMembers,
-            File avatarFile
+            List<String> admins,
+            List<String> removeAdmins,
+            boolean resetGroupLink,
+            GroupLinkState groupLinkState,
+            GroupPermission addMemberPermission,
+            GroupPermission editDetailsPermission,
+            File avatarFile,
+            Integer expirationTimer
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
         return updateGroup(groupId,
                 name,
                 description,
                 members == null ? null : getSignalServiceAddresses(members),
                 removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
-                avatarFile);
+                admins == null ? null : getSignalServiceAddresses(admins),
+                removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
+                resetGroupLink,
+                groupLinkState,
+                addMemberPermission,
+                editDetailsPermission,
+                avatarFile,
+                expirationTimer);
     }
 
     private Pair<Long, List<SendMessageResult>> updateGroup(
-            GroupId groupId,
-            String name,
-            String description,
-            Set<RecipientId> members,
+            final GroupId groupId,
+            final String name,
+            final String description,
+            final Set<RecipientId> members,
             final Set<RecipientId> removeMembers,
-            File avatarFile
+            final Set<RecipientId> admins,
+            final Set<RecipientId> removeAdmins,
+            final boolean resetGroupLink,
+            final GroupLinkState groupLinkState,
+            final GroupPermission addMemberPermission,
+            final GroupPermission editDetailsPermission,
+            final File avatarFile,
+            final Integer expirationTimer
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
         var group = getGroupForUpdating(groupId);
 
         if (group instanceof GroupInfoV2) {
-            return updateGroupV2((GroupInfoV2) group, name, description, members, removeMembers, avatarFile);
+            return updateGroupV2((GroupInfoV2) group,
+                    name,
+                    description,
+                    members,
+                    removeMembers,
+                    admins,
+                    removeAdmins,
+                    resetGroupLink,
+                    groupLinkState,
+                    addMemberPermission,
+                    editDetailsPermission,
+                    avatarFile,
+                    expirationTimer);
+        }
+
+        final var gv1 = (GroupInfoV1) group;
+        final var result = updateGroupV1(gv1, name, members, avatarFile);
+        if (expirationTimer != null) {
+            setExpirationTimer(gv1, expirationTimer);
         }
-
-        return updateGroupV1((GroupInfoV1) group, name, members, avatarFile);
+        return result;
     }
 
     private Pair<Long, List<SendMessageResult>> updateGroupV1(
@@ -913,7 +978,14 @@ public class Manager implements Closeable {
             final String description,
             final Set<RecipientId> members,
             final Set<RecipientId> removeMembers,
-            final File avatarFile
+            final Set<RecipientId> admins,
+            final Set<RecipientId> removeAdmins,
+            final boolean resetGroupLink,
+            final GroupLinkState groupLinkState,
+            final GroupPermission addMemberPermission,
+            final GroupPermission editDetailsPermission,
+            final File avatarFile,
+            Integer expirationTimer
     ) throws IOException {
         Pair<Long, List<SendMessageResult>> result = null;
         if (group.isPendingMember(account.getSelfRecipientId())) {
@@ -947,7 +1019,59 @@ public class Manager implements Closeable {
             }
         }
 
-        if (result == null || name != null || description != null || avatarFile != null) {
+        if (admins != null) {
+            final var newAdmins = new HashSet<>(admins);
+            newAdmins.retainAll(group.getMembers());
+            newAdmins.removeAll(group.getAdminMembers());
+            if (newAdmins.size() > 0) {
+                for (var admin : newAdmins) {
+                    var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
+                    result = sendUpdateGroupV2Message(group,
+                            groupGroupChangePair.first(),
+                            groupGroupChangePair.second());
+                }
+            }
+        }
+
+        if (removeAdmins != null) {
+            final var existingRemoveAdmins = new HashSet<>(removeAdmins);
+            existingRemoveAdmins.retainAll(group.getAdminMembers());
+            if (existingRemoveAdmins.size() > 0) {
+                for (var admin : existingRemoveAdmins) {
+                    var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
+                    result = sendUpdateGroupV2Message(group,
+                            groupGroupChangePair.first(),
+                            groupGroupChangePair.second());
+                }
+            }
+        }
+
+        if (resetGroupLink) {
+            var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
+            result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+        }
+
+        if (groupLinkState != null) {
+            var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState);
+            result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+        }
+
+        if (addMemberPermission != null) {
+            var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission);
+            result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+        }
+
+        if (editDetailsPermission != null) {
+            var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission);
+            result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+        }
+
+        if (expirationTimer != null) {
+            var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer);
+            result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+        }
+
+        if (name != null || description != null || avatarFile != null) {
             var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
             if (avatarFile != null) {
                 avatarStore.storeGroupAvatar(group.getGroupId(),
@@ -1243,15 +1367,17 @@ public class Manager implements Closeable {
     /**
      * Change the expiration timer for a group
      */
-    public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
-        var g = getGroup(groupId);
-        if (g instanceof GroupInfoV1) {
-            var groupInfoV1 = (GroupInfoV1) g;
-            groupInfoV1.messageExpirationTime = messageExpirationTimer;
-            account.getGroupStore().updateGroup(groupInfoV1);
-        } else {
-            throw new RuntimeException("TODO Not implemented!");
-        }
+    private void setExpirationTimer(
+            GroupInfoV1 groupInfoV1, int messageExpirationTimer
+    ) throws NotAGroupMemberException, GroupNotFoundException, IOException {
+        groupInfoV1.messageExpirationTime = messageExpirationTimer;
+        account.getGroupStore().updateGroup(groupInfoV1);
+        sendExpirationTimerUpdate(groupInfoV1.getGroupId());
+    }
+
+    private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
+        final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
+        sendGroupMessage(messageBuilder, groupId);
     }
 
     /**
@@ -1867,6 +1993,7 @@ public class Manager implements Closeable {
             SignalServiceContent content = null;
             Exception exception = null;
             final CachedMessage[] cachedMessage = {null};
+            account.setLastReceiveTimestamp(System.currentTimeMillis());
             try {
                 var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
                     final var recipientId = envelope1.hasSource()