]> nmode's Git Repositories - signal-cli/commitdiff
Store group member uuids in group store
authorAsamK <asamk@gmx.de>
Mon, 23 Mar 2020 18:08:41 +0000 (19:08 +0100)
committerAsamK <asamk@gmx.de>
Mon, 23 Mar 2020 19:51:13 +0000 (20:51 +0100)
The member list is now stored as a mixed list of strings and objects, e.g.:
"members": [ "+XXXX", { "number": "+XXXX", "uuid": "XXX-XX" } ]

src/main/java/org/asamk/Signal.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/QuitGroupCommand.java
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/storage/groups/GroupInfo.java

index 90d0275812ef89c2e47e292f99512d26e37da95d..528116b11f108d159ddd289e6c200169b9294990 100644 (file)
@@ -19,7 +19,7 @@ public interface Signal extends DBusInterface {
 
     void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException;
 
-    void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException, InvalidNumberException;
+    void sendGroupMessage(String message, List<String> attachments, byte[] groupId) throws EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, IOException;
 
     String getContactName(String number) throws InvalidNumberException;
 
index 565bacba3cf305aefd9071f8dfec582e06d131b6..0baa8744dde318acdfe8cceeda90c221e0e0378f 100644 (file)
@@ -6,19 +6,20 @@ import net.sourceforge.argparse4j.inf.Subparser;
 
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.storage.groups.GroupInfo;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.util.Base64;
 
 import java.util.List;
 
 public class ListGroupsCommand implements LocalCommand {
 
-    private static void printGroup(GroupInfo group, boolean detailed, String username) {
+    private static void printGroup(GroupInfo group, boolean detailed, SignalServiceAddress address) {
         if (detailed) {
             System.out.println(String.format("Id: %s Name: %s  Active: %s Blocked: %b Members: %s",
-                    Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked, group.members));
+                    Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked, group.getMembersE164()));
         } else {
             System.out.println(String.format("Id: %s Name: %s  Active: %s Blocked: %b",
-                    Base64.encodeBytes(group.groupId), group.name, group.members.contains(username), group.blocked));
+                    Base64.encodeBytes(group.groupId), group.name, group.isMember(address), group.blocked));
         }
     }
 
@@ -40,7 +41,7 @@ public class ListGroupsCommand implements LocalCommand {
         boolean detailed = ns.getBoolean("detailed");
 
         for (GroupInfo group : groups) {
-            printGroup(group, detailed, m.getUsername());
+            printGroup(group, detailed, m.getSelfAddress());
         }
         return 0;
     }
index 38bc79accb09babcfa76cf225cda68af1662015a..6e53cb2aad025fb5cecb9ef5a23176ff19f8fb1d 100644 (file)
@@ -9,7 +9,6 @@ import org.asamk.signal.NotAGroupMemberException;
 import org.asamk.signal.manager.Manager;
 import org.asamk.signal.util.Util;
 import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
-import org.whispersystems.signalservice.api.util.InvalidNumberException;
 
 import java.io.IOException;
 
@@ -18,7 +17,6 @@ import static org.asamk.signal.util.ErrorUtils.handleEncapsulatedExceptions;
 import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
 import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
 import static org.asamk.signal.util.ErrorUtils.handleIOException;
-import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
 import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
 
 public class QuitGroupCommand implements LocalCommand {
@@ -58,9 +56,6 @@ public class QuitGroupCommand implements LocalCommand {
         } catch (GroupIdFormatException e) {
             handleGroupIdFormatException(e);
             return 1;
-        } catch (InvalidNumberException e) {
-            handleInvalidNumberException(e);
-            return 1;
         }
     }
 }
index 8364888cbaa29b0d7e1a8af8ae3f1feee1e5d7fb..7a28f7bccf16e7c83730042b75edf111078694d1 100644 (file)
@@ -171,6 +171,10 @@ public class Manager implements Signal {
         return username;
     }
 
+    public SignalServiceAddress getSelfAddress() {
+        return account.getSelfAddress();
+    }
+
     private SignalServiceAccountManager getSignalServiceAccountManager() {
         return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, null, account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
     }
