From: AsamK Date: Mon, 31 Oct 2016 19:52:06 +0000 (+0100) Subject: Implement support for sending disappearing messages X-Git-Tag: v0.5.1~8 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/82cecfff85ef29e627d4238e3237222b492d5248?ds=inline Implement support for sending disappearing messages Stores the expiration timeout received from contacts in the config file Fixes #27 --- diff --git a/src/main/java/org/asamk/signal/JsonThreadStore.java b/src/main/java/org/asamk/signal/JsonThreadStore.java new file mode 100644 index 00000000..3a8eb830 --- /dev/null +++ b/src/main/java/org/asamk/signal/JsonThreadStore.java @@ -0,0 +1,56 @@ +package org.asamk.signal; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +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 JsonThreadStore { + @JsonProperty("threads") + @JsonSerialize(using = JsonThreadStore.MapToListSerializer.class) + @JsonDeserialize(using = ThreadsDeserializer.class) + private Map threads = new HashMap<>(); + + private static final ObjectMapper jsonProcessor = new ObjectMapper(); + + void updateThread(ThreadInfo thread) { + threads.put(thread.id, thread); + } + + ThreadInfo getThread(String id) { + return threads.get(id); + } + + List getThreads() { + return new ArrayList<>(threads.values()); + } + + public static class MapToListSerializer extends JsonSerializer> { + @Override + public void serialize(final Map value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { + jgen.writeObject(value.values()); + } + } + + public static class ThreadsDeserializer extends JsonDeserializer> { + @Override + public Map deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + Map threads = new HashMap<>(); + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + for (JsonNode n : node) { + ThreadInfo t = jsonProcessor.treeToValue(n, ThreadInfo.class); + threads.put(t.id, t); + } + + return threads; + } + } +} diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index 42f5ba85..1aefc252 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -107,6 +107,7 @@ class Manager implements Signal { private SignalServiceAccountManager accountManager; private JsonGroupStore groupStore; private JsonContactsStore contactStore; + private JsonThreadStore threadStore; public Manager(String username, String settingsPath) { this.username = username; @@ -267,6 +268,13 @@ class Manager implements Signal { if (contactStore == null) { contactStore = new JsonContactsStore(); } + JsonNode threadStoreNode = rootNode.get("threadStore"); + if (threadStoreNode != null) { + threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class); + } + if (threadStore == null) { + threadStore = new JsonThreadStore(); + } } private void migrateLegacyConfigs() { @@ -305,6 +313,7 @@ class Manager implements Signal { .putPOJO("axolotlStore", signalProtocolStore) .putPOJO("groupStore", groupStore) .putPOJO("contactStore", contactStore) + .putPOJO("threadStore", threadStore) ; try { openFileChannel(); @@ -572,14 +581,17 @@ class Manager implements Signal { .build(); messageBuilder.asGroupMessage(group); } - SignalServiceDataMessage message = messageBuilder.build(); + ThreadInfo thread = threadStore.getThread(Base64.encodeBytes(groupId)); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } final GroupInfo g = getGroupForSending(groupId); // Don't send group message to ourself final List membersSend = new ArrayList<>(g.members); membersSend.remove(this.username); - sendMessage(message, membersSend); + sendMessage(messageBuilder, membersSend); } public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions { @@ -587,15 +599,14 @@ class Manager implements Signal { .withId(groupId) .build(); - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group) - .build(); + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asGroupMessage(group); final GroupInfo g = getGroupForSending(groupId); g.members.remove(this.username); groupStore.updateGroup(g); - sendMessage(message, g.members); + sendMessage(messageBuilder, g.members); } private static String join(CharSequence separator, Iterable list) { @@ -672,14 +683,13 @@ class Manager implements Signal { groupStore.updateGroup(g); - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asGroupMessage(group.build()) - .build(); + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asGroupMessage(group.build()); // Don't send group message to ourself final List membersSend = new ArrayList<>(g.members); membersSend.remove(this.username); - sendMessage(message, membersSend); + sendMessage(messageBuilder, membersSend); return g.groupId; } @@ -699,25 +709,22 @@ class Manager implements Signal { if (attachments != null) { messageBuilder.withAttachments(getSignalServiceAttachments(attachments)); } - SignalServiceDataMessage message = messageBuilder.build(); - - sendMessage(message, recipients); + sendMessage(messageBuilder, recipients); } @Override public void sendEndSessionMessage(List recipients) throws IOException, EncapsulatedExceptions { - SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() - .asEndSessionMessage() - .build(); + SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() + .asEndSessionMessage(); - sendMessage(message, recipients); + sendMessage(messageBuilder, 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); + sendSyncMessage(message); } catch (UntrustedIdentityException e) { e.printStackTrace(); } @@ -727,13 +734,13 @@ class Manager implements Signal { 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); + sendSyncMessage(message); } catch (UntrustedIdentityException e) { e.printStackTrace(); } } - private void sendMessage(SignalServiceSyncMessage message) + private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); @@ -745,24 +752,17 @@ class Manager implements Signal { } } - private void sendMessage(SignalServiceDataMessage message, Collection recipients) + private void sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection recipients) throws EncapsulatedExceptions, IOException { - Set recipientsTS = new HashSet<>(recipients.size()); - for (String recipient : recipients) { - try { - recipientsTS.add(getPushAddress(recipient)); - } catch (InvalidNumberException e) { - System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage()); - System.err.println("Aborting sending."); - save(); - return; - } - } + Set recipientsTS = getSignalServiceAddresses(recipients); + if (recipientsTS == null) return; + SignalServiceDataMessage message = null; try { SignalServiceMessageSender messageSender = new SignalServiceMessageSender(URL, TRUST_STORE, username, password, deviceId, signalProtocolStore, USER_AGENT, Optional.absent()); + message = messageBuilder.build(); if (message.getGroupInfo().isPresent()) { try { messageSender.sendMessage(new ArrayList<>(recipientsTS), message); @@ -777,6 +777,13 @@ class Manager implements Signal { List unregisteredUsers = new LinkedList<>(); List networkExceptions = new LinkedList<>(); for (SignalServiceAddress address : recipientsTS) { + ThreadInfo thread = threadStore.getThread(address.getNumber()); + if (thread != null) { + messageBuilder.withExpiration(thread.messageExpirationTime); + } else { + messageBuilder.withExpiration(0); + } + message = messageBuilder.build(); try { messageSender.sendMessage(address, message); } catch (UntrustedIdentityException e) { @@ -793,7 +800,7 @@ class Manager implements Signal { } } } finally { - if (message.isEndSession()) { + if (message != null && message.isEndSession()) { for (SignalServiceAddress recipient : recipientsTS) { handleEndSession(recipient.getNumber()); } @@ -802,6 +809,21 @@ class Manager implements Signal { } } + private Set getSignalServiceAddresses(Collection recipients) { + Set recipientsTS = new HashSet<>(recipients.size()); + for (String recipient : recipients) { + try { + recipientsTS.add(getPushAddress(recipient)); + } catch (InvalidNumberException e) { + System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage()); + System.err.println("Aborting sending."); + save(); + return null; + } + } + return recipientsTS; + } + private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws NoSessionException, LegacyMessageException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, InvalidKeyException, InvalidKeyIdException, org.whispersystems.libsignal.UntrustedIdentityException { SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), signalProtocolStore); try { @@ -821,8 +843,10 @@ class Manager implements Signal { } private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, String source, String destination) { + String threadId; if (message.getGroupInfo().isPresent()) { SignalServiceGroup groupInfo = message.getGroupInfo().get(); + threadId = Base64.encodeBytes(groupInfo.getGroupId()); switch (groupInfo.getType()) { case UPDATE: GroupInfo group; @@ -862,10 +886,27 @@ class Manager implements Signal { } break; } + } else { + if (isSync) { + threadId = destination; + } else { + threadId = source; + } } if (message.isEndSession()) { handleEndSession(isSync ? destination : source); } + if (message.isExpirationUpdate() || message.getBody().isPresent()) { + ThreadInfo thread = threadStore.getThread(threadId); + if (thread == null) { + thread = new ThreadInfo(); + thread.id = threadId; + } + if (thread.messageExpirationTime != message.getExpiresInSeconds()) { + thread.messageExpirationTime = message.getExpiresInSeconds(); + threadStore.updateThread(thread); + } + } if (message.getAttachments().isPresent()) { for (SignalServiceAttachment attachment : message.getAttachments().get()) { if (attachment.isPointer()) { @@ -1273,7 +1314,7 @@ class Manager implements Signal { .withLength(groupsFile.length()) .build(); - sendMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); + sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); } } finally { groupsFile.delete(); @@ -1302,7 +1343,7 @@ class Manager implements Signal { .withLength(contactsFile.length()) .build(); - sendMessage(SignalServiceSyncMessage.forContacts(attachmentStream)); + sendSyncMessage(SignalServiceSyncMessage.forContacts(attachmentStream)); } } finally { contactsFile.delete(); diff --git a/src/main/java/org/asamk/signal/ThreadInfo.java b/src/main/java/org/asamk/signal/ThreadInfo.java new file mode 100644 index 00000000..a664059b --- /dev/null +++ b/src/main/java/org/asamk/signal/ThreadInfo.java @@ -0,0 +1,11 @@ +package org.asamk.signal; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ThreadInfo { + @JsonProperty + public String id; + + @JsonProperty + public int messageExpirationTime; +}