From c77b2e951142b4ae19512df81bafddcac697a26f Mon Sep 17 00:00:00 2001 From: John Freed Date: Fri, 13 Aug 2021 10:11:44 +0200 Subject: [PATCH] extend DBus signals for DbusAttachments This change creates the DBus signals `SyncMessageReceivedV2` and `MessageReceivedV2`, as well as the new data type `DbusAttachment`. A DbusAttachment is a struct that contains considerably more information than the filename. To maintain backward compatibility, `SyncMessageReceived` and `MessageReceived` remain unchanged. V2 includes Mentions and DbusAttachments. This change also permits sending arbitrary URLs as attachment names. --- .gitignore | 3 + README.md | 2 +- build.gradle.kts | 2 +- .../org/asamk/signal/manager/Manager.java | 2 +- .../signal/manager/util/AttachmentUtils.java | 34 ++++- .../org/asamk/signal/manager/util/Utils.java | 8 + src/main/java/org/asamk/Signal.java | 141 ++++++++++++++++-- src/main/java/org/asamk/signal/App.java | 3 +- .../signal/JsonDbusReceiveMessageHandler.java | 59 +++++++- .../asamk/signal/ReceiveMessageHandler.java | 3 + .../asamk/signal/commands/DaemonCommand.java | 7 +- .../asamk/signal/commands/ReceiveCommand.java | 38 +++++ .../asamk/signal/commands/SendCommand.java | 27 +++- .../signal/dbus/DbusSignalControlImpl.java | 1 - .../org/asamk/signal/dbus/DbusSignalImpl.java | 81 +++++++++- .../org/asamk/signal/json/JsonAttachment.java | 47 ++++++ .../asamk/signal/json/JsonDataMessage.java | 33 ++++ src/main/java/org/asamk/signal/util/Util.java | 2 + 18 files changed, 446 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 8fa9c8bd..536bdd41 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ local.properties .settings/ out/ .DS_Store +.git/ +signal-cli +bin/ diff --git a/README.md b/README.md index f8eee725..67f2c397 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # signal-cli signal-cli is a commandline interface for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering, verifying, sending and receiving messages. -To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a slave device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). +To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a secondary device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). For registering you need a phone number where you can receive SMS or incoming calls. signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings. diff --git a/build.gradle.kts b/build.gradle.kts index c7de229c..da2a54bf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { `check-lib-versions` } -version = "0.8.5" +version = "0.8.6" java { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 98b02c7f..f7290cd7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -1274,7 +1274,7 @@ public class Manager implements Closeable { ) throws IOException, AttachmentInvalidException, InvalidNumberException { final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { - var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); + List attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); // Upload attachments here, so we only upload once even for multiple recipients var messageSender = createMessageSender(); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java index aadadf95..e7caadab 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.net.URL; public class AttachmentUtils { @@ -21,8 +22,13 @@ public class AttachmentUtils { for (var attachment : attachments) { try { signalServiceAttachments.add(createAttachment(new File(attachment))); - } catch (IOException e) { - throw new AttachmentInvalidException(attachment, e); + } catch (IOException f) { + // no such file, send it as URL + try { + signalServiceAttachments.add(createAttachment(new URL(attachment))); + } catch (IOException e) { + throw new AttachmentInvalidException(attachment, e); + } } } } @@ -34,25 +40,39 @@ public class AttachmentUtils { return createAttachment(streamDetails, Optional.of(attachmentFile.getName())); } + public static SignalServiceAttachmentStream createAttachment(URL aURL) throws IOException { + final var streamDetails = Utils.createStreamDetailsFromURL(aURL); + String path = aURL.getPath(); + String name = path.substring(path.lastIndexOf('/') + 1); + return createAttachment(streamDetails, Optional.of(name)); + } + public static SignalServiceAttachmentStream createAttachment( StreamDetails streamDetails, Optional name ) { - // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option + // TODO maybe add a parameter to set the voiceNote, borderless, preview, width, height, caption, blurHash options final var uploadTimestamp = System.currentTimeMillis(); + boolean voicenote = false; + boolean borderless = false; Optional preview = Optional.absent(); + int width = 0; + int height = 0; Optional caption = Optional.absent(); Optional blurHash = Optional.absent(); final Optional resumableUploadSpec = Optional.absent(); + //ProgressListener listener = null; //Android OS + //CancellationSignal cancellationSignal = null; //Android OS; Signal developers misspelled class name + return new SignalServiceAttachmentStream(streamDetails.getStream(), streamDetails.getContentType(), streamDetails.getLength(), name, - false, - false, + voicenote, + borderless, false, preview, - 0, - 0, + width, + height, uploadTimestamp, caption, blurHash, diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index b0a9bbb1..1001a600 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; @@ -36,6 +37,13 @@ public class Utils { return new StreamDetails(stream, mime, size); } + public static StreamDetails createStreamDetailsFromURL(URL aURL) throws IOException { + InputStream stream = aURL.openStream(); + final var mime = aURL.openConnection().getContentType(); + final var size = aURL.openConnection().getContentLengthLong(); + return new StreamDetails(stream, mime, size); + } + public static String computeSafetyNumber( boolean isUuidCapable, SignalServiceAddress ownAddress, diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 2261c650..460e8ae7 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -1,5 +1,6 @@ package org.asamk; +import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.dbus.DbusMention; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; @@ -16,12 +17,21 @@ import java.util.Map; */ public interface Signal extends DBusInterface { - long sendMessage( - String message, List attachments, String recipient + + long sendMessageV2( + String message, List dBusAttachments, String recipient + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + + long sendMessageV2( + String message, List dBusAttachments, List recipients ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendMessage( - String message, List attachments, List recipients + String message, List attachmentNames, String recipient + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + + long sendMessage( + String message, List attachmentNames, List recipients ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendRemoteDeleteMessage( @@ -45,13 +55,21 @@ public interface Signal extends DBusInterface { ) throws Error.InvalidNumber, Error.Failure; long sendNoteToSelfMessage( - String message, List attachments + String message, List attachmentNames + ) throws Error.AttachmentInvalid, Error.Failure; + + long sendNoteToSelfMessageV2( + String message, List dBusAttachments ) throws Error.AttachmentInvalid, Error.Failure; void sendEndSessionMessage(List recipients) throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendGroupMessage( - String message, List attachments, byte[] groupId + String message, List attachmentNames, byte[] groupId + ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid; + + long sendGroupMessageV2( + String message, List dBusAttachments, byte[] groupId ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid; long sendGroupMessageReaction( @@ -116,17 +134,62 @@ public interface Signal extends DBusInterface { private final String sender; private final byte[] groupId; private final String message; - private final List mentions; - private final List attachments; + private final List attachmentNames; public MessageReceived( + String objectpath, + long timestamp, + String sender, + byte[] groupId, + String message, + List attachmentNames + ) throws DBusException { + super(objectpath, timestamp, sender, groupId, message, attachmentNames); + this.timestamp = timestamp; + this.sender = sender; + this.groupId = groupId; + this.message = message; + this.attachmentNames = attachmentNames; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSender() { + return sender; + } + + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public List getAttachmentNames() { + return attachmentNames; + } + } + + class MessageReceivedV2 extends DBusSignal { + + private final long timestamp; + private final String sender; + private final byte[] groupId; + private final String message; + private final List mentions; + private final List attachments; + + public MessageReceivedV2( String objectpath, long timestamp, String sender, byte[] groupId, String message, List mentions, - List attachments + List attachments ) throws DBusException { super(objectpath, timestamp, sender, groupId, message, mentions, attachments); this.timestamp = timestamp; @@ -157,7 +220,7 @@ public interface Signal extends DBusInterface { return mentions; } - public List getAttachments() { + public List getAttachments() { return attachments; } } @@ -189,10 +252,62 @@ public interface Signal extends DBusInterface { private final String destination; private final byte[] groupId; private final String message; - private final List mentions; - private final List attachments; + private final List attachmentNames; public SyncMessageReceived( + String objectpath, + long timestamp, + String source, + String destination, + byte[] groupId, + String message, + List attachmentNames + ) throws DBusException { + super(objectpath, timestamp, source, destination, groupId, message, attachmentNames); + this.timestamp = timestamp; + this.source = source; + this.destination = destination; + this.groupId = groupId; + this.message = message; + this.attachmentNames = attachmentNames; + } + + public long getTimestamp() { + return timestamp; + } + + public String getSource() { + return source; + } + + public String getDestination() { + return destination; + } + + public byte[] getGroupId() { + return groupId; + } + + public String getMessage() { + return message; + } + + public List getAttachmentNames() { + return attachmentNames; + } + } + + class SyncMessageReceivedV2 extends DBusSignal { + + private final long timestamp; + private final String source; + private final String destination; + private final byte[] groupId; + private final String message; + private final List mentions; + private final List attachments; + + public SyncMessageReceivedV2( String objectpath, long timestamp, String source, @@ -200,7 +315,7 @@ public interface Signal extends DBusInterface { byte[] groupId, String message, List mentions, - List attachments + List attachments ) throws DBusException { super(objectpath, timestamp, source, destination, groupId, message, mentions, attachments); this.timestamp = timestamp; @@ -236,7 +351,7 @@ public interface Signal extends DBusInterface { return mentions; } - public List getAttachments() { + public List getAttachments() { return attachments; } } diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 49172f80..e7afe13b 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -163,7 +163,7 @@ public class App { username = usernames.get(0); } else if (!PhoneNumberFormatter.isValidNumber(username, null)) { - throw new UserErrorException("Invalid username (phone number), make sure you include the country code."); + throw new UserErrorException("Invalid username (phone number), make sure you include a plus sign (+) followed by the country code."); } if (command instanceof RegistrationCommand) { @@ -264,6 +264,7 @@ public class App { try { manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT); } catch (NotRegisteredException e) { + logger.debug("dataPath=" + dataPath + " serviceEnvironment=" + serviceEnvironment, e); throw new UserErrorException("User " + username + " is not registered."); } catch (Throwable e) { logger.debug("Loading state file failed", e); diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 054cc160..07680aed 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -1,6 +1,7 @@ package org.asamk.signal; import org.asamk.Signal; +import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.dbus.DbusMention; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupUtils; @@ -10,6 +11,7 @@ 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.messages.SignalServiceGroup; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import java.util.ArrayList; import java.util.List; @@ -72,13 +74,23 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { || message.getGroupContext().get().getGroupV1Type() == SignalServiceGroup.Type.DELIVER )) { try { - conn.sendMessage(new Signal.MessageReceived(objectPath, + List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); + List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); + conn.sendMessage(new Signal.MessageReceivedV2(objectPath, message.getTimestamp(), getLegacyIdentifier(sender), groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", JsonDbusReceiveMessageHandler.getMentions(message, m), - JsonDbusReceiveMessageHandler.getAttachments(message, m))); + dBusAttachments + )); + conn.sendMessage(new Signal.MessageReceived(objectPath, + message.getTimestamp(), + getLegacyIdentifier(sender), + groupId != null ? groupId : new byte[0], + message.getBody().isPresent() ? message.getBody().get() : "", + JsonDbusReceiveMessageHandler.getAttachmentNames(message, m) + )); } catch (DBusException e) { e.printStackTrace(); } @@ -95,7 +107,9 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { var groupId = getGroupId(message); try { - conn.sendMessage(new Signal.SyncMessageReceived(objectPath, + List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); + List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); + conn.sendMessage(new Signal.SyncMessageReceivedV2(objectPath, transcript.getTimestamp(), getLegacyIdentifier(sender), transcript.getDestination().isPresent() @@ -104,7 +118,18 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", JsonDbusReceiveMessageHandler.getMentions(message, m), - JsonDbusReceiveMessageHandler.getAttachments(message, m))); + dBusAttachments + )); + conn.sendMessage(new Signal.SyncMessageReceived(objectPath, + transcript.getTimestamp(), + getLegacyIdentifier(sender), + transcript.getDestination().isPresent() + ? getLegacyIdentifier(transcript.getDestination().get()) + : "", + groupId != null ? groupId : new byte[0], + message.getBody().isPresent() ? message.getBody().get() : "", + JsonDbusReceiveMessageHandler.getAttachmentNames(message, m) + )); } catch (DBusException e) { e.printStackTrace(); } @@ -119,7 +144,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { .serialize() : null; } - static private List getAttachments(SignalServiceDataMessage message, Manager m) { + static private List getAttachmentNames(SignalServiceDataMessage message, Manager m) { var attachments = new ArrayList(); if (message.getAttachments().isPresent()) { for (var attachment : message.getAttachments().get()) { @@ -141,10 +166,34 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { return mentions; } + + static private List getAttachments(SignalServiceDataMessage message) { + var attachments = new ArrayList(); + if (message.getAttachments().isPresent()) { + for (var attachment : message.getAttachments().get()) { + if (attachment.isPointer()) { + attachments.add(attachment); + } + } + } + return attachments; + } + @Override public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) { super.handleMessage(envelope, content, exception); sendReceivedMessageToDbus(envelope, content, conn, objectPath, m); } + + static private List convertSignalAttachmentsToDbus(List attachments) { + ArrayList dBusAttachments = new ArrayList<>(); + if (!attachments.isEmpty()) { + for (SignalServiceAttachment attachment : attachments) { + DbusAttachment dBusAttachment = new DbusAttachment(attachment); + dBusAttachments.add(dBusAttachment); + } + } + return dBusAttachments; + } } diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 323b6edf..56496973 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -646,6 +646,9 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (pointer.getCaption().isPresent()) { writer.println("Caption: {}", pointer.getCaption().get()); } + if (pointer.getBlurHash().isPresent()) { + writer.println("Blur Hash: {}", pointer.getBlurHash().get()); + } if (pointer.getFileName().isPresent()) { writer.println("Filename: {}", pointer.getFileName().get()); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 7b6f243d..ffd20d9e 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -65,7 +65,12 @@ public class DaemonCommand implements MultiLocalCommand { var objectPath = DbusConfig.getObjectPath(); var t = run(conn, objectPath, m, ignoreAttachments); - conn.requestBusName(DbusConfig.getBusname()); + try { + conn.requestBusName(DbusConfig.getBusname()); + } catch (DBusException e) { + logger.error("Dbus request command failed", e); + throw new UnexpectedErrorException("Dbus request command failed"); + } try { t.join(); diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index c71225e0..4a0231e6 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -81,6 +81,24 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { final var writer = (PlainTextWriter) outputWriter; dbusconnection.addSigHandler(Signal.MessageReceived.class, signal, messageReceived -> { + writer.println("Envelope from: {}", messageReceived.getSender()); + writer.println("Timestamp: {}", DateUtils.formatTimestamp(messageReceived.getTimestamp())); + writer.println("Body: {}", messageReceived.getMessage()); + if (messageReceived.getGroupId().length > 0) { + writer.println("Group info:"); + writer.indentedWriter() + .println("Id: {}", Base64.getEncoder().encodeToString(messageReceived.getGroupId())); + } + if (messageReceived.getAttachmentNames().size() > 0) { + writer.println("Attachments:"); + for (var attachment : messageReceived.getAttachmentNames()) { + writer.println("- Stored plaintext in: {}", attachment); + } + } + writer.println(); + }); + + dbusconnection.addSigHandler(Signal.MessageReceivedV2.class, signal, messageReceived -> { writer.println("Envelope from: {}", messageReceived.getSender()); writer.println("Timestamp: {}", DateUtils.formatTimestamp(messageReceived.getTimestamp())); writer.println("Body: {}", messageReceived.getMessage()); @@ -104,6 +122,26 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand { }); dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, signal, syncReceived -> { + writer.println("Sync Envelope from: {} to: {}", + syncReceived.getSource(), + syncReceived.getDestination()); + writer.println("Timestamp: {}", DateUtils.formatTimestamp(syncReceived.getTimestamp())); + writer.println("Body: {}", syncReceived.getMessage()); + if (syncReceived.getGroupId().length > 0) { + writer.println("Group info:"); + writer.indentedWriter() + .println("Id: {}", Base64.getEncoder().encodeToString(syncReceived.getGroupId())); + } + if (syncReceived.getAttachmentNames().size() > 0) { + writer.println("Attachments:"); + for (var attachment : syncReceived.getAttachmentNames()) { + writer.println("- Stored plaintext in: {}", attachment); + } + } + writer.println(); + }); + + dbusconnection.addSigHandler(Signal.SyncMessageReceivedV2.class, signal, syncReceived -> { writer.println("Sync Envelope from: {} to: {}", syncReceived.getSource(), syncReceived.getDestination()); diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 8459118c..3f6d1551 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -12,6 +12,7 @@ import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; +import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupIdFormatException; @@ -21,9 +22,11 @@ import org.freedesktop.dbus.errors.UnknownObject; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import java.io.IOException; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -91,9 +94,17 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { } } - List attachments = ns.getList("attachment"); - if (attachments == null) { - attachments = List.of(); + List attachmentNames = ns.getList("attachment"); + if (attachmentNames == null) { + attachmentNames = List.of(); + } + + ArrayList dBusAttachments = new ArrayList<>(); + if (!attachmentNames.isEmpty()) { + for (var attachmentName : attachmentNames) { + DbusAttachment dBusAttachment = new DbusAttachment(attachmentName); + dBusAttachments.add(dBusAttachment); + } } if (groupIdString != null) { @@ -105,7 +116,7 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { } try { - var timestamp = signal.sendGroupMessage(messageText, attachments, groupId); + var timestamp = signal.sendGroupMessage(messageText, attachmentNames, groupId); outputResult(timestamp); return; } catch (DBusExecutionException e) { @@ -115,25 +126,25 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { if (isNoteToSelf) { try { - var timestamp = signal.sendNoteToSelfMessage(messageText, attachments); + var timestamp = signal.sendNoteToSelfMessage(messageText, attachmentNames); outputResult(timestamp); return; } catch (Signal.Error.UntrustedIdentity e) { - throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage()); + throw new UntrustedKeyErrorException("Failed to send note to self message: " + e.getMessage()); } catch (DBusExecutionException e) { throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage()); } } try { - var timestamp = signal.sendMessage(messageText, attachments, recipients); + var timestamp = signal.sendMessageV2(messageText, dBusAttachments, recipients); outputResult(timestamp); } catch (UnknownObject e) { throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); } catch (Signal.Error.UntrustedIdentity e) { throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage()); } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage()); + throw new UnexpectedErrorException("Failed to send message, did not find attachment: " + e.getMessage()); } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index 35f530b0..ff6bfc09 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -150,7 +150,6 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { return BaseConfig.PROJECT_VERSION; } - @Override public List listAccounts() { synchronized (receiveThreads) { return receiveThreads.stream() diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index a0f40ada..313c2ae7 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -110,11 +110,38 @@ public class DbusSignalImpl implements Signal { } return results; } + @Override - public long sendMessage(final String message, final List attachments, final String recipient) { + public long sendMessageV2(final String message, final List dBusAttachments, final String recipient) { var recipients = new ArrayList(1); recipients.add(recipient); - return sendMessage(message, attachments, recipients); + return sendMessageV2(message, dBusAttachments, recipients); + } + + @Override + public long sendMessageV2(final String message, final List dBusAttachments, final List recipients) { + try { + ArrayList attachmentNames = new ArrayList<>(); + for (var dBusAttachment : dBusAttachments) { + attachmentNames.add(dBusAttachment.getFileName()); + } + final var results = m.sendMessage(message, attachmentNames, recipients); + checkSendMessageResults(results.first(), results.second()); + return results.first(); + } catch (InvalidNumberException e) { + throw new Error.InvalidNumber(e.getMessage()); + } catch (AttachmentInvalidException e) { + throw new Error.AttachmentInvalid(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } + } + + @Override + public long sendMessage(final String message, final List attachmentNames, final String recipient) { + var recipients = new ArrayList(1); + recipients.add(recipient); + return sendMessage(message, attachmentNames, recipients); } private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException { @@ -157,9 +184,9 @@ public class DbusSignalImpl implements Signal { } @Override - public long sendMessage(final String message, final List attachments, final List recipients) { + public long sendMessage(final String message, final List attachmentNames, final List recipients) { try { - final var results = m.sendMessage(message, attachments, recipients); + final var results = m.sendMessage(message, attachmentNames, recipients); checkSendMessageResults(results.first(), results.second()); return results.first(); } catch (InvalidNumberException e) { @@ -244,10 +271,29 @@ public class DbusSignalImpl implements Signal { @Override public long sendNoteToSelfMessage( - final String message, final List attachments + final String message, final List attachmentNames ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { try { - final var results = m.sendSelfMessage(message, attachments); + final var results = m.sendSelfMessage(message, attachmentNames); + checkSendMessageResult(results.first(), results.second()); + return results.first(); + } catch (AttachmentInvalidException e) { + throw new Error.AttachmentInvalid(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } + } + + @Override + public long sendNoteToSelfMessageV2( + final String message, final List dBusAttachments + ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { + try { + ArrayList attachmentNames = new ArrayList<>(); + for (var dBusAttachment : dBusAttachments) { + attachmentNames.add(dBusAttachment.getFileName()); + } + final var results = m.sendSelfMessage(message, attachmentNames); checkSendMessageResult(results.first(), results.second()); return results.first(); } catch (AttachmentInvalidException e) { @@ -270,9 +316,28 @@ public class DbusSignalImpl implements Signal { } @Override - public long sendGroupMessage(final String message, final List attachments, final byte[] groupId) { + public long sendGroupMessage(final String message, final List attachmentNames, final byte[] groupId) { try { - var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId)); + var results = m.sendGroupMessage(message, attachmentNames, GroupId.unknownVersion(groupId)); + checkSendMessageResults(results.first(), results.second()); + return results.first(); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException e) { + throw new Error.GroupNotFound(e.getMessage()); + } catch (AttachmentInvalidException e) { + throw new Error.AttachmentInvalid(e.getMessage()); + } + } + + @Override + public long sendGroupMessageV2(final String message, final List dBusAttachments, final byte[] groupId) { + try { + ArrayList attachmentNames = new ArrayList<>(); + for (var dBusAttachment : dBusAttachments) { + attachmentNames.add(dBusAttachment.getFileName()); + } + var results = m.sendGroupMessage(message, attachmentNames, GroupId.unknownVersion(groupId)); checkSendMessageResults(results.first(), results.second()); return results.first(); } catch (IOException e) { diff --git a/src/main/java/org/asamk/signal/json/JsonAttachment.java b/src/main/java/org/asamk/signal/json/JsonAttachment.java index a96fc534..3fd2505d 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachment.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.asamk.signal.dbus.DbusAttachment; + class JsonAttachment { @JsonProperty @@ -18,6 +20,33 @@ class JsonAttachment { @JsonProperty final Long size; + @JsonProperty + Integer keyLength; + + @JsonProperty + Integer width; + + @JsonProperty + Integer height; + + @JsonProperty + boolean voiceNote; + + @JsonProperty + String caption; + + @JsonProperty + String relay; + + @JsonProperty + byte[] preview; + + @JsonProperty + String digest; + + @JsonProperty + String blurHash; + JsonAttachment(SignalServiceAttachment attachment) { this.contentType = attachment.getContentType(); @@ -26,6 +55,11 @@ class JsonAttachment { this.id = pointer.getRemoteId().toString(); this.filename = pointer.getFileName().orNull(); this.size = pointer.getSize().transform(Integer::longValue).orNull(); + this.keyLength = pointer.getKey().length; + this.width = pointer.getWidth(); + this.height = pointer.getHeight(); + this.voiceNote = pointer.getVoiceNote(); + if (pointer.getCaption().isPresent()) {this.caption = pointer.getCaption().get();} } else { final var stream = attachment.asStream(); this.id = null; @@ -40,4 +74,17 @@ class JsonAttachment { this.id = null; this.size = null; } + + JsonAttachment(DbusAttachment attachment) { + this.contentType = attachment.getContentType(); + this.id = attachment.getId(); + this.filename = attachment.getFileName(); + this.size = attachment.getFileSize(); + this.keyLength = attachment.getKeyLength(); + this.width = attachment.getWidth(); + this.height = attachment.getHeight(); + this.voiceNote = attachment.getVoiceNote(); + this.caption = attachment.getCaption(); + } + } diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 6dbda978..1586cbe6 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.asamk.Signal; import org.asamk.signal.manager.Manager; +import org.asamk.signal.dbus.DbusAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import java.util.List; @@ -114,6 +115,22 @@ class JsonDataMessage { } public JsonDataMessage(Signal.MessageReceived messageReceived) { + timestamp = messageReceived.getTimestamp(); + message = messageReceived.getMessage(); + groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null; + expiresInSeconds = null; + viewOnce = null; + remoteDelete = null; + reaction = null; // TODO Replace these 5 with the proper commands + quote = null; + mentions = null; + sticker = null; + contacts = null; + attachments = messageReceived.getAttachmentNames().stream().map(JsonAttachment::new).collect(Collectors.toList()); + + } + + public JsonDataMessage(Signal.MessageReceivedV2 messageReceived) { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null; @@ -126,9 +143,25 @@ class JsonDataMessage { sticker = null; contacts = null; attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList()); + } public JsonDataMessage(Signal.SyncMessageReceived messageReceived) { + timestamp = messageReceived.getTimestamp(); + message = messageReceived.getMessage(); + groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null; + expiresInSeconds = null; + viewOnce = null; + remoteDelete = null; + reaction = null; // TODO Replace these 5 with the proper commands + quote = null; + mentions = null; + sticker = null; + contacts = null; + attachments = messageReceived.getAttachmentNames().stream().map(JsonAttachment::new).collect(Collectors.toList()); + } + + public JsonDataMessage(Signal.SyncMessageReceivedV2 messageReceived) { timestamp = messageReceived.getTimestamp(); message = messageReceived.getMessage(); groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null; diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index 31c6b68e..ced6f9f2 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -7,7 +7,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.dbus.DbusAttachment; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Arrays;