@@ -499,12 +503,10 @@ public class Manager implements Signal {
         if (g == null) {
             throw new GroupNotFoundException(groupId);
         }
-        for (String member : g.members) {
-            if (member.equals(account.getUsername())) {
-                return g;
-            }
+        if (!g.isMember(account.getSelfAddress())) {
+            throw new NotAGroupMemberException(groupId, g.name);
         }
-        throw new NotAGroupMemberException(groupId, g.name);
+        return g;
     }
 
     public List<GroupInfo> getGroups() {
@@ -514,7 +516,7 @@ public class Manager implements Signal {
     @Override
     public void sendGroupMessage(String messageText, List<String> attachments,
                                  byte[] groupId)
-            throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+            throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
         if (attachments != null) {
             messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
@@ -532,15 +534,12 @@ public class Manager implements Signal {
 
         final GroupInfo g = getGroupForSending(groupId);
 
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
     public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
                                          long targetSentTimestamp, byte[] groupId)
-            throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
+            throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
         SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .withReaction(reaction)
@@ -552,13 +551,10 @@ public class Manager implements Signal {
             messageBuilder.asGroupMessage(group);
         }
         final GroupInfo g = getGroupForSending(groupId);
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, InvalidNumberException {
+    public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
         SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
                 .withId(groupId)
                 .build();
@@ -567,18 +563,18 @@ public class Manager implements Signal {
                 .asGroupMessage(group);
 
         final GroupInfo g = getGroupForSending(groupId);
-        g.members.remove(account.getUsername());
+        g.removeMember(account.getSelfAddress());
         account.getGroupStore().updateGroup(g);
 
-        sendMessageLegacy(messageBuilder, getSignalServiceAddresses(g.members));
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
     }
 
-    private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+    private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
         GroupInfo g;
         if (groupId == null) {
             // Create new group
             g = new GroupInfo(KeyUtils.createGroupId());
-            g.members.add(account.getUsername());
+            g.addMembers(Collections.singleton(account.getSelfAddress()));
         } else {
             g = getGroupForSending(groupId);
         }
@@ -588,25 +584,26 @@ public class Manager implements Signal {
         }
 
         if (members != null) {
-            Set<String> newMembers = new HashSet<>();
-            for (String member : members) {
-                member = Utils.canonicalizeNumber(member, account.getUsername());
-                if (g.members.contains(member)) {
+            final Set<String> newE164Members = new HashSet<>();
+            for (SignalServiceAddress member : members) {
+                if (g.isMember(member) || !member.getNumber().isPresent()) {
                     continue;
                 }
-                newMembers.add(member);
-                g.members.add(member);
+                newE164Members.add(member.getNumber().get());
             }
-            final List<ContactTokenDetails> contacts = accountManager.getContacts(newMembers);
-            if (contacts.size() != newMembers.size()) {
+
+            final List<ContactTokenDetails> contacts = accountManager.getContacts(newE164Members);
+            if (contacts.size() != newE164Members.size()) {
                 // Some of the new members are not registered on Signal
                 for (ContactTokenDetails contact : contacts) {
-                    newMembers.remove(contact.getNumber());
+                    newE164Members.remove(contact.getNumber());
                 }
-                System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
+                System.err.println("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
                 System.err.println("Aborting…");
                 System.exit(1);
             }
+
+            g.addMembers(members);
         }
 
         if (avatarFile != null) {
@@ -619,10 +616,7 @@ public class Manager implements Signal {
 
         SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
 
-        final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
-        // Don't send group message to ourself
-        membersSend.remove(account.getSelfAddress());
-        sendMessageLegacy(messageBuilder, membersSend);
+        sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
         return g.groupId;
     }
 
@@ -632,7 +626,7 @@ public class Manager implements Signal {
         }
         GroupInfo g = getGroupForSending(groupId);
 
-        if (!g.members.contains(recipient.getNumber().get())) {
+        if (!g.isMember(recipient)) {
             return;
         }
 
@@ -819,9 +813,9 @@ public class Manager implements Signal {
     public List<String> getGroupMembers(byte[] groupId) {
         GroupInfo group = getGroup(groupId);
         if (group == null) {
-            return new ArrayList<>();
+            return Collections.emptyList();
         } else {
-            return new ArrayList<>(group.members);
+            return new ArrayList<>(group.getMembersE164());
         }
     }
 
@@ -839,7 +833,7 @@ public class Manager implements Signal {
         if (avatar.isEmpty()) {
             avatar = null;
         }
-        return sendUpdateGroupMessage(groupId, name, members, avatar);
+        return sendUpdateGroupMessage(groupId, name, members == null ? null : getSignalServiceAddresses(members), avatar);
     }
 
     /**
@@ -1284,7 +1278,7 @@ public class Manager implements Signal {
                             e.printStackTrace();
                         }
                     } else {
-                        group.members.remove(source.getNumber().get());
+                        group.removeMember(source);
                         account.getGroupStore().updateGroup(group);
                     }
                     break;
@@ -1559,10 +1553,10 @@ public class Manager implements Signal {
                                 }
                                 syncGroup.addMembers(g.getMembers());
                                 if (!g.isActive()) {
-                                    syncGroup.members.remove(account.getUsername());
+                                    syncGroup.removeMember(account.getSelfAddress());
                                 } else {
                                     // Add ourself to the member set as it's marked as active
-                                    syncGroup.members.add(account.getUsername());
+                                    syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
                                 }
                                 syncGroup.blocked = g.isBlocked();
                                 if (g.getColor().isPresent()) {
@@ -1778,7 +1772,7 @@ public class Manager implements Signal {
                     ThreadInfo info = account.getThreadStore().getThread(Base64.encodeBytes(record.groupId));
                     out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
                             new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
-                            record.members.contains(account.getUsername()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+                            record.isMember(account.getSelfAddress()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
                             Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived));
                 }
             }
index cb53d3af3a44ed275da71b5901417e6016138f5f..21ba910f485b80fc9773da05ca0c8fe5518d6cff 100644 (file)
@@ -2,15 +2,29 @@ package org.asamk.signal.storage.groups;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 
+import java.io.IOException;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.UUID;
 
 public class GroupInfo {
 
+    private static final ObjectMapper jsonProcessor = new ObjectMapper();
+
     @JsonProperty
     public final byte[] groupId;
 
@@ -18,7 +32,9 @@ public class GroupInfo {
     public String name;
 
     @JsonProperty
-    public Set<String> members = new HashSet<>();
+    @JsonDeserialize(using = MembersDeserializer.class)
+    @JsonSerialize(using = MembersSerializer.class)
+    public Set<SignalServiceAddress> members = new HashSet<>();
     @JsonProperty
     public String color;
     @JsonProperty(defaultValue = "false")
@@ -38,7 +54,7 @@ public class GroupInfo {
         this.groupId = groupId;
     }
 
-    public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<String> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) {
+    public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<SignalServiceAddress> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) {
         this.groupId = groupId;
         this.name = name;
         this.members.addAll(members);
@@ -56,16 +72,108 @@ public class GroupInfo {
 
     @JsonIgnore
     public Set<SignalServiceAddress> getMembers() {
-        Set<SignalServiceAddress> addresses = new HashSet<>(members.size());
-        for (String member : members) {
-            addresses.add(new SignalServiceAddress(null, member));
-        }
-        return addresses;
+        return members;
     }
 
-    public void addMembers(Collection<SignalServiceAddress> members) {
+    @JsonIgnore
+    public Set<String> getMembersE164() {
+        Set<String> membersE164 = new HashSet<>();
         for (SignalServiceAddress member : members) {
-            this.members.add(member.getNumber().get());
+            if (!member.getNumber().isPresent()) {
+                continue;
+            }
+            membersE164.add(member.getNumber().get());
+        }
+        return membersE164;
+    }
+
+    @JsonIgnore
+    public Set<SignalServiceAddress> getMembersWithout(SignalServiceAddress address) {
+        Set<SignalServiceAddress> members = new HashSet<>(this.members.size());
+        for (SignalServiceAddress member : this.members) {
+            if (!member.matches(address)) {
+                members.add(member);
+            }
+        }
+        return members;
+    }
+
+    public void addMembers(Collection<SignalServiceAddress> addresses) {
+        for (SignalServiceAddress address : addresses) {
+            removeMember(address);
+            this.members.add(address);
+        }
+    }
+
+    public void removeMember(SignalServiceAddress address) {
+        this.members.removeIf(member -> member.matches(address));
+    }
+
+    @JsonIgnore
+    public boolean isMember(SignalServiceAddress address) {
+        for (SignalServiceAddress member : this.members) {
+            if (member.matches(address)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static final class JsonSignalServiceAddress {
+
+        @JsonProperty
+        private UUID uuid;
+
+        @JsonProperty
+        private String number;
+
+        JsonSignalServiceAddress(@JsonProperty("uuid") final UUID uuid, @JsonProperty("number") final String number) {
+            this.uuid = uuid;
+            this.number = number;
+        }
+
+        JsonSignalServiceAddress(SignalServiceAddress address) {
+            this.uuid = address.getUuid().orNull();
+            this.number = address.getNumber().orNull();
+        }
+
+        SignalServiceAddress toSignalServiceAddress() {
+            return new SignalServiceAddress(uuid, number);
+        }
+    }
+
+    private static class MembersSerializer extends JsonSerializer<Set<SignalServiceAddress>> {
+
+        @Override
+        public void serialize(final Set<SignalServiceAddress> value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
+            jgen.writeStartArray(value.size());
+            for (SignalServiceAddress address : value) {
+                if (address.getUuid().isPresent()) {
+                    jgen.writeObject(new JsonSignalServiceAddress(address));
+                } else {
+                    jgen.writeString(address.getNumber().get());
+                }
+            }
+            jgen.writeEndArray();
+        }
+    }
+
+    private static class MembersDeserializer extends JsonDeserializer<Set<SignalServiceAddress>> {
+
+        @Override
+        public Set<SignalServiceAddress> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+            Set<SignalServiceAddress> addresses = new HashSet<>();
+            JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+            for (JsonNode n : node) {
+                if (n.isTextual()) {
+                    addresses.add(new SignalServiceAddress(null, n.textValue()));
+                } else {
+                    JsonSignalServiceAddress address = jsonProcessor.treeToValue(n, JsonSignalServiceAddress.class);
+                    addresses.add(address.toSignalServiceAddress());
+                }
+            }
+
+            return addresses;
         }
     }
 }