signal-cli/src/main/java/org/asamk/signal/commands/JsonEventLoopCommand.java

270 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package org.asamk.signal.commands;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.Signal;
import org.asamk.signal.GroupIdFormatException;
import org.asamk.signal.JsonEventLoopReceiveMessageHandler;
import org.asamk.signal.JsonReceiveMessageHandler;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.Util;
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.util.Base64;
import com.fasterxml.jackson.databind.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
public class JsonEventLoopCommand implements LocalCommand {
/**
* Handles the rows read from STDIN (parse JSON and act on the requests)
* @author bingel
*
*/
private static class JsonEventLoopRequestHandler {
Manager m;
/**
* Constructor
* @param m Signal Manager object
*/
JsonEventLoopRequestHandler( Manager m) {
this.m = m;
}
/**
* Command Handler: list_groups
* @param reqObj
* @param reqID
* @return
*/
JsonEventLoopStatusReport list_groups( JsonNode reqObj, JsonNode reqID) {
List<GroupInfo> groups = m.getGroups();
ObjectMapper mapper = new ObjectMapper();
JsonNode data_obj = mapper.valueToTree(groups);
return new JsonEventLoopStatusReport( "group_list", reqID, data_obj);
}
/**
* Command Handler: list_contacts
*/
JsonEventLoopStatusReport list_contacts( JsonNode reqObj, JsonNode reqID) {
List<ContactInfo> contacts = m.getContacts();
ObjectMapper mpr = new ObjectMapper();
JsonNode data_obj = mpr.valueToTree(contacts);
return new JsonEventLoopStatusReport( "contact_list", reqID, data_obj);
}
/**
* Command Handler: Send signal message and respond with status message
* @param reqObj JSON parsed request object
*/
JsonEventLoopStatusReport send_message( JsonNode reqObj, JsonNode reqID) {
// get body text
String body_text = null;
if(reqObj.get("messageBody") != null) {
body_text = reqObj.get("messageBody").asText();
}
// parse attachment list
List<String> attachments = new ArrayList<String>();
if(reqObj.get("attachments") != null && reqObj.get("attachments").isArray()) {
for( JsonNode node : reqObj.get("attachments")) {
attachments.add(node.asText());
}
}
if( reqObj.get("recipientNumber") != null) {
// send message directly to another user
String recipientNumber = reqObj.get("recipientNumber").asText();
try {
m.sendMessage( body_text, attachments, recipientNumber);
} catch( IOException e) {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_SEND_ERROR, "IOException: " + e.toString() );
} catch( EncapsulatedExceptions e) {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_SEND_ERROR, "EncapsulatedException: " + e.toString() );
}
} else if( reqObj.get("recipientGroupID") != null) {
// send message to group
String recipientGroupID = reqObj.get("recipientGroupID").asText();
try {
byte[] recipientGroupID_bytearray = Util.decodeGroupId(recipientGroupID);
m.sendGroupMessage( body_text, attachments, recipientGroupID_bytearray);
} catch( GroupIdFormatException e) {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_GROUP_ID_FORMAT_ERROR, "GroupIdFormatException: " + e.toString() );
} catch( IOException e) {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_SEND_ERROR, "IOException: " + e.toString() );
} catch( EncapsulatedExceptions e) {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_SEND_ERROR, "EncapsulatedException: " + e.toString() );
}
} else {
return new JsonEventLoopStatusReport( "send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_RECIPIENT_FORMAT_ERROR, "Neither recipientNumber or recipientGroupID present in request, nothing to do");
}
return new JsonEventLoopStatusReport("send_message", reqID, JsonEventLoopStatusReport.STATUSCODE_OK);
}
/**
* Handle incoming textRow from stdin, parse as JSON and dispatch request
* @param textRow String with the textRow to process
*/
void handle( String textRow) {
//System.err.println( "JsonRequestHandler: incoming string: " + textRow);
JsonEventLoopStatusReport resp = null;
boolean exitNow = false;
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode reqObj = objectMapper.readTree(textRow);
if(reqObj != null) {
if( reqObj.get("reqType") != null) {
String reqType = reqObj.get("reqType").asText();
JsonNode reqID = reqObj.get("reqID");
switch(reqType) {
case "alive":
resp = new JsonEventLoopStatusReport("alive", reqID, JsonEventLoopStatusReport.STATUSCODE_OK);
break;
case "exit":
resp = new JsonEventLoopStatusReport("exit", reqID, JsonEventLoopStatusReport.STATUSCODE_OK);
//System.exit(0);
exitNow = true;
break;
case "send_message":
resp = this.send_message( reqObj, reqID);
break;
case "list_groups":
resp = this.list_groups( reqObj, reqID);
break;
case "list_contacts":
resp = this.list_contacts( reqObj, reqID);
break;
default:
resp = new JsonEventLoopStatusReport("error", reqID, JsonEventLoopStatusReport.STATUSCODE_REQUEST_FORMAT_ERROR, "Unknown reqType '" + reqType + "'");
System.err.println("JsonEvtRequestHandler: ERROR: Unknown reqType '" + reqType + "'");
break;
}
} else {
resp = new JsonEventLoopStatusReport("error", null, JsonEventLoopStatusReport.STATUSCODE_REQUEST_FORMAT_ERROR, "reqType attribute missing");
System.err.println("JsonEvtRequestHandler: ERROR: reqType attribute is missing in request");
}
} else {
resp = new JsonEventLoopStatusReport("error", null, JsonEventLoopStatusReport.STATUSCODE_JSON_PARSE_ERROR, "Failed to parse JSON, reqObj is NULL");
System.err.println( "JsonEvtRequestHandler: Failed to parse JSON, reqObj is NULL");
}
} catch( IOException e) {
resp = new JsonEventLoopStatusReport("error", null, JsonEventLoopStatusReport.STATUSCODE_JSON_PARSE_ERROR, "Failed to parse JSON: " + e.toString());
System.err.println( "JsonEvtRequestHandler: Failed to parse JSON, text='" + textRow + "', IOException: " + e.toString());
}
// Emit response (if any - but there should always be one, otherwise something is wrong)
if(resp != null) {
resp.emit();
} else {
System.err.println( "JsonEventRequestHandler: ERROR: No response!");
}
// Someone requested we exit the program now
if(exitNow) {
System.exit(0);
}
}
}
/**
* Thread Runner to handle reading STDIN line-by-line and passing to JsonEvtRequestHandler
* @author bingel
*
*/
private static class JsonStdinReader implements Runnable {
JsonEventLoopRequestHandler jsonEventLoopRequestHandler = null;
public void run() {
BufferedReader br = new BufferedReader( new InputStreamReader( System.in));
while( true) {
String line=null;
try {
line = br.readLine();
} catch( IOException e) {
System.err.println("ERROR Reading stdin: " + e.toString() + ", exiting");
System.exit(1);
}
if( line != null && !line.equals(""))
this.jsonEventLoopRequestHandler.handle(line);
}
}
public JsonStdinReader( JsonEventLoopRequestHandler j) {
this.jsonEventLoopRequestHandler = j;
}
}
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.addArgument("--ignore-attachments")
.help("Dont download attachments of received messages.")
.action(Arguments.storeTrue());
}
@Override
public int handleCommand(final Namespace ns, final Manager m) {
if (!m.isRegistered()) {
//System.err.println("User is not registered.");
new JsonEventLoopStatusReport( "error", null, JsonEventLoopStatusReport.STATUSCODE_USER_NOT_REGISTERED_ERROR, "JsonEventLoopCommand::handleCommand: User is not registered, aborting");
return 1;
}
// Start stdin reader thread
JsonEventLoopRequestHandler jhandler = new JsonEventLoopRequestHandler( m);
JsonStdinReader reader = new JsonStdinReader( jhandler);
Thread jsonStdinReaderThread = new Thread( reader);
jsonStdinReaderThread.start();
// Emit metadata response message at startup
JsonEventLoopStatusReport meta_resp = new JsonEventLoopStatusReport("metadata", null, JsonEventLoopStatusReport.STATUSCODE_OK);
meta_resp.apiVer = JsonEventLoopStatusReport.CURRENT_APIVER;
meta_resp.attachmentsPath = m.getAttachmentsPath();
meta_resp.emit();
// start JsonReceiveMessageHandler and let it run forever
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
try {
final Manager.ReceiveMessageHandler handler = new JsonEventLoopReceiveMessageHandler(m, (resp) -> {
// For future use - utilize this lambda callback to emit responses in correct channel
// if that should not happen to be stdout, but for now, just emit json text to stdout
resp.emit();
});
m.receiveMessages((long) (3600 * 1000), TimeUnit.MILLISECONDS, false, ignoreAttachments, handler);
return 0;
} catch (IOException e) {
System.err.println("Error while receiving messages: " + e.getMessage());
new JsonEventLoopStatusReport( "error", null, JsonEventLoopStatusReport.STATUSCODE_GENERIC_ERROR, "JsonEventLoopCommand::handleCommand: Error while receiving messages: " + e.getMessage()).emit();
return 3;
} catch (AssertionError e) {
handleAssertionError(e);
return 1;
}
}
}