Added socket ipc and docker support

This commit is contained in:
Matteljay 2020-07-07 10:53:11 +02:00
parent 4177deccf1
commit 04a545b395
13 changed files with 757 additions and 4 deletions

3
.gitignore vendored
View file

@ -10,3 +10,6 @@ local.properties
.project
.settings/
out/
.vscode
testme
buildcontainer

10
Dockerfile Normal file
View file

@ -0,0 +1,10 @@
FROM openjdk:8u151-alpine
RUN apk update \
&& apk upgrade \
&& apk add --no-cache bash libc6-compat
COPY build/install/signal-cli /opt/signal-cli
ENV PATH="/opt/signal-cli/bin:${PATH}"
RUN adduser -D -g '' user
USER user
RUN mkdir -pv ~/.config/signal/data
CMD ["signal-cli", "--singleuser", "socket", "-a", "0.0.0.0"]

View file

@ -20,6 +20,7 @@ sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
You can find further instructions on the Wiki:
- [Install on Ubuntu](https://github.com/AsamK/signal-cli/wiki/HowToUbuntu)
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
- [Socket IPC using JSON](https://github.com/Matteljay/signal-cli/wiki/Socket.md)
## Usage

152
SOCKET.md Normal file
View file

@ -0,0 +1,152 @@
# Socket IPC with signal-cli
Signal-cli offers a simple yet powerful way of inter-process communication via sockets. The purpose is to be able to connect your own programmed logic to Signal. Allowing you to manage contacts and their privileges on your own so you can create a powerful automated chat bot with Signal. It is now easy to set up signal-cli in it's own Docker microservice and connect a script to it running in a separate container. This configuration adds security and deployment speed via Docker's network orchestration. There is a [Dockerfile](/Matteljay/signal-cli/blob/master/Dockerfile) included.
## Get started
The API formats are explained below but first, signal-cli should be launched as a socket daemon. As an alternative to Docker, the command below could be wrapped in a [systemd](https://www.shellhacks.com/systemd-service-file-example/) or [screen](https://www.thegeekdiary.com/how-to-use-the-screen-command-in-linux/) launcher script.
It is assumed you have one Signal user registered and verified as explained in the [README](/Matteljay/signal-cli/blob/master/README.md#usage) file. You are now ready to launch the socket listener from the command line:
signal-cli --singleuser socket
Or by setting the default values explicitly:
signal-cli -u USERNAME socket -a localhost -p 24250
This will start listening and sending JSON string objects locally on port `24250`. From your own bash or python program, you can now interact seamlessly with the Signal server via this socket. An example of a single-threaded non-blocking testing application can be found here [socketTest.py](/Matteljay/signal-cli/blob/master/src/socketTest.py).
## Sending
```
{
"sendMessage" : {
"contacts" : [ "+31638555555" ],
"message" : "This PM comes from Python <3"
}
}
{
"sendMessage" : {
"groups" : [ "Y5555rtl2p/TnLYvY555dA==", "DK555555UjPU55545557bA==" ],
"message" : "This GROUP message comes from Python!"
}
}
{
"updateContacts" : {
"+31638555555" : {
"name" : "NewName",
"color" : "",
"messageExpirationTime" : 555000,
"blocked" : "false",
"inboxPosition" : 2,
"archived" : true
}
}
}
{
"trust" : {
"contacts" : [ "+31638555555" ]
}
}
{
"endSession" : {
"contacts" : [ "+31638555555" ]
}
}
{
"getContacts" : ""
}
{
"getGroups" : ""
}
```
## Receiving
### Incoming message
```
{
"envelope": {
"source": "+31638422555",
"sourceDevice": 1,
"relay": None,
"timestamp": 1592692535999,
"isReceipt": False,
"dataMessage": {
"timestamp": 1592692535999,
"message": "Hello signal-cli!",
"expiresInSeconds": 0,
"attachments": [],
"groupInfo": {
"groupId": "Y5555rtl2p/TnLYvY555dA==",
"members": None,
"name": None,
"type": "DELIVER"
}
},
"syncMessage": None,
"callMessage": None,
"receiptMessage": None
}
}
```
### Response to 'getContacts'
```
{
"+31638422555" : {
"name" : null,
"uuid" : "1555f555-eb02-555d-a1b6-555517905552",
"color" : null,
"messageExpirationTime" : 0,
"profileKey" : "Y4555c7P4LM6Y555XW2PKx4555BMFxO555g/sJ555KU=",
"blocked" : false,
"inboxPosition" : null,
"archived" : false
},
"+31638422999" : {
"name" : null,
"uuid" : "1999f999-eb02-999d-a1b6-999517909992",
"color" : null,
"messageExpirationTime" : 0,
"profileKey" : "Y4999c7P4LM6Y999XW2PKx4999BMFxO999g/sJ999KU=",
"blocked" : false,
"inboxPosition" : null,
"archived" : false
}
}
```
### Response to 'getGroups'
```
{
"DK555555UjPU55545557bA==" : {
"name" : "Watchdogs inc.",
"color" : null,
"messageExpirationTime" : 0,
"blocked" : false,
"inboxPosition" : null,
"archived" : false,
"avatarId" : 0,
"members" : [ "+31638555555", "+31638999999" ]
},
"Y5555rtl2p/TnLYvY555dA==" : {
"name" : "Freddy ftw",
"color" : null,
"messageExpirationTime" : 0,
"blocked" : false,
"inboxPosition" : null,
"archived" : false,
"avatarId" : 0,
"members" : [ "+31638555555", "+31638999999" ]
}
}
```

View file

@ -40,8 +40,12 @@ public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler
result.putPOJO("envelope", new JsonMessageEnvelope(envelope, content));
}
try {
jsonProcessor.writeValue(System.out, result);
System.out.println();
String output = jsonProcessor.writeValueAsString(result);
if (m.socketTasks != null) {
m.socketTasks.send(output);
} else {
System.out.println(output);
}
} catch (IOException e) {
e.printStackTrace();
}

View file

@ -26,6 +26,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
import net.sourceforge.argparse4j.inf.Subparsers;
import org.asamk.Signal;
import org.asamk.signal.storage.SignalAccount;
import org.asamk.signal.commands.Command;
import org.asamk.signal.commands.Commands;
import org.asamk.signal.commands.DbusCommand;
@ -36,6 +37,7 @@ import org.asamk.signal.dbus.DbusSignalImpl;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.ProvisioningManager;
import org.asamk.signal.manager.ServiceConfig;
import org.asamk.signal.manager.PathConfig;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.SecurityProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -73,7 +75,7 @@ public class Main {
}
private static int handleCommands(Namespace ns) {
final String username = ns.getString("username");
String username = ns.getString("username");
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
try {
@ -103,6 +105,16 @@ public class Main {
dataPath = getDefaultDataPath();
}
if (ns.getBoolean("singleuser")) {
String completeDataPath = PathConfig.createDefault(dataPath).getDataPath();
username = SignalAccount.getSingleUser(completeDataPath);
if (username == null) {
System.exit(1);
}
System.out.println("Assuming username: " + username);
ns.getAttrs().put("username", username);
}
final SignalServiceConfiguration serviceConfiguration = ServiceConfig.createDefaultServiceConfiguration(BaseConfig.USER_AGENT);
if (username == null) {
@ -234,6 +246,9 @@ public class Main {
MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
mut.addArgument("-u", "--username")
.help("Specify your phone number, that will be used for verification.");
mut.addArgument("--singleuser")
.help("Can be used if only one user account exists on this machine.")
.action(Arguments.storeTrue());
mut.addArgument("--dbus")
.help("Make request via user dbus.")
.action(Arguments.storeTrue());
@ -267,7 +282,7 @@ public class Main {
System.err.println("You cannot specify a username (phone number) when linking");
System.exit(2);
}
} else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
} else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system") && !ns.getBoolean("singleuser")) {
if (ns.getString("username") == null) {
parser.printUsage();
System.err.println("You need to specify a username (phone number)");

View file

@ -34,6 +34,7 @@ public class Commands {
addCommand("updateProfile", new UpdateProfileCommand());
addCommand("verify", new VerifyCommand());
addCommand("uploadStickerPack", new UploadStickerPackCommand());
addCommand("socket", new SocketCommand());
}
public static Map<String, Command> getCommands() {

View file

@ -0,0 +1,65 @@
package org.asamk.signal.commands;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.JsonReceiveMessageHandler;
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
import org.asamk.signal.socket.SocketTasks;
public class SocketCommand implements LocalCommand {
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.addArgument("-a", "--address")
.setDefault("127.0.0.1")
.help("Socket bind address");
subparser.addArgument("-p", "--port")
.type(int.class)
.setDefault(24250)
.help("Socket port to use");
subparser.help("Receive at a socket while being able to send as well");
}
@Override
public int handleCommand(final Namespace ns, final Manager m) {
if (!m.isRegistered()) {
System.err.println("User is not registered.");
return 1;
}
System.out.println("Starting socket thread...");
m.socketTasks = new SocketTasks(ns, m);
try {
Thread thread = new Thread(m.socketTasks);
thread.start();
} catch (Exception e) {
System.err.println(e);
}
try {
// Let the thread settle for a second
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {}
System.out.println("Listening from Signal server...");
final long timeout = 3600 * 1000; // timeout in ms
final boolean returnOnTimeout = false;
final boolean ignoreAttachments = true;
try {
final Manager.ReceiveMessageHandler handler = new JsonReceiveMessageHandler(m);
while(true) {
m.receiveMessages(timeout, TimeUnit.MILLISECONDS, returnOnTimeout, ignoreAttachments, handler);
}
} catch (IOException e) {
System.err.println("Error while receiving messages: " + e.getMessage());
return 3;
} catch (AssertionError e) {
handleAssertionError(e);
return 1;
}
}
}

View file

@ -141,6 +141,8 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.asamk.signal.socket.SocketTasks;
public class Manager implements Closeable {
private final SleepTimer timer = new UptimeSleepTimer();
@ -153,6 +155,8 @@ public class Manager implements Closeable {
private SignalServiceMessagePipe messagePipe = null;
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
public SocketTasks socketTasks = null;
public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) {
this.account = account;
this.pathConfig = pathConfig;
@ -163,6 +167,10 @@ public class Manager implements Closeable {
this.account.setResolver(this::resolveSignalServiceAddress);
}
public SignalAccount getAccount() {
return account;
}
public String getUsername() {
return account.getUsername();
}

View file

@ -0,0 +1,287 @@
package org.asamk.signal.socket;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.io.IOException;
import java.net.Socket;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.core.type.TypeReference;
import org.asamk.signal.storage.SignalAccount;
import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.util.Util;
import org.asamk.signal.util.GroupIdFormatException;
import org.asamk.signal.manager.GroupNotFoundException;
import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.NotAGroupMemberException;
import org.whispersystems.util.Base64;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
import org.asamk.signal.socket.SocketTasks;
public class Commander {
private Manager m;
private SocketTasks tasks;
private ObjectMapper mapper;
public Commander(Manager manager, SocketTasks socketTasks) {
this.m = manager;
this.tasks = socketTasks;
this.mapper = new ObjectMapper();
}
public void handleCommand(String command, JsonNode arguments) {
switch (command) {
case "getContacts":
this.tasks.send(this.getContacts());
break;
case "updateContacts":
this.updateContacts(arguments);
break;
case "sendMessage":
this.sendMessage(arguments);
break;
case "trust":
this.trust(arguments);
break;
case "endSession":
this.endSession(arguments);
break;
case "getGroups":
this.tasks.send(this.getGroups());
break;
default:
this.tasks.send("Unknown command: '" + command + "'");
}
}
private String getGroups() {
ObjectNode node = this.mapper.createObjectNode();
List<GroupInfo> groups = this.m.getAccount().getGroupStore().getGroups();
for (GroupInfo g: groups) {
ObjectNode group = this.mapper.createObjectNode();
group.put("name", g.name);
group.put("color", g.color);
group.put("messageExpirationTime", g.messageExpirationTime);
group.put("blocked", g.blocked);
group.put("inboxPosition", g.inboxPosition);
group.put("archived", g.archived);
group.put("avatarId", g.getAvatarId());
for (String m: g.getMembersE164()) {
group.putArray("members").add(m);
}
node.set(Base64.encodeBytes(g.groupId), group);
}
String out = null;
try {
out = this.mapper.writeValueAsString(node); //writerWithDefaultPrettyPrinter()
} catch (Exception e) {
System.err.println("Could not send groups");
}
return out;
}
private void sendMessage(JsonNode args) {
Iterator<Map.Entry<String,JsonNode>> entryIt = args.fields();
Map.Entry<String,JsonNode> entry;
String argument;
String message = null;
ArrayList<String> contacts = null;
ArrayList<String> groups = null;
while (entryIt.hasNext()) {
entry = entryIt.next();
argument = entry.getKey();
switch(argument) {
case "contacts":
try {
contacts = this.mapper.readValue(entry.getValue().toString(), new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
System.err.println("bad list of contacts");
return;
}
break;
case "groups":
try {
groups = this.mapper.readValue(entry.getValue().toString(), new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
System.err.println("bad list of groups");
return;
}
break;
case "message":
message = entry.getValue().asText();
if (message.trim().length() <= 0) {
System.err.println("cannot send empty message");
return;
}
break;
}
}
if (contacts != null) {
try {
this.m.sendMessage(message, new ArrayList<>(), contacts);
} catch (Exception | EncapsulatedExceptions | InvalidNumberException e) {
System.err.println(e);
}
}
if (groups != null) {
byte[] groupId;
for (String group: groups) {
try {
groupId = Util.decodeGroupId(group);
} catch (GroupIdFormatException e) {
handleGroupIdFormatException(e);
continue;
}
try {
this.m.sendGroupMessage(message, new ArrayList<>(), groupId);
} catch (IOException | EncapsulatedExceptions | GroupNotFoundException | AttachmentInvalidException | NotAGroupMemberException e) {
System.err.println(e);
}
}
}
}
private void endSession(JsonNode args) {
Iterator<Map.Entry<String,JsonNode>> entryIt = args.fields();
Map.Entry<String,JsonNode> entry;
String argument;
ArrayList<String> contacts = null;
while (entryIt.hasNext()) {
entry = entryIt.next();
argument = entry.getKey();
switch(argument) {
case "contacts":
try {
contacts = this.mapper.readValue(entry.getValue().toString(), new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
System.err.println("bad list of contacts");
return;
}
break;
}
}
if (contacts != null) {
try {
this.m.sendEndSessionMessage(contacts);
System.err.println("WARNING: Ended session with " + String.join(", ", contacts));
} catch (Exception | EncapsulatedExceptions | InvalidNumberException e) {
System.err.println(e);
}
}
}
private void trust(JsonNode args) {
Iterator<Map.Entry<String,JsonNode>> entryIt = args.fields();
Map.Entry<String,JsonNode> entry;
String argument;
ArrayList<String> contacts = null;
while (entryIt.hasNext()) {
entry = entryIt.next();
argument = entry.getKey();
switch(argument) {
case "contacts":
try {
contacts = this.mapper.readValue(entry.getValue().toString(), new TypeReference<ArrayList<String>>(){});
} catch (IOException e) {
System.err.println("bad list of contacts");
return;
}
break;
}
}
if (contacts != null) {
for (String contact: contacts) {
this.m.trustIdentityAllKeys(contact);
}
System.err.println("WARNING: Updated trust for all keys from " + String.join(", ", contacts));
}
}
private String getContacts() {
ObjectNode node = this.mapper.createObjectNode();
List<ContactInfo> contacts = this.m.getContacts();
for (ContactInfo c: contacts) {
ObjectNode group = this.mapper.createObjectNode();
group.put("name", c.name);
group.put("uuid", c.uuid.toString());
group.put("color", c.color);
group.put("messageExpirationTime", c.messageExpirationTime);
group.put("profileKey", c.profileKey);
group.put("blocked", c.blocked);
group.put("inboxPosition", c.inboxPosition);
group.put("archived", c.archived);
node.set(c.number, group);
}
String out = null;
try {
out = this.mapper.writeValueAsString(node); //writerWithDefaultPrettyPrinter()
} catch (Exception e) {
System.err.println("Could not send contacts");
}
return out;
}
private void updateContacts(JsonNode args) {
Iterator<Map.Entry<String,JsonNode>> entryIt = args.fields();
Map.Entry<String,JsonNode> entry;
String number;
while (entryIt.hasNext()) {
entry = entryIt.next();
number = entry.getKey();
ContactInfo contactInfo = this.m.getContact(number);
if (contactInfo == null) {
System.err.println("Could not update " + number);
continue;
} else {
patchContactInfo(contactInfo, entry.getValue());
SignalAccount account = this.m.getAccount();
account.getContactStore().updateContact(contactInfo);
account.save();
System.out.println("Updated account info for " + number);
}
}
}
private void patchContactInfo(ContactInfo contactInfo, JsonNode args) {
Iterator<Map.Entry<String,JsonNode>> entryIt = args.fields();
Map.Entry<String,JsonNode> entry;
String argument;
JsonNode value;
while (entryIt.hasNext()) {
entry = entryIt.next();
argument = entry.getKey();
value = entry.getValue();
switch (argument) {
case "name":
contactInfo.name = value.asText();
break;
case "color":
contactInfo.color = value.asText();
break;
case "messageExpirationTime":
contactInfo.messageExpirationTime = value.asInt();
break;
case "blocked":
contactInfo.blocked = value.asBoolean();
break;
case "inboxPosition":
contactInfo.inboxPosition = value.asInt();
break;
case "archived":
contactInfo.archived = value.asBoolean();
break;
default:
System.err.println("Invalid field '" + argument + "' in " + contactInfo.number);
}
}
}
}

View file

@ -0,0 +1,102 @@
package org.asamk.signal.socket;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.InetAddress;
import java.nio.charset.Charset;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import net.sourceforge.argparse4j.inf.Namespace;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.socket.Commander;
public class SocketTasks implements Runnable {
private String address;
private int port;
private Socket socket;
private OutputStream output = null;
private ObjectMapper mapper;
private Commander commander;
public SocketTasks(Namespace namespace, Manager manager) {
this.address = namespace.getString("address");
this.port = namespace.getInt("port");
this.mapper = new ObjectMapper();
this.commander = new Commander(manager, this);
}
public void send(String message) {
if (message == null) return;
if (this.output == null || this.socket.isClosed()) {
System.err.println("WARNING: a message is ready but the socket isn't connected");
return;
}
message += "\n";
try {
this.output.write(message.getBytes(Charset.forName("UTF-8")));
this.output.flush();
} catch (SocketException e) {
System.err.println("Output socket connection lost!");
this.output = null;
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
ServerSocket serverSocket = new ServerSocket(this.port, 1, InetAddress.getByName(this.address));
System.out.println("Socket ready, binding to address: " + this.address + ":" + this.port);
this.socket = serverSocket.accept(); // accept() blocks
this.output = socket.getOutputStream();
InputStream input = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input));
System.out.println("Connection established.");
while (this.responseProcessor(bufferedReader.readLine())); // readLine() blocks
bufferedReader.close();
this.output.close();
input.close();
this.socket.close();
serverSocket.close();
} catch (Exception e) { System.err.println(e); }
System.err.println("Socket connection lost.");
try {
// Help relax potential resource hogging and log spam
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {}
}
}
private boolean responseProcessor(String line) {
if (line == null) return false;
JsonNode node = null;
try {
node = this.mapper.readValue(line.trim(), JsonNode.class);
} catch (Exception e) { System.err.println(e); }
if (node == null) return true;
String reply = null;
Iterator<Map.Entry<String,JsonNode>> entryIt = node.fields();
Map.Entry<String,JsonNode> entry;
String command;
while (entryIt.hasNext()) {
entry = entryIt.next();
command = entry.getKey();
this.commander.handleCommand(command, entry.getValue());
}
return true;
}
}

View file

@ -152,6 +152,26 @@ public class SignalAccount implements Closeable {
return !(!f.exists() || f.isDirectory());
}
public static String getSingleUser(String dataPath) {
File folder = new File(dataPath);
File[] listOfFiles = folder.listFiles();
if (listOfFiles == null || listOfFiles.length <= 0) {
System.err.println("No user account found");
return null;
}
String user = null;
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
if (user != null) {
System.err.println("Too many user accounts found");
return null; // too many users
}
user = listOfFiles[i].getName();
}
}
return user;
}
private void load() throws IOException {
JsonNode rootNode;
synchronized (fileChannel) {

85
src/socketTest.py Normal file
View file

@ -0,0 +1,85 @@
#!/usr/bin/python3
import socket
import time
import sys
import signal # optional: for SIGINT, SIGKILL
import json
class SignalProcessor:
def __init__(self):
self.HOST = '127.0.0.1'
self.PORT = 24250
self.messageQueue = []
def connect(self):
print(f'Connecting to {self.HOST}:{self.PORT}...')
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while True:
try:
self.sock.connect((self.HOST, self.PORT))
self.sock.setblocking(False) # sock.settimeout(0.0)
break
except ConnectionRefusedError:
print('Refused, attemping to reconnect...')
time.sleep(3)
print('Connection established')
self.testCommands()
def receive(self):
try:
buffer = self.sock.recv(65536) # should be plenty for large contact lists
except BlockingIOError:
return True
if not buffer:
print('Lost connection!')
return False
for line in filter(None, buffer.split(b'\n')):
self.messageQueue.append(line.decode())
return True
def send(self, message):
self.sock.sendall(message.encode() + b'\n');
def testCommands(self):
#self.send('{ "sendMessage" : { "contacts" : [ "+31638555555" ], "groups" : [ "Y5555rtl2p/TnLYvY555dA==", "DK555555UjPU55545557bA==" ], "message" : "This GROUP message comes from Python!" } }')
#self.send('{ "updateContacts" : { "+31638555555" : { "archived" : false } }, "getContacts" : "" }')
#self.send('{ "sendMessage" : { "contacts" : [ "+31638555555" ], "message" : "This PM comes from Python <3" } }')
#self.send('{ "getGroups" : "", "getContacts" : "" }')
self.send('{ "trust" : { "contacts" : [ "+31638555555" ] }, "endSession" : { "contacts" : [ "+31638555555" ] } }')
time.sleep(1)
def handleMessages(self):
while len(signalCli.messageQueue):
rawMessage = signalCli.messageQueue.pop(0)
print(rawMessage)
obj = json.loads(rawMessage)
try:
source = obj['envelope']['source']
msg = obj['envelope']['dataMessage']['message']
except Exception:
continue
if msg.strip().lower() == 'love':
replyObj = { 'sendMessage': { 'contacts' : source.split(), 'message' : 'From Russia with ' + msg } }
self.send(json.dumps(replyObj))
def yourTimedCode(self):
print('yourStuff')
def sigHandler(signal, frame):
print(f'Clean exit, received signal {signal}')
sys.exit(0)
if __name__ == '__main__':
signal.signal(signal.SIGINT, sigHandler)
signal.signal(signal.SIGTERM, sigHandler)
signalCli = SignalProcessor()
signalCli.connect()
while True:
if signalCli.receive():
signalCli.handleMessages()
signalCli.yourTimedCode()
time.sleep(1)
else:
signalCli.connect()
# EOF