mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Implement support for sending disappearing messages
Stores the expiration timeout received from contacts in the config file Fixes #27
This commit is contained in:
parent
a4e22539a3
commit
82cecfff85
3 changed files with 143 additions and 35 deletions
56
src/main/java/org/asamk/signal/JsonThreadStore.java
Normal file
56
src/main/java/org/asamk/signal/JsonThreadStore.java
Normal file
|
@ -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<String, ThreadInfo> 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<ThreadInfo> getThreads() {
|
||||
return new ArrayList<>(threads.values());
|
||||
}
|
||||
|
||||
public static class MapToListSerializer extends JsonSerializer<Map<?, ?>> {
|
||||
@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<Map<String, ThreadInfo>> {
|
||||
@Override
|
||||
public Map<String, ThreadInfo> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
|
||||
Map<String, ThreadInfo> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> 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<? extends CharSequence> 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<String> 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<String> 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.<SignalServiceMessageSender.EventListener>absent());
|
||||
|
@ -745,24 +752,17 @@ class Manager implements Signal {
|
|||
}
|
||||
}
|
||||
|
||||
private void sendMessage(SignalServiceDataMessage message, Collection<String> recipients)
|
||||
private void sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<String> recipients)
|
||||
throws EncapsulatedExceptions, IOException {
|
||||
Set<SignalServiceAddress> 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<SignalServiceAddress> 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.<SignalServiceMessageSender.EventListener>absent());
|
||||
|
||||
message = messageBuilder.build();
|
||||
if (message.getGroupInfo().isPresent()) {
|
||||
try {
|
||||
messageSender.sendMessage(new ArrayList<>(recipientsTS), message);
|
||||
|
@ -777,6 +777,13 @@ class Manager implements Signal {
|
|||
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
|
||||
List<NetworkFailureException> 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<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients) {
|
||||
Set<SignalServiceAddress> 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();
|
||||
|
|
11
src/main/java/org/asamk/signal/ThreadInfo.java
Normal file
11
src/main/java/org/asamk/signal/ThreadInfo.java
Normal file
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue