From: AsamK Date: Sat, 16 Apr 2016 09:50:52 +0000 (+0200) Subject: Implement requesting/sending groups when linking device X-Git-Tag: v0.4.0~21 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/0ad42a72ab64e663733eb7ab89b6de5ec9f80a4a?ds=sidebyside Implement requesting/sending groups when linking device --- diff --git a/src/main/java/org/asamk/signal/GroupInfo.java b/src/main/java/org/asamk/signal/GroupInfo.java index 4ad7003e..cf5d0158 100644 --- a/src/main/java/org/asamk/signal/GroupInfo.java +++ b/src/main/java/org/asamk/signal/GroupInfo.java @@ -19,6 +19,9 @@ public class GroupInfo { @JsonProperty public long avatarId; + @JsonProperty + public boolean active; + public GroupInfo(byte[] groupId) { this.groupId = groupId; } diff --git a/src/main/java/org/asamk/signal/JsonGroupStore.java b/src/main/java/org/asamk/signal/JsonGroupStore.java index a75c5148..29dc0031 100644 --- a/src/main/java/org/asamk/signal/JsonGroupStore.java +++ b/src/main/java/org/asamk/signal/JsonGroupStore.java @@ -8,7 +8,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class JsonGroupStore { @@ -31,6 +33,10 @@ public class JsonGroupStore { return g; } + List getGroups() { + return new ArrayList<>(groups.values()); + } + public static class MapToListSerializer extends JsonSerializer> { @Override public void serialize(final Map value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index f2c83dfc..9ffcb660 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -622,6 +622,7 @@ public class Main { handleSignalServiceDataMessage(message, group); } if (content.getSyncMessage().isPresent()) { + System.out.println("Received a sync message"); SignalServiceSyncMessage syncMessage = content.getSyncMessage().get(); if (syncMessage.getContacts().isPresent()) { diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index eb883bb8..5bff3cf3 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -39,11 +39,10 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessagePipe; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; -import org.whispersystems.signalservice.api.crypto.*; +import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.*; -import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; -import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; +import org.whispersystems.signalservice.api.messages.multidevice.*; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; @@ -250,6 +249,10 @@ class Manager implements Signal { registered = true; refreshPreKeys(); + + requestSyncGroups(); + requestSyncContacts(); + save(); } @@ -384,7 +387,7 @@ class Manager implements Signal { return SignalServiceAttachments; } - private static SignalServiceAttachmentStream createAttachment(String attachment) throws IOException { + private static SignalServiceAttachment createAttachment(String attachment) throws IOException { File attachmentFile = new File(attachment); InputStream attachmentStream = new FileInputStream(attachmentFile); final long attachmentSize = attachmentFile.length(); @@ -504,6 +507,37 @@ class Manager implements Signal { sendMessage(message, recipients); } + private void requestSyncGroups() throws IOException { + SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build(); + SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); + try { + sendMessage(message); + } catch (EncapsulatedExceptions encapsulatedExceptions) { + encapsulatedExceptions.printStackTrace(); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); + } + } + + private void requestSyncContacts() throws IOException { + SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build(); + SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); + try { + sendMessage(message); + } catch (EncapsulatedExceptions encapsulatedExceptions) { + encapsulatedExceptions.printStackTrace(); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); + } + } + + private void sendMessage(SignalServiceSyncMessage message) + throws IOException, EncapsulatedExceptions, UntrustedIdentityException { + SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, + deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + messageSender.sendMessage(message); + } + private void sendMessage(SignalServiceDataMessage message, Collection recipients) throws IOException, EncapsulatedExceptions, UntrustedIdentityException { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, @@ -575,6 +609,7 @@ class Manager implements Signal { long avatarId = avatar.asPointer().getId(); try { retrieveAttachment(avatar.asPointer()); + // TODO store group avatar in /avatar/groups folder group.avatarId = avatarId; } catch (IOException | InvalidMessageException e) { System.err.println("Failed to retrieve group avatar (" + avatarId + "): " + e.getMessage()); @@ -651,10 +686,68 @@ class Manager implements Signal { group = handleSignalServiceDataMessage(message, true, envelope.getSource(), syncMessage.getSent().get().getDestination().get()); } if (syncMessage.getRequest().isPresent()) { - // TODO + RequestMessage rm = syncMessage.getRequest().get(); + if (rm.isContactsRequest()) { + // TODO implement when we have contacts + } + if (rm.isGroupsRequest()) { + try { + sendGroups(); + } catch (EncapsulatedExceptions encapsulatedExceptions) { + encapsulatedExceptions.printStackTrace(); + } catch (UntrustedIdentityException e) { + e.printStackTrace(); + } + } } if (syncMessage.getGroups().isPresent()) { - // TODO + try { + DeviceGroupsInputStream s = new DeviceGroupsInputStream(retrieveAttachmentAsStream(syncMessage.getGroups().get().asPointer())); + DeviceGroup g; + while ((g = s.read()) != null) { + GroupInfo syncGroup; + try { + syncGroup = groupStore.getGroup(g.getId()); + } catch (GroupNotFoundException e) { + syncGroup = new GroupInfo(g.getId()); + } + if (g.getName().isPresent()) { + syncGroup.name = g.getName().get(); + } + syncGroup.members.addAll(g.getMembers()); + syncGroup.active = g.isActive(); + + if (g.getAvatar().isPresent()) { + byte[] ava = new byte[(int) g.getAvatar().get().getLength()]; + org.whispersystems.signalservice.internal.util.Util.readFully(g.getAvatar().get().getInputStream(), ava); + // TODO store group avatar in /avatar/groups folder + } + groupStore.updateGroup(syncGroup); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + if (syncMessage.getContacts().isPresent()) { + try { + DeviceContactsInputStream s = new DeviceContactsInputStream(retrieveAttachmentAsStream(syncMessage.getContacts().get().asPointer())); + DeviceContact c; + while ((c = s.read()) != null) { + // TODO implement when we have contact storage + if (c.getName().isPresent()) { + c.getName().get(); + } + c.getNumber(); + + if (c.getAvatar().isPresent()) { + byte[] ava = new byte[(int) c.getAvatar().get().getLength()]; + org.whispersystems.signalservice.internal.util.Util.readFully(c.getAvatar().get().getInputStream(), ava); + // TODO store contact avatar in /avatar/contacts folder + } + } + } catch (Exception e) { + e.printStackTrace(); + } } } } @@ -725,6 +818,14 @@ class Manager implements Signal { return outputFile; } + private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException { + final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(URL, TRUST_STORE, username, password, deviceId, signalingKey, USER_AGENT); + File file = File.createTempFile("ts_tmp", "tmp"); + file.deleteOnExit(); + + return messageReceiver.retrieveAttachment(pointer, file); + } + private String canonicalizeNumber(String number) throws InvalidNumberException { String localNumber = username; return PhoneNumberFormatter.formatNumber(number, localNumber); @@ -739,4 +840,34 @@ class Manager implements Signal { public boolean isRemote() { return false; } + + private void sendGroups() throws IOException, EncapsulatedExceptions, UntrustedIdentityException { + File contactsFile = File.createTempFile("multidevice-contact-update", ".tmp"); + + try { + DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(new FileOutputStream(contactsFile)); + try { + for (GroupInfo record : groupStore.getGroups()) { + out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name), + new ArrayList<>(record.members), Optional.of(new SignalServiceAttachmentStream(new FileInputStream("/home/sebastian/Bilder/00026_150512_14-00-18.JPG"), "octet", new File("/home/sebastian/Bilder/00026_150512_14-00-18.JPG").length(), null)), + record.active)); + } + } finally { + out.close(); + } + + if (contactsFile.exists() && contactsFile.length() > 0) { + FileInputStream contactsFileStream = new FileInputStream(contactsFile); + SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder() + .withStream(contactsFileStream) + .withContentType("application/octet-stream") + .withLength(contactsFile.length()) + .build(); + + sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + } + } finally { + if (contactsFile != null) contactsFile.delete(); + } + } }