From 43a2b4674f0ebe763a739e8325c2aaa74eb4bcf0 Mon Sep 17 00:00:00 2001 From: technillogue Date: Mon, 9 Nov 2020 16:41:21 -0500 Subject: [PATCH] it works! --- src/main/java/org/asamk/signal/Main.java | 2 +- .../asamk/signal/commands/DaemonCommand.java | 82 ++++++++++++-- .../asamk/signal/commands/SendCommand.java | 3 +- .../asamk/signal/json/JsonDataMessage.java | 57 +++++++++- .../signal/json/JsonMessageEnvelope.java | 12 +- .../org/asamk/signal/manager/Manager.java | 106 ++++++++++++++++++ 6 files changed, 243 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index b95b0093..3a11ee24 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -222,7 +222,7 @@ public class Main { ArgumentParser parser = ArgumentParsers.newFor("signal-cli") .build() .defaultHelp(true) - .description("Commandline interface for Signal.") + .description("Commandline interface for Signal, patched to support sending messages from stdin and outputing reactions.") .version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION); parser.addArgument("-v", "--version") diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 2b983851..12cf7f98 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -6,17 +6,71 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.DbusReceiveMessageHandler; import org.asamk.signal.JsonDbusReceiveMessageHandler; +import org.asamk.signal.ReceiveMessageHandler; +import org.asamk.signal.JsonReceiveMessageHandler; import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.AttachmentInvalidException; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.IOException; import java.util.concurrent.TimeUnit; - +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import static org.asamk.signal.DbusConfig.SIGNAL_BUSNAME; import static org.asamk.signal.DbusConfig.SIGNAL_OBJECTPATH; import static org.asamk.signal.util.ErrorUtils.handleAssertionError; +import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions; +import org.whispersystems.signalservice.api.util.InvalidNumberException; + +class InputReader implements Runnable { + private volatile boolean alive = true; + private Manager m; + + public void terminate() { + this.alive = false; + } + InputReader (final Manager m) { + this.m = m; + } + + @Override + public void run() { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + while (alive) { + try { + String in = br.readLine(); + String args[] = in.split(":", 2); + // it should really read only one line though + if (args.length == 2) { + System.out.println(Arrays.toString(args)); + System.out.println(args.length); + String message = args[1]; + List recipients = new ArrayList(); + recipients.add(args[0]); + List attachments = new ArrayList<>(); + try { + System.out.println("sent " + in); + this.m.sendMessage(message, attachments, recipients); + } catch (AssertionError | EncapsulatedExceptions| AttachmentInvalidException | InvalidNumberException e) { + System.err.println("aaaaaa (Manager.java ~L1457)"); + e.printStackTrace(System.out); + } + } + + } catch (IOException e) { + System.err.println(e); + } + // getMessageSender().sendTyping(signalServiceAddress?, ....) + + } + } +} public class DaemonCommand implements LocalCommand { @@ -39,7 +93,7 @@ public class DaemonCommand implements LocalCommand { System.err.println("User is not registered."); return 1; } - DBusConnection conn = null; +/* DBusConnection conn = null; try { try { DBusConnection.DBusBusType busType; @@ -48,9 +102,9 @@ public class DaemonCommand implements LocalCommand { } else { busType = DBusConnection.DBusBusType.SESSION; } - conn = DBusConnection.getConnection(busType); - conn.exportObject(SIGNAL_OBJECTPATH, new DbusSignalImpl(m)); - conn.requestBusName(SIGNAL_BUSNAME); +// conn = DBusConnection.getConnection(busType); +// conn.exportObject(SIGNAL_OBJECTPATH, new DbusSignalImpl(m)); +// conn.requestBusName(SIGNAL_BUSNAME); } catch (UnsatisfiedLinkError e) { System.err.println("Missing native library dependency for dbus service: " + e.getMessage()); return 1; @@ -58,9 +112,17 @@ public class DaemonCommand implements LocalCommand { e.printStackTrace(); return 2; } + */ boolean ignoreAttachments = ns.getBoolean("ignore_attachments"); + InputReader reader = new InputReader(m); + Thread readerThread = new Thread(reader); + readerThread.start(); try { - m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, ns.getBoolean("json") ? new JsonDbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH) : new DbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH)); + m.receiveMessagesAndReadStdin(1, TimeUnit.HOURS, false, ignoreAttachments, + ns.getBoolean("json") + ? new JsonReceiveMessageHandler(m) + : new ReceiveMessageHandler(m) + /*true*/); return 0; } catch (IOException e) { System.err.println("Error while receiving messages: " + e.getMessage()); @@ -68,11 +130,9 @@ public class DaemonCommand implements LocalCommand { } catch (AssertionError e) { handleAssertionError(e); return 1; + } finally { + reader.terminate(); } - } finally { - if (conn != null) { - conn.disconnect(); - } - } + } } diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 43166b5b..e82a1fee 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; - +import java.util.Arrays; import static org.asamk.signal.util.ErrorUtils.handleAssertionError; import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException; @@ -102,6 +102,7 @@ public class SendCommand implements DbusCommand { } try { + System.out.println(Arrays.toString(ns.getList("recipient").toArray())); long timestamp = signal.sendMessage(messageText, attachments, ns.getList("recipient")); System.out.println(timestamp); return 0; diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index fc8538aa..5364b9a5 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -4,11 +4,29 @@ import org.asamk.Signal; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; - +//import org.whispersystems.libsignal.util.guava.Optional; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; + +// i think this is what you have to do to get another dict in json +// but i'm not sure +class JsonReaction { + String emoji; // unicode?? + String targetAuthor; + long targetTimestamp; + boolean isRemove; + JsonReaction (SignalServiceDataMessage.Reaction reaction) { + this.emoji = reaction.getEmoji(); + // comment on this line from ReceiveMessageHandler: todo resolve + this.targetAuthor = reaction.getTargetAuthor().getLegacyIdentifier(); + this.targetTimestamp = reaction.getTargetSentTimestamp(); + this.isRemove = reaction.isRemove(); + } +} + + class JsonDataMessage { long timestamp; @@ -16,6 +34,8 @@ class JsonDataMessage { int expiresInSeconds; List attachments; JsonGroupInfo groupInfo; + JsonReaction reaction; + SignalServiceDataMessage.Quote quote; JsonDataMessage(SignalServiceDataMessage dataMessage) { this.timestamp = dataMessage.getTimestamp(); @@ -35,8 +55,38 @@ class JsonDataMessage { } else { this.attachments = new ArrayList<>(); } - } + if (dataMessage.getReaction().isPresent()) { + final SignalServiceDataMessage.Reaction reaction = dataMessage.getReaction().get(); + this.reaction = new JsonReaction(reaction); +/* this.emoji = reaction.getEmoji(); + // comment on this line from ReceiveMessageHandler: todo resolve + this.targetAuthor = reaction.getTargetAuthor().getLegacyIdentifier(); + this.targetTimestamp = reaction.getTargetSentTimestamp(); +*/ } /*else { + this.reaction = null; +/* + this.emoji = ""; + this.targetAuthor = ""; + this.targetTimestamp = 0; +*/ // } +/* + + if (message.getQuote().isPresent()) { + SignalServiceDataMessage.Quote quote = message.getQuote().get(); + System.out.println("Quote: (" + quote.getId() + ")"); + // there doesn't seem to be any fucking way to find a message's id? + System.out.println(" Author: " + quote.getAuthor().getLegacyIdentifier()); + System.out.println(" Text: " + quote.getText()); + } + if (message.isExpirationUpdate()) { + System.out.println("Is Expiration update: " + message.isExpirationUpdate()); + } +*/ + } + // very confusingly MessageReceived seems to be only made in JsonDbusReceiveMessageHandler + // and only when *sending* to dbus, so to my current understanding this never gets called + // which would suggest i'm not understanding something public JsonDataMessage(Signal.MessageReceived messageReceived) { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); @@ -46,7 +96,8 @@ class JsonDataMessage { .map(JsonAttachment::new) .collect(Collectors.toList()); } - + // i don't understand what SyncMessages are so i'm gonna ignore them + // i think it only matters if you have multiple devices on your end public JsonDataMessage(Signal.SyncMessageReceived messageReceived) { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index b4269949..7ed75da9 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -1,12 +1,14 @@ package org.asamk.signal.json; import org.asamk.Signal; +//import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.push.SignalServiceAddress; public class JsonMessageEnvelope { - + // gotta do something so that it actually emits valid json instead of null + // or just fix it on the python side i guess String source; int sourceDevice; String relay; @@ -16,7 +18,7 @@ public class JsonMessageEnvelope { JsonSyncMessage syncMessage; JsonCallMessage callMessage; JsonReceiptMessage receiptMessage; - + // String typingAction; public JsonMessageEnvelope(SignalServiceEnvelope envelope, SignalServiceContent content) { if (!envelope.isUnidentifiedSender() && envelope.hasSource()) { SignalServiceAddress source = envelope.getSourceAddress(); @@ -43,7 +45,11 @@ public class JsonMessageEnvelope { if (content.getReceiptMessage().isPresent()) { this.receiptMessage = new JsonReceiptMessage(content.getReceiptMessage().get()); } - } +/* if (content.getTypingMessage().isPresent()) { + SignalServiceTypingMessage typingMessage = content.getTypingMessage().get(); + this.typingAction = content.getTypingMessage().get(); + } +*/ } } public JsonMessageEnvelope(Signal.MessageReceived messageReceived) { diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index c332a959..319463c5 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -113,6 +113,7 @@ import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.util.Base64; +import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; @@ -120,10 +121,12 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @@ -148,6 +151,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import static org.asamk.signal.manager.ServiceConfig.capabilities; +import static org.asamk.signal.util.ErrorUtils.handleAssertionError; public class Manager implements Closeable { @@ -1419,6 +1423,108 @@ public class Manager implements Closeable { } } + public void receiveMessagesAndReadStdin(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { + retryFailedReceivedMessages(handler, ignoreAttachments); + final SignalServiceMessageReceiver messageReceiver = getMessageReceiver(); + + Set queuedActions = null; + + if (messagePipe == null) { + messagePipe = messageReceiver.createMessagePipe(); + } + + boolean hasCaughtUpWithOldMessages = false; + + while (true) { + SignalServiceEnvelope envelope; + SignalServiceContent content = null; + Exception exception = null; + final long now = new Date().getTime(); + try { + Optional result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> { + // store message on disk, before acknowledging receipt to the server + try { + String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : ""; + File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp()); + Utils.storeEnvelope(envelope1, cacheFile); + } catch (IOException e) { + System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage()); + } + }); + if (result.isPresent()) { + envelope = result.get(); + } else { + // Received indicator that server queue is empty + hasCaughtUpWithOldMessages = true; + + if (queuedActions != null) { + for (HandleAction action : queuedActions) { + try { + action.execute(this); + } catch (Throwable e) { + e.printStackTrace(); + } + } + queuedActions.clear(); + queuedActions = null; + } + + // Continue to wait another timeout for new messages + continue; + } + } catch (TimeoutException e) { + if (returnOnTimeout) + return; + continue; + } catch (InvalidVersionException e) { + System.err.println("Ignoring error: " + e.getMessage()); + continue; + } + if (envelope.hasSource()) { + // Store uuid if we don't have it already + SignalServiceAddress source = envelope.getSourceAddress(); + resolveSignalServiceAddress(source); + } + if (!envelope.isReceipt()) { + try { + content = decryptMessage(envelope); + } catch (Exception e) { + exception = e; + } + List actions = handleMessage(envelope, content, ignoreAttachments); + if (hasCaughtUpWithOldMessages) { + for (HandleAction action : actions) { + try { + action.execute(this); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } else { + if (queuedActions == null) { + queuedActions = new HashSet<>(); + } + queuedActions.addAll(actions); + } + } + account.save(); + if (!isMessageBlocked(envelope, content)) { + handler.handleMessage(envelope, content, exception); + } + if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) { + File cacheFile = null; + try { + String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : ""; + cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp()); + Files.delete(cacheFile.toPath()); + // Try to delete directory if empty + new File(getMessageCachePath()).delete(); + } catch (IOException e) { + System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + e.getMessage()); + } + } + } + } public void receiveMessages(long timeout, TimeUnit unit, boolean returnOnTimeout, boolean ignoreAttachments, ReceiveMessageHandler handler) throws IOException { retryFailedReceivedMessages(handler, ignoreAttachments); final SignalServiceMessageReceiver messageReceiver = getMessageReceiver();