mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-02 12:30:39 +00:00
Accept JSON formatted command via sockets
- Start ServerSocket and listen for clients - Accept multiple JSON objects as commands * Send messages (single recipient / group) * Receive messages (minimal support) * Send reactions - Support attachments embedded in JSON (base64 encoded)
This commit is contained in:
parent
c3c1802b4d
commit
4f3e3f9a24
24 changed files with 1130 additions and 4 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,3 +11,5 @@ local.properties
|
||||||
.settings/
|
.settings/
|
||||||
out/
|
out/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
bin/
|
||||||
|
config/
|
159
json_socket.md
Normal file
159
json_socket.md
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
# TCP daemon mode
|
||||||
|
|
||||||
|
signal-cli can run in daemon mode and listen to incoming TCP connections.
|
||||||
|
Once a client connected, the daemon is accepting commands wrapped in JSON objects.
|
||||||
|
Each requested command results in a corresponding response.
|
||||||
|
|
||||||
|
Multiple commands can be send using the same TCP connection. Invalid commands -
|
||||||
|
e.g. invalid JSON syntax - will result in error responses, but the connection
|
||||||
|
will not be terminated.
|
||||||
|
|
||||||
|
## send message
|
||||||
|
|
||||||
|
Sending a message to one recipient or one group. Attachments can be attached as
|
||||||
|
as base64 encoded string.
|
||||||
|
`recipient` and `groupId` must not be provided at the same time.
|
||||||
|
|
||||||
|
### Request `send_message`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "send_message",
|
||||||
|
"reqId": "[request ID (optional)]",
|
||||||
|
|
||||||
|
"recipient": "[phone number]",
|
||||||
|
"groupId": "[base64 group ID]",
|
||||||
|
|
||||||
|
"dataMessage": {
|
||||||
|
"message": "[text message (optional)]",
|
||||||
|
"attachments": [{
|
||||||
|
"base64Data": "[base64 encoded data]",
|
||||||
|
"filename": "[filename (optional)]"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response `send_message`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "send_message",
|
||||||
|
"reqId": "[referencing request ID]",
|
||||||
|
|
||||||
|
"statusCode": 0,
|
||||||
|
"timestamp": 1234567
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## send reaction
|
||||||
|
|
||||||
|
Reacting to an existing message.
|
||||||
|
`recipient` and `groupId` must not be provided at the same time.
|
||||||
|
|
||||||
|
### Request `send_reaction`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "send_reaction",
|
||||||
|
"reqId": "[request ID (optional)]",
|
||||||
|
|
||||||
|
"recipient": "[phone number]",
|
||||||
|
"groupId": "[base64 group ID]",
|
||||||
|
|
||||||
|
"reaction": {
|
||||||
|
"emoji": "😀",
|
||||||
|
"author": "[phone number of original message]",
|
||||||
|
"remove": false,
|
||||||
|
"timestamp": 1234567
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response `send_reaction`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "send_reaction",
|
||||||
|
"reqId": "[referencing request ID]",
|
||||||
|
|
||||||
|
"statusCode": 0,
|
||||||
|
"timestamp": 1234567
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## receive messages
|
||||||
|
|
||||||
|
Receiving incoming messages. Command waits for `timeout` milliseconds for new messages. Default `1000`.
|
||||||
|
|
||||||
|
Attachments can be omitted in the command response with `ignoreAttachments`. Default `false`.
|
||||||
|
|
||||||
|
### Request `receive_messages`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "receive_messages",
|
||||||
|
"reqId": "[request ID (optional)]",
|
||||||
|
|
||||||
|
"timeout": 1000,
|
||||||
|
"ignoreAttachments": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response `receive_messages`
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "receive_messages",
|
||||||
|
"reqId": "[referencing request ID]",
|
||||||
|
|
||||||
|
"statusCode": 0,
|
||||||
|
"messages": [{
|
||||||
|
"timestamp": 1234567,
|
||||||
|
"sender": "[senders phone number]",
|
||||||
|
"body": "[text message]",
|
||||||
|
"attachments": [{
|
||||||
|
"base64Data": "[base64 encoded data]",
|
||||||
|
"filename": "[filename (optional)]"
|
||||||
|
}]
|
||||||
|
},{
|
||||||
|
"statusCode": -1,
|
||||||
|
"errorMessage": "[message parsing error]"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error response
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"command": "[requested command]",
|
||||||
|
"reqId": "[referencing request ID]",
|
||||||
|
|
||||||
|
"statusCode": -1,
|
||||||
|
"errorMessage": "[additional information]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Example
|
||||||
|
Running the client
|
||||||
|
```
|
||||||
|
signal-cli --username +436XXYYYZZZZ socket
|
||||||
|
```
|
||||||
|
|
||||||
|
Sending command with e.g. `socat`
|
||||||
|
```
|
||||||
|
echo "{command:'receive_messages'}" | socat -t 2 TCP:localhost:6789
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Status codes
|
||||||
|
|
||||||
|
| Code | Value | Description |
|
||||||
|
| ----:| ----------------------- | ----------------------------------------------------------- |
|
||||||
|
| -1 | UNKNOWN | Unknown error. see `errorMessage` |
|
||||||
|
| 0 | SUCCESS | Command successfully executed |
|
||||||
|
| 1 | UNKNOWN_COMMAND | Unknown or not implemented command |
|
||||||
|
| 2 | INVALID_NUMBER | Invalid `recipient` number |
|
||||||
|
| 3 | INVALID_ATTACHMENT | Error while parsing attachment |
|
||||||
|
| 4 | INVALID_JSON | Invalid JSON received |
|
||||||
|
| 5 | INVALID_RECIPIENT | None or both of `recipient` and `groupId` are set |
|
||||||
|
| 6 | GROUP_NOT_FOUND | Invalid `groupId` provided |
|
||||||
|
| 7 | NOT_A_GROUP_MEMBER | User isn't a member of provided `groupId` |
|
||||||
|
| 8 | MESSAGE_ERROR | Received error while fetching messages |
|
||||||
|
| 9 | MISSING_MESSAGE_CONTENT | Missing message content. Can't parse message |
|
||||||
|
| 10 | MESSAGE_PARSING_ERROR | General error while parsing the message. see `errorMessage` |
|
||||||
|
| 11 | MISSING_REACTION | Incomplete reaction request |
|
|
@ -368,6 +368,16 @@ Use DBus system bus instead of user bus.
|
||||||
*--ignore-attachments*::
|
*--ignore-attachments*::
|
||||||
Don’t download attachments of received messages.
|
Don’t download attachments of received messages.
|
||||||
|
|
||||||
|
=== TCP daemon
|
||||||
|
|
||||||
|
Run the signal-cli in daemon mode accepting commands in JSON format via a TCP socket.
|
||||||
|
Detailed information about the JSON format can be found here <https://github.com/AsamK/signal-cli/json_socket.md>
|
||||||
|
|
||||||
|
*-p*, *--port*::
|
||||||
|
Specify the listening port. Default is `6789`
|
||||||
|
*-a*, *--address*::
|
||||||
|
Bind socket to specified IP address. Default is `127.0.0.1`
|
||||||
|
|
||||||
== Examples
|
== Examples
|
||||||
|
|
||||||
Register a number (with SMS verification)::
|
Register a number (with SMS verification)::
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class Commands {
|
||||||
addCommand("updateProfile", new UpdateProfileCommand());
|
addCommand("updateProfile", new UpdateProfileCommand());
|
||||||
addCommand("verify", new VerifyCommand());
|
addCommand("verify", new VerifyCommand());
|
||||||
addCommand("uploadStickerPack", new UploadStickerPackCommand());
|
addCommand("uploadStickerPack", new UploadStickerPackCommand());
|
||||||
|
addCommand("socket", new SocketCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, Command> getCommands() {
|
public static Map<String, Command> getCommands() {
|
||||||
|
|
71
src/main/java/org/asamk/signal/commands/SocketCommand.java
Normal file
71
src/main/java/org/asamk/signal/commands/SocketCommand.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.socket.JsonSocketHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
public class SocketCommand implements LocalCommand {
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(SocketCommand.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
|
subparser.addArgument("-p", "--port").type(Integer.class).setDefault(6789).help("Port to bind");
|
||||||
|
subparser.addArgument("-a", "--address").setDefault("127.0.0.1").help("Address to bind");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleCommand(final Namespace ns, final Manager m) {
|
||||||
|
final Integer port = ns.getInt("port");
|
||||||
|
InetAddress address = null;
|
||||||
|
final String addressParam = ns.getString("address");
|
||||||
|
try {
|
||||||
|
address = InetAddress.getByName(addressParam);
|
||||||
|
} catch (final UnknownHostException e1) {
|
||||||
|
logger.error("Invalid bind address: %s\n", addressParam);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
try (ServerSocket serverSocket = new ServerSocket(port, 0, address)) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
final Socket socket = serverSocket.accept();
|
||||||
|
final InetSocketAddress remote = (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||||
|
logger.debug("Client connected from {}:{}", remote.getHostName(), remote.getPort());
|
||||||
|
new Thread(new JsonSocketHandler(m, socket)).start();
|
||||||
|
} catch (final IOException ioe) {
|
||||||
|
logger.error("Client connection failed with '{}'", ioe.getMessage(), ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Cannot open socket ({}:{}): {}", addressParam, port, e.getMessage());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -484,7 +484,7 @@ public class Manager implements Closeable {
|
||||||
return unidentifiedMessagePipe;
|
return unidentifiedMessagePipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SignalServiceMessageSender createMessageSender() {
|
public SignalServiceMessageSender createMessageSender() {
|
||||||
final ExecutorService executor = null;
|
final ExecutorService executor = null;
|
||||||
return new SignalServiceMessageSender(serviceConfiguration,
|
return new SignalServiceMessageSender(serviceConfiguration,
|
||||||
account.getUuid(),
|
account.getUuid(),
|
||||||
|
@ -1208,7 +1208,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
|
public Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
|
||||||
final Set<SignalServiceAddress> signalServiceAddresses = new HashSet<>(numbers.size());
|
final Set<SignalServiceAddress> signalServiceAddresses = new HashSet<>(numbers.size());
|
||||||
final Set<SignalServiceAddress> addressesMissingUuid = new HashSet<>();
|
final Set<SignalServiceAddress> addressesMissingUuid = new HashSet<>();
|
||||||
|
|
||||||
|
@ -1255,7 +1255,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Long, List<SendMessageResult>> sendMessage(
|
public Pair<Long, List<SendMessageResult>> sendMessage(
|
||||||
SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients
|
SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
recipients = recipients.stream().map(this::resolveSignalServiceAddress).collect(Collectors.toSet());
|
recipients = recipients.stream().map(this::resolveSignalServiceAddress).collect(Collectors.toSet());
|
||||||
|
@ -2186,7 +2186,7 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream retrieveAttachmentAsStream(
|
public InputStream retrieveAttachmentAsStream(
|
||||||
SignalServiceAttachmentPointer pointer, File tmpFile
|
SignalServiceAttachmentPointer pointer, File tmpFile
|
||||||
) throws IOException, InvalidMessageException, MissingConfigurationException {
|
) throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||||
return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
|
return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.asamk.signal.socket.commands.AbstractCommand;
|
||||||
|
import org.asamk.signal.socket.commands.JsonReceiveMessagesCommand;
|
||||||
|
import org.asamk.signal.socket.commands.JsonSendMessageCommand;
|
||||||
|
import org.asamk.signal.socket.commands.JsonSendReactionCommand;
|
||||||
|
import org.asamk.signal.socket.commands.JsonUnknownCommand;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
public class JsonCommandDeserializer extends JsonDeserializer<AbstractCommand> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractCommand deserialize(final JsonParser jp, final DeserializationContext ctxt)
|
||||||
|
throws IOException, JsonProcessingException {
|
||||||
|
final ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||||
|
final ObjectNode root = mapper.readTree(jp);
|
||||||
|
final Optional<String> command = Optional.ofNullable(root).map(r -> r.get("command")).map(JsonNode::asText);
|
||||||
|
if (command.isEmpty()) {
|
||||||
|
return new JsonUnknownCommand();
|
||||||
|
}
|
||||||
|
Class<? extends AbstractCommand> instanceClass;
|
||||||
|
switch (command.get()) {
|
||||||
|
case "send_message":
|
||||||
|
instanceClass = JsonSendMessageCommand.class;
|
||||||
|
break;
|
||||||
|
case "receive_messages":
|
||||||
|
instanceClass = JsonReceiveMessagesCommand.class;
|
||||||
|
break;
|
||||||
|
case "send_reaction":
|
||||||
|
instanceClass = JsonSendReactionCommand.class;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
instanceClass = JsonUnknownCommand.class;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return mapper.treeToValue(root, instanceClass);
|
||||||
|
}
|
||||||
|
}
|
104
src/main/java/org/asamk/signal/socket/JsonSocketHandler.java
Normal file
104
src/main/java/org/asamk/signal/socket/JsonSocketHandler.java
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.socket.commands.AbstractCommand;
|
||||||
|
import org.asamk.signal.socket.json.JsonErrorResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse.StatusCode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser.Feature;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.core.io.JsonEOFException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.MappingJsonFactory;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
public class JsonSocketHandler implements Runnable {
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(JsonSocketHandler.class);
|
||||||
|
|
||||||
|
private final Manager manager;
|
||||||
|
private final Socket socket;
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private final OutputStream outputStream;
|
||||||
|
private final JsonParser parser;
|
||||||
|
private final JsonGenerator writer;
|
||||||
|
|
||||||
|
public JsonSocketHandler(final Manager manager, final Socket socket) throws IOException {
|
||||||
|
this.manager = manager;
|
||||||
|
this.socket = socket;
|
||||||
|
inputStream = socket.getInputStream();
|
||||||
|
outputStream = socket.getOutputStream();
|
||||||
|
final JsonFactory factory = new MappingJsonFactory();
|
||||||
|
final ObjectMapper objectMapper = new ObjectMapper(factory).enable(Feature.AUTO_CLOSE_SOURCE)
|
||||||
|
.enable(Feature.ALLOW_SINGLE_QUOTES).enable(Feature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||||
|
.enable(Feature.ALLOW_TRAILING_COMMA).enable(Feature.ALLOW_UNQUOTED_FIELD_NAMES)
|
||||||
|
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).disable(Feature.AUTO_CLOSE_SOURCE);
|
||||||
|
factory.setCodec(objectMapper);
|
||||||
|
parser = factory.createParser(inputStream);
|
||||||
|
writer = factory.createGenerator(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
final JsonToken curToken = parser.nextToken();
|
||||||
|
if (curToken == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (curToken) {
|
||||||
|
case START_OBJECT:
|
||||||
|
final AbstractCommand command = parser.readValueAs(AbstractCommand.class);
|
||||||
|
final JsonResponse result = command.apply(manager);
|
||||||
|
writer.writeObject(result);
|
||||||
|
break;
|
||||||
|
case START_ARRAY:
|
||||||
|
break;
|
||||||
|
case END_ARRAY:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
writer.writeObject(new JsonErrorResponse(null, StatusCode.INVALID_JSON, curToken.asString()));
|
||||||
|
}
|
||||||
|
} catch (final JsonEOFException eof) {
|
||||||
|
break;
|
||||||
|
} catch (final JsonParseException e) {
|
||||||
|
writer.writeObject(new JsonErrorResponse(null, StatusCode.INVALID_JSON, e.getMessage()));
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
writer.flush();
|
||||||
|
socket.close();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.error("Connection failed with '{}'", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.socket.JsonCommandDeserializer;
|
||||||
|
import org.asamk.signal.socket.json.JsonEnvelope;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize(using = JsonCommandDeserializer.class)
|
||||||
|
public abstract class AbstractCommand extends JsonEnvelope implements Function<Manager, JsonResponse> {
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
public abstract class AbstractSendCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
protected String recipient;
|
||||||
|
protected String groupId;
|
||||||
|
|
||||||
|
public AbstractSendCommand() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecipient(final String recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRecipient() {
|
||||||
|
return recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(final String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.attribute.FileAttribute;
|
||||||
|
import java.nio.file.attribute.PosixFilePermissions;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
|
import org.asamk.signal.manager.groups.GroupUtils;
|
||||||
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
|
import org.asamk.signal.socket.json.JsonErrorResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonMessageAttachment;
|
||||||
|
import org.asamk.signal.socket.json.JsonReceivedMessage;
|
||||||
|
import org.asamk.signal.socket.json.JsonReceivedMessageResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse.StatusCode;
|
||||||
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize(as = JsonReceiveMessagesCommand.class)
|
||||||
|
public class JsonReceiveMessagesCommand extends AbstractCommand {
|
||||||
|
private long timeout = 1000;
|
||||||
|
private boolean ignoreAttachments = false;
|
||||||
|
|
||||||
|
private final FileAttribute<?> tempFileAttributes = PosixFilePermissions
|
||||||
|
.asFileAttribute(PosixFilePermissions.fromString("rw-------"));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonResponse apply(final Manager manager) {
|
||||||
|
final JsonReceivedMessageResponse result = new JsonReceivedMessageResponse(this);
|
||||||
|
try {
|
||||||
|
manager.receiveMessages(getTimeout(), TimeUnit.MILLISECONDS, true, false,
|
||||||
|
(final SignalServiceEnvelope envelope, final SignalServiceContent content, final Throwable e) -> {
|
||||||
|
if (content != null) {
|
||||||
|
try {
|
||||||
|
final JsonReceivedMessage msg = new JsonReceivedMessage(content.getTimestamp(),
|
||||||
|
content.getSender().getNumber().orNull());
|
||||||
|
if (content.getDataMessage().isPresent()) {
|
||||||
|
final SignalServiceDataMessage message = content.getDataMessage().get();
|
||||||
|
msg.withBody(message.getBody().orNull());
|
||||||
|
|
||||||
|
Optional.ofNullable(message.getGroupContext().orNull()).map(GroupUtils::getGroupId)
|
||||||
|
.map(GroupId::toBase64).ifPresent(msg::withGroupId);
|
||||||
|
|
||||||
|
if (!isIgnoreAttachments() && message.getAttachments().isPresent()) {
|
||||||
|
final List<SignalServiceAttachment> attachments = message.getAttachments()
|
||||||
|
.get();
|
||||||
|
for (final SignalServiceAttachment a : attachments) {
|
||||||
|
final JsonMessageAttachment jsonAttachment = new JsonMessageAttachment();
|
||||||
|
if (a.isPointer()) {
|
||||||
|
final SignalServiceAttachmentPointer pointer = a.asPointer();
|
||||||
|
final File tempFile = Files
|
||||||
|
.createTempFile(null, null, tempFileAttributes).toFile();
|
||||||
|
jsonAttachment.setData(IOUtils.readFully(
|
||||||
|
manager.retrieveAttachmentAsStream(pointer, tempFile)));
|
||||||
|
tempFile.delete();
|
||||||
|
jsonAttachment.setFilename(pointer.getFileName().orNull());
|
||||||
|
} else if (a.isStream()) {
|
||||||
|
final SignalServiceAttachmentStream stream = a.asStream();
|
||||||
|
jsonAttachment.setData(IOUtils.readFully(stream.getInputStream()));
|
||||||
|
jsonAttachment.setFilename(stream.getFileName().orNull());
|
||||||
|
}
|
||||||
|
msg.withAttachment(jsonAttachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.addMessage(msg);
|
||||||
|
} catch (final IOException | InvalidMessageException | MissingConfigurationException ex) {
|
||||||
|
result.addMessage(
|
||||||
|
new JsonErrorResponse(StatusCode.MESSAGE_PARSING_ERROR, ex.getMessage()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.addMessage(
|
||||||
|
new JsonErrorResponse(StatusCode.MISSING_MESSAGE_CONTENT, "missing content"));
|
||||||
|
}
|
||||||
|
if (e != null) {
|
||||||
|
result.addMessage(new JsonErrorResponse(StatusCode.MESSAGE_ERROR, e.getMessage()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.UNKNOWN, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(final long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoreAttachments() {
|
||||||
|
return ignoreAttachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnoreAttachments(final boolean ignoreAttachments) {
|
||||||
|
this.ignoreAttachments = ignoreAttachments;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
|
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||||
|
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||||
|
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||||
|
import org.asamk.signal.manager.util.AttachmentUtils;
|
||||||
|
import org.asamk.signal.socket.json.JsonErrorResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonMessageAttachment;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse.StatusCode;
|
||||||
|
import org.asamk.signal.socket.json.JsonSendMessageData;
|
||||||
|
import org.asamk.signal.socket.json.JsonSendMessageResponse;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize(as = JsonSendMessageCommand.class)
|
||||||
|
public class JsonSendMessageCommand extends AbstractSendCommand {
|
||||||
|
|
||||||
|
private JsonSendMessageData dataMessage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonResponse apply(final Manager manager) {
|
||||||
|
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
|
||||||
|
.withBody(dataMessage.getMessage());
|
||||||
|
|
||||||
|
if (dataMessage.getAttachments() != null) {
|
||||||
|
final SignalServiceMessageSender messageSender = manager.createMessageSender();
|
||||||
|
final List<SignalServiceAttachment> attachmentPointers = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
for (final JsonMessageAttachment a : dataMessage.getAttachments()) {
|
||||||
|
final ByteArrayInputStream stream = new ByteArrayInputStream(a.getData());
|
||||||
|
final int size = stream.available();
|
||||||
|
final String mime = URLConnection.guessContentTypeFromStream(stream);
|
||||||
|
|
||||||
|
final SignalServiceAttachmentStream attachment = AttachmentUtils.createAttachment(
|
||||||
|
new StreamDetails(stream, mime, size), Optional.fromNullable(a.getFilename()));
|
||||||
|
|
||||||
|
if (attachment.isStream()) {
|
||||||
|
attachmentPointers.add(messageSender.uploadAttachment(attachment.asStream()));
|
||||||
|
} else if (attachment.isPointer()) {
|
||||||
|
attachmentPointers.add(attachment.asPointer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_ATTACHMENT, e.getMessage());
|
||||||
|
}
|
||||||
|
messageBuilder.withAttachments(attachmentPointers);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Pair<Long, List<SendMessageResult>> result;
|
||||||
|
if (recipient != null && groupId == null) {
|
||||||
|
result = manager.sendMessage(messageBuilder,
|
||||||
|
manager.getSignalServiceAddresses(Arrays.asList(recipient)));
|
||||||
|
} else if (groupId != null && recipient == null) {
|
||||||
|
result = manager.sendGroupMessage(messageBuilder, GroupId.fromBase64(groupId));
|
||||||
|
} else {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_RECIPIENT,
|
||||||
|
"'recipient' or 'groupId' must be set");
|
||||||
|
}
|
||||||
|
return new JsonSendMessageResponse(this, StatusCode.SUCCESS, result.first());
|
||||||
|
} catch (final InvalidNumberException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_NUMBER, e.getMessage());
|
||||||
|
} catch (final GroupNotFoundException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.GROUP_NOT_FOUND, e.getMessage());
|
||||||
|
} catch (final NotAGroupMemberException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.NOT_A_GROUP_MEMBER, e.getMessage());
|
||||||
|
} catch (final GroupIdFormatException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_NUMBER, e.getMessage());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.UNKNOWN, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonSendMessageData getDataMessage() {
|
||||||
|
return dataMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataMessage(final JsonSendMessageData dataMessage) {
|
||||||
|
this.dataMessage = dataMessage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.manager.groups.GroupId;
|
||||||
|
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||||
|
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||||
|
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||||
|
import org.asamk.signal.socket.json.JsonErrorResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse.StatusCode;
|
||||||
|
import org.asamk.signal.socket.json.JsonSendMessageResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonSendReaction;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||||
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize(as = JsonSendReactionCommand.class)
|
||||||
|
public class JsonSendReactionCommand extends AbstractSendCommand {
|
||||||
|
private JsonSendReaction reaction;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonResponse apply(final Manager manager) {
|
||||||
|
final JsonSendReaction r = getReaction();
|
||||||
|
if (r == null) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.MISSING_REACTION, "incomplete request");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Pair<Long, List<SendMessageResult>> result;
|
||||||
|
if (recipient != null && groupId == null) {
|
||||||
|
result = manager.sendMessageReaction(r.getEmoji(), r.isRemove(), r.getAuthor(), r.getTimestamp(),
|
||||||
|
Arrays.asList(getRecipient()));
|
||||||
|
} else if (groupId != null && recipient == null) {
|
||||||
|
result = manager.sendGroupMessageReaction(r.getEmoji(), r.isRemove(), r.getAuthor(), r.getTimestamp(),
|
||||||
|
GroupId.fromBase64(groupId));
|
||||||
|
} else {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_RECIPIENT,
|
||||||
|
"'recipient' xor 'groupId' must be set");
|
||||||
|
}
|
||||||
|
return new JsonSendMessageResponse(this, StatusCode.SUCCESS, result.first());
|
||||||
|
} catch (final InvalidNumberException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_NUMBER, e.getMessage());
|
||||||
|
} catch (final GroupNotFoundException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.GROUP_NOT_FOUND, e.getMessage());
|
||||||
|
} catch (final NotAGroupMemberException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.NOT_A_GROUP_MEMBER, e.getMessage());
|
||||||
|
} catch (final GroupIdFormatException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.INVALID_NUMBER, e.getMessage());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return new JsonErrorResponse(this, StatusCode.UNKNOWN, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonSendReaction getReaction() {
|
||||||
|
return reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReaction(final JsonSendReaction reaction) {
|
||||||
|
this.reaction = reaction;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2021 sehaas and contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.asamk.signal.socket.commands;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.Manager;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse;
|
||||||
|
import org.asamk.signal.socket.json.JsonResponse.StatusCode;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
|
@JsonDeserialize(as = JsonUnknownCommand.class)
|
||||||
|
public class JsonUnknownCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonResponse apply(final Manager manager) {
|
||||||
|
return new JsonResponse(this, StatusCode.UNKNOWN_COMMAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
public interface IJsonReceiveableMessage {
|
||||||
|
|
||||||
|
}
|
27
src/main/java/org/asamk/signal/socket/json/JsonEnvelope.java
Normal file
27
src/main/java/org/asamk/signal/socket/json/JsonEnvelope.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
|
||||||
|
public class JsonEnvelope {
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
private String command;
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
private String reqId;
|
||||||
|
|
||||||
|
public String getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommand(final String command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReqId() {
|
||||||
|
return reqId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReqId(final String reqId) {
|
||||||
|
this.reqId = reqId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
|
||||||
|
public class JsonErrorResponse extends JsonResponse implements IJsonReceiveableMessage {
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
private final String errorMessage;
|
||||||
|
|
||||||
|
public JsonErrorResponse(final StatusCode code, final String errorMsg) {
|
||||||
|
this(null, code, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonErrorResponse(final JsonEnvelope req, final StatusCode code, final String errorMsg) {
|
||||||
|
super(req, code);
|
||||||
|
this.errorMessage = errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public class JsonMessageAttachment {
|
||||||
|
@JsonIgnore
|
||||||
|
private byte[] data;
|
||||||
|
private String filename;
|
||||||
|
private String base64Data;
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilename(final String filename) {
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBase64Data() {
|
||||||
|
return base64Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBase64Data(final String base64Data) {
|
||||||
|
this.base64Data = base64Data;
|
||||||
|
data = base64Data != null ? Base64.getDecoder().decode(base64Data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(final byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
this.base64Data = data != null ? Base64.getEncoder().encodeToString(data) : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
|
||||||
|
public class JsonReceivedMessage extends JsonResponse implements IJsonReceiveableMessage {
|
||||||
|
private final Long timestamp;
|
||||||
|
private final String sender;
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
private String body;
|
||||||
|
private final List<JsonMessageAttachment> attachments;
|
||||||
|
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
public JsonReceivedMessage(final Long timestamp, final String sender) {
|
||||||
|
super(null, StatusCode.SUCCESS);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.sender = sender;
|
||||||
|
this.attachments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonReceivedMessage withBody(final String body) {
|
||||||
|
this.body = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonReceivedMessage withGroupId(final String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonReceivedMessage withAttachment(final JsonMessageAttachment attach) {
|
||||||
|
attachments.add(attach);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JsonMessageAttachment> getAttachments() {
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JsonReceivedMessageResponse extends JsonResponse {
|
||||||
|
private final List<IJsonReceiveableMessage> messages;
|
||||||
|
|
||||||
|
public JsonReceivedMessageResponse(final JsonEnvelope req) {
|
||||||
|
super(req, StatusCode.SUCCESS);
|
||||||
|
messages = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IJsonReceiveableMessage> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMessage(final IJsonReceiveableMessage msg) {
|
||||||
|
if (msg != null) {
|
||||||
|
messages.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/org/asamk/signal/socket/json/JsonResponse.java
Normal file
43
src/main/java/org/asamk/signal/socket/json/JsonResponse.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
public class JsonResponse extends JsonEnvelope {
|
||||||
|
private final StatusCode statusCode;
|
||||||
|
|
||||||
|
public static enum StatusCode {
|
||||||
|
UNKNOWN(-1),
|
||||||
|
SUCCESS(0),
|
||||||
|
UNKNOWN_COMMAND(1),
|
||||||
|
INVALID_NUMBER(2),
|
||||||
|
INVALID_ATTACHMENT(3),
|
||||||
|
INVALID_JSON(4),
|
||||||
|
INVALID_RECIPIENT(5),
|
||||||
|
GROUP_NOT_FOUND(6),
|
||||||
|
NOT_A_GROUP_MEMBER(7),
|
||||||
|
MESSAGE_ERROR(8),
|
||||||
|
MISSING_MESSAGE_CONTENT(9),
|
||||||
|
MESSAGE_PARSING_ERROR(10),
|
||||||
|
MISSING_REACTION(11);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private StatusCode(final int c) {
|
||||||
|
code = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonResponse(final JsonEnvelope req, final StatusCode code) {
|
||||||
|
if (req != null) {
|
||||||
|
setReqId(req.getReqId());
|
||||||
|
setCommand(req.getCommand());
|
||||||
|
}
|
||||||
|
statusCode = code != null ? code : StatusCode.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatusCode() {
|
||||||
|
return statusCode.getCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JsonSendMessageData {
|
||||||
|
private String message;
|
||||||
|
private List<JsonMessageAttachment> attachments;
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JsonMessageAttachment> getAttachments() {
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttachments(List<JsonMessageAttachment> attachments) {
|
||||||
|
this.attachments = attachments;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
public class JsonSendMessageResponse extends JsonResponse {
|
||||||
|
private final Long timestamp;
|
||||||
|
|
||||||
|
public JsonSendMessageResponse(final JsonEnvelope req, final StatusCode code, final Long timestamp) {
|
||||||
|
super(req, code);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.asamk.signal.socket.json;
|
||||||
|
|
||||||
|
public class JsonSendReaction {
|
||||||
|
private String emoji;
|
||||||
|
private String author;
|
||||||
|
private boolean remove = false;
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
|
public String getEmoji() {
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmoji(final String emoji) {
|
||||||
|
this.emoji = emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(final String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRemove() {
|
||||||
|
return remove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemove(final boolean remove) {
|
||||||
|
this.remove = remove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(final long timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue