mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-01 20:10:39 +00:00
Merge 669801edbe
into 8717665d1d
This commit is contained in:
commit
5ff976c14d
4 changed files with 388 additions and 3 deletions
|
@ -23,6 +23,7 @@ dependencies {
|
|||
compile 'org.bouncycastle:bcprov-jdk15on:1.55'
|
||||
compile 'net.sourceforge.argparse4j:argparse4j:0.7.0'
|
||||
compile 'org.freedesktop.dbus:dbus-java:2.7.0'
|
||||
compile 'org.json:json:20160810'
|
||||
}
|
||||
|
||||
jar {
|
||||
|
|
338
src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java
Normal file
338
src/main/java/org/asamk/signal/JsonReceiveMessageHandler.java
Normal file
|
@ -0,0 +1,338 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import org.asamk.signal.util.Base64;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.*;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.*;
|
||||
import org.whispersystems.signalservice.api.messages.calls.*;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||
private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC");
|
||||
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
|
||||
final Manager m;
|
||||
final Writer logfile;
|
||||
|
||||
public JsonReceiveMessageHandler(Manager m, Writer logfile) {
|
||||
this.m = m;
|
||||
this.logfile = logfile;
|
||||
df.setTimeZone(tzUTC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
|
||||
final JSONObject obj = new JSONObject();
|
||||
|
||||
obj.put( "envelope", jsonEnvelope( envelope ) );
|
||||
|
||||
if (exception != null) {
|
||||
if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
|
||||
org.whispersystems.libsignal.UntrustedIdentityException e = (org.whispersystems.libsignal.UntrustedIdentityException) exception;
|
||||
final JSONObject err = new JSONObject();
|
||||
err.put("type", "UntrustedIdentityException");
|
||||
err.put("name", e.getName());
|
||||
obj.put("error", err);
|
||||
} else {
|
||||
final JSONObject err = new JSONObject();
|
||||
err.put("type", exception.getClass().getSimpleName());
|
||||
err.put("message", exception.getMessage() );
|
||||
obj.put("error", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (content == null) {
|
||||
if( envelope.hasContent() && exception == null ) {
|
||||
final JSONObject err = new JSONObject();
|
||||
err.put("type", exception.getClass().getSimpleName());
|
||||
err.put("message", "Failed to decrypt message");
|
||||
obj.put("error", err);
|
||||
}
|
||||
} else {
|
||||
if (content.getDataMessage().isPresent())
|
||||
obj.put( "data", jsonDataMessage( content.getDataMessage().get() ) );
|
||||
if (content.getSyncMessage().isPresent())
|
||||
obj.put( "sync", jsonSyncMessage( content.getSyncMessage().get() ) );
|
||||
if (content.getCallMessage().isPresent())
|
||||
obj.put( "call", jsonCallMessage( content.getCallMessage().get() ) );
|
||||
}
|
||||
|
||||
try {
|
||||
logfile.write(obj.toString());
|
||||
logfile.write(",\n");
|
||||
logfile.flush();
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static final JSONObject jsonEnvelope( SignalServiceEnvelope envelope ) {
|
||||
final JSONObject env = new JSONObject();
|
||||
env.put("timestamp", formatTimestamp(envelope.getTimestamp()));
|
||||
final JSONObject from = new JSONObject();
|
||||
from.put("number", envelope.getSource());
|
||||
from.put("device", envelope.getSourceDevice());
|
||||
SignalServiceAddress sourceAddress = envelope.getSourceAddress();
|
||||
if( sourceAddress.getRelay().isPresent() ) from.put("relay", envelope.getRelay() );
|
||||
env.put("from", from);
|
||||
final JSONObject type = new JSONObject();
|
||||
type.put("number", envelope.getType());
|
||||
if(envelope.isReceipt()) type.put("name", "receipt");
|
||||
if(envelope.isSignalMessage()) type.put("name", "message");
|
||||
if(envelope.isPreKeySignalMessage()) type.put("name", "prekey");
|
||||
env.put("type", type);
|
||||
if(envelope.hasLegacyMessage()) env.put("legacyMessage", true );
|
||||
return env;
|
||||
}
|
||||
|
||||
private final JSONObject jsonAttachment(SignalServiceAttachment attachment ) {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put("content_type", attachment.getContentType() );
|
||||
json.put("type", (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") );
|
||||
if (attachment.isPointer()) {
|
||||
final SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||
json.put("id", pointer.getId() );
|
||||
json.put("key", Base64.encodeBytes( pointer.getKey() ) );
|
||||
if( pointer.getRelay().isPresent() ) json.put("relay", pointer.getRelay().get() );
|
||||
if( pointer.getSize().isPresent() ) json.put("size", pointer.getSize().get());
|
||||
if( pointer.getPreview().isPresent() ) json.put("preview", Base64.encodeBytes( pointer.getPreview().get() ) );
|
||||
// Added 2017-02-25, version 2.5.2
|
||||
//if( pointer.getDigest().isPresent() ) json.put("digest", Base64.encodeBytes( pointer.getDigest().get() ) );
|
||||
final File file = m.getAttachmentFile(pointer.getId());
|
||||
if (file.exists()) json.put("file", file.toString() );
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private final JSONObject jsonGroup( SignalServiceGroup ssg ) {
|
||||
final JSONObject group = new JSONObject();
|
||||
group.put( "id", Base64.encodeBytes( ssg.getGroupId() ) );
|
||||
group.put( "type", ssg.getType() );
|
||||
if( ssg.getName().isPresent() ) group.put( "name", ssg.getName().get() );
|
||||
if( ssg.getMembers().isPresent() ) {
|
||||
final JSONArray members = new JSONArray();
|
||||
for( String member : ssg.getMembers().get()) {
|
||||
members.put(member);
|
||||
}
|
||||
group.put("members", members);
|
||||
}
|
||||
if( ssg.getAvatar().isPresent() ) {
|
||||
group.put("avatar", jsonAttachment( ssg.getAvatar().get() ) );
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
private static final JSONArray jsonListOfStrings( List<String> membersList ) {
|
||||
final JSONArray members = new JSONArray();
|
||||
for( final String member : membersList ) {
|
||||
members.put( member );
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
private final JSONObject jsonDataMessage( SignalServiceDataMessage message ) {
|
||||
final JSONObject data = new JSONObject();
|
||||
data.put("timestamp", message.getTimestamp());
|
||||
if( message.getGroupInfo().isPresent() )
|
||||
data.put( "group", jsonGroup( message.getGroupInfo().get() ) );
|
||||
if( message.getBody().isPresent() )
|
||||
data.put("body", message.getBody().get());
|
||||
if( message.isEndSession() )
|
||||
data.put("isEndSession", true);
|
||||
if( message.isExpirationUpdate() )
|
||||
data.put("isExpirationUpdate", true);
|
||||
if( message.isGroupUpdate() )
|
||||
data.put("isGroupUpdate", true );
|
||||
if( message.getExpiresInSeconds() > 0)
|
||||
data.put("expiresInSeconds", message.getExpiresInSeconds());
|
||||
if( message.getAttachments().isPresent() ) {
|
||||
final JSONArray attachments = new JSONArray();
|
||||
for (SignalServiceAttachment attachment : message.getAttachments().get()) {
|
||||
attachments.put( jsonAttachment( attachment ) );
|
||||
}
|
||||
data.put("attachments", attachments );
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private final JSONObject jsonSyncMessage( SignalServiceSyncMessage syncMessage ) {
|
||||
final JSONObject sync = new JSONObject();
|
||||
if (syncMessage.getGroups().isPresent()) {
|
||||
sync.put("groups", jsonAttachment( syncMessage.getGroups().get() ) );
|
||||
}
|
||||
if (syncMessage.getContacts().isPresent()) {
|
||||
sync.put("contacts", jsonAttachment( syncMessage.getContacts().get() ) );
|
||||
}
|
||||
if (syncMessage.getRead().isPresent()) {
|
||||
final JSONArray read = new JSONArray();
|
||||
for (ReadMessage rm : syncMessage.getRead().get()) {
|
||||
final JSONObject mesg = new JSONObject();
|
||||
mesg.put("from", rm.getSender());
|
||||
mesg.put("timestamp", rm.getTimestamp());
|
||||
read.put(mesg);
|
||||
}
|
||||
sync.put("read", read);
|
||||
}
|
||||
if (syncMessage.getRequest().isPresent()) {
|
||||
final RequestMessage requestMessage = syncMessage.getRequest().get();
|
||||
final JSONObject request = new JSONObject();
|
||||
if (requestMessage.isContactsRequest()) {
|
||||
request.put("contacts", true);
|
||||
}
|
||||
if (requestMessage.isGroupsRequest()) {
|
||||
request.put("groups", true);
|
||||
}
|
||||
if (requestMessage.isBlockedListRequest()) {
|
||||
request.put("blockedNumbers", true);
|
||||
}
|
||||
sync.put("request", request);
|
||||
}
|
||||
if (syncMessage.getSent().isPresent()) {
|
||||
final JSONObject sent = new JSONObject();
|
||||
final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get();
|
||||
sent.put("timestamp", formatTimestamp(sentTranscriptMessage.getTimestamp()));
|
||||
if (sentTranscriptMessage.getDestination().isPresent())
|
||||
sent.put("dest", sentTranscriptMessage.getDestination().get() );
|
||||
if (sentTranscriptMessage.getExpirationStartTimestamp() > 0)
|
||||
sent.put("burntime", formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp()));
|
||||
sent.put("data", jsonDataMessage( sentTranscriptMessage.getMessage() ) );
|
||||
sync.put("sent", sent);
|
||||
}
|
||||
if (syncMessage.getBlockedList().isPresent()) {
|
||||
final JSONArray blockedNumbers = new JSONArray();
|
||||
final BlockedListMessage blockedList = syncMessage.getBlockedList().get();
|
||||
for (final String number : blockedList.getNumbers()) {
|
||||
blockedNumbers.put( number );
|
||||
}
|
||||
sync.put("blockedNumbers", blockedNumbers);
|
||||
}
|
||||
return sync;
|
||||
}
|
||||
|
||||
private static final JSONObject jsonCallMessage( SignalServiceCallMessage ssCall ) {
|
||||
final JSONObject call = new JSONObject();
|
||||
if( ssCall.getOfferMessage().isPresent() ) {
|
||||
final OfferMessage offerMessage = ssCall.getOfferMessage().get();
|
||||
final JSONObject offer = new JSONObject();
|
||||
offer.put( "id", offerMessage.getId() );
|
||||
offer.put( "description", offerMessage.getDescription() );
|
||||
call.put( "offer", offer );
|
||||
}
|
||||
if( ssCall.getAnswerMessage().isPresent() ) {
|
||||
final AnswerMessage answerMessage = ssCall.getAnswerMessage().get();
|
||||
final JSONObject answer = new JSONObject();
|
||||
answer.put( "id", answerMessage.getId() );
|
||||
answer.put( "description", answerMessage.getDescription() );
|
||||
call.put( "answer", answer );
|
||||
}
|
||||
if( ssCall.getHangupMessage().isPresent() ) {
|
||||
final HangupMessage hangupMessage = ssCall.getHangupMessage().get();
|
||||
final JSONObject hangup = new JSONObject();
|
||||
hangup.put( "id", hangupMessage.getId() );
|
||||
call.put( "hangup", hangup );
|
||||
}
|
||||
if( ssCall.getBusyMessage().isPresent() ) {
|
||||
final BusyMessage busyMessage = ssCall.getBusyMessage().get();
|
||||
final JSONObject busy = new JSONObject();
|
||||
busy.put( "id", busyMessage.getId() );
|
||||
call.put( "busy", busy );
|
||||
}
|
||||
if( ssCall.getIceUpdateMessages().isPresent() ) {
|
||||
final JSONArray ices = new JSONArray();
|
||||
for( final IceUpdateMessage iceMessage : ssCall.getIceUpdateMessages().get()) {
|
||||
final JSONObject ice = new JSONObject();
|
||||
ice.put( "id", iceMessage.getId() );
|
||||
ice.put( "sdp", iceMessage.getSdp() );
|
||||
ice.put( "sdpMLineIndex", iceMessage.getSdpMLineIndex() );
|
||||
ice.put( "sdpMid", iceMessage.getSdpMid() );
|
||||
ices.put(ice);
|
||||
}
|
||||
call.put( "ices", ices );
|
||||
}
|
||||
return call;
|
||||
}
|
||||
|
||||
private static final String formatTimestamp(long timestamp) {
|
||||
final Date date = new Date(timestamp);
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
private final JSONArray jsonGroupsAttachment( SignalServiceAttachment groupsAttachment ) {
|
||||
final JSONArray groups = new JSONArray();
|
||||
File tmpFile = null;
|
||||
try {
|
||||
tmpFile = Util.createTempFile();
|
||||
final DeviceGroupsInputStream s = new DeviceGroupsInputStream(
|
||||
m.retrieveAttachmentAsStream(groupsAttachment.asPointer(), tmpFile ) );
|
||||
DeviceGroup g;
|
||||
while ((g = s.read()) != null) {
|
||||
final JSONObject group = new JSONObject();
|
||||
group.put("id", Base64.encodeBytes( g.getId() ) );
|
||||
group.put("isActive", g.isActive() );
|
||||
group.put("members", jsonListOfStrings( g.getMembers() ) );
|
||||
if(g.getName().isPresent())
|
||||
group.put("name", g.getName().get() );
|
||||
if (g.getAvatar().isPresent()) // group.put("avatar", jsonAttachment( g.getAvatar().get() ) );
|
||||
m.retrieveGroupAvatarAttachment(g.getAvatar().get(), g.getId() );
|
||||
groups.put( group );
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (tmpFile != null) {
|
||||
try {
|
||||
Files.delete(tmpFile.toPath());
|
||||
} catch (IOException e) {
|
||||
System.out.println("Failed to delete temp file “" + tmpFile + "”: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private final JSONArray jsonContactsAttachment( SignalServiceAttachment contactsAttachment ) {
|
||||
final JSONArray contacts = new JSONArray();
|
||||
File tmpFile = null;
|
||||
try {
|
||||
tmpFile = Util.createTempFile();
|
||||
final DeviceContactsInputStream s = new DeviceContactsInputStream(
|
||||
m.retrieveAttachmentAsStream(contactsAttachment.asPointer(), tmpFile));
|
||||
DeviceContact c;
|
||||
while ((c = s.read()) != null) {
|
||||
final JSONObject contact = new JSONObject();
|
||||
contact.put( "number", c.getNumber() );
|
||||
if( c.getName().isPresent() )
|
||||
contact.put( "name", c.getName().get() );
|
||||
if( c.getColor().isPresent() )
|
||||
contact.put( "color", c.getColor().get() );
|
||||
if( c.getAvatar().isPresent() ) //contact.put( "avatar", jsonAttachment( c.getAvatar().get() ) );
|
||||
m.retrieveContactAvatarAttachment(c.getAvatar().get(), c.getNumber() );
|
||||
contacts.put( contact );
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (tmpFile != null) {
|
||||
try {
|
||||
Files.delete(tmpFile.toPath());
|
||||
} catch (IOException e) {
|
||||
System.out.println("Failed to delete temp file “" + tmpFile + "”: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ import org.asamk.signal.storage.contacts.ContactInfo;
|
|||
import org.asamk.signal.storage.groups.GroupInfo;
|
||||
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
|
||||
import org.asamk.signal.util.Hex;
|
||||
import org.asamk.signal.JsonReceiveMessageHandler;
|
||||
import org.freedesktop.dbus.DBusConnection;
|
||||
import org.freedesktop.dbus.DBusSigHandler;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
|
@ -48,9 +49,11 @@ import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
|||
import org.whispersystems.signalservice.internal.util.Base64;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -375,6 +378,39 @@ public class Main {
|
|||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "json":
|
||||
{
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
double timeout = 5;
|
||||
if (ns.getDouble("timeout") != null) {
|
||||
timeout = ns.getDouble("timeout");
|
||||
}
|
||||
boolean returnOnTimeout = true;
|
||||
if (timeout < 0) {
|
||||
returnOnTimeout = false;
|
||||
timeout = 3600;
|
||||
}
|
||||
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
|
||||
try {
|
||||
Writer logfile = new FileWriter(ns.getString("logfile"),true);
|
||||
logfile.write("{\"init\":true},\n");
|
||||
logfile.flush();
|
||||
m.receiveMessages((long) (timeout * 1000), TimeUnit.MILLISECONDS, returnOnTimeout, ignoreAttachments, new JsonReceiveMessageHandler(m,logfile));
|
||||
logfile.write("{\"done\":true}]\n");
|
||||
logfile.flush();
|
||||
logfile.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error while receiving messages: " + e.getMessage());
|
||||
return 3;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "receive":
|
||||
if (dBusConn != null) {
|
||||
|
@ -823,6 +859,16 @@ public class Main {
|
|||
mutTrust.addArgument("-v", "--verified-fingerprint")
|
||||
.help("Specify the fingerprint of the key, only use this option if you have verified the fingerprint.");
|
||||
|
||||
Subparser parserJson = subparsers.addParser("json");
|
||||
parserJson.addArgument("-t", "--timeout")
|
||||
.type(double.class)
|
||||
.help("Number of seconds to wait for new messages (negative values disable timeout)");
|
||||
parserJson.addArgument("--ignore-attachments")
|
||||
.help("Don’t download attachments of received messages.")
|
||||
.action(Arguments.storeTrue());
|
||||
parserJson.addArgument("-l","--logfile")
|
||||
.help("File to store received messages in JSON format.");
|
||||
|
||||
Subparser parserReceive = subparsers.addParser("receive");
|
||||
parserReceive.addArgument("-t", "--timeout")
|
||||
.type(double.class)
|
||||
|
|
|
@ -1364,7 +1364,7 @@ class Manager implements Signal {
|
|||
return new File(avatarsPath, "contact-" + number);
|
||||
}
|
||||
|
||||
private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
|
||||
public File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
|
||||
createPrivateDirectories(avatarsPath);
|
||||
if (attachment.isPointer()) {
|
||||
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||
|
@ -1379,7 +1379,7 @@ class Manager implements Signal {
|
|||
return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
|
||||
}
|
||||
|
||||
private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
|
||||
public File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
|
||||
createPrivateDirectories(avatarsPath);
|
||||
if (attachment.isPointer()) {
|
||||
SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||
|
@ -1453,7 +1453,7 @@ class Manager implements Signal {
|
|||
return outputFile;
|
||||
}
|
||||
|
||||
private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException {
|
||||
public InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException {
|
||||
final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceUrls, username, password, deviceId, signalingKey, USER_AGENT);
|
||||
return messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue