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 ef0b404b..183bfdea 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/signal-cli.1.gz b/signal-cli.1.gz new file mode 100644 index 00000000..48c52d9f Binary files /dev/null and b/signal-cli.1.gz differ diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index e7a21b88..22669412 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -4,6 +4,7 @@ import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.interfaces.DBusInterface; import org.freedesktop.dbus.messages.DBusSignal; +import org.asamk.signal.dbus.DbusAttachment; import java.util.List; @@ -13,12 +14,21 @@ import java.util.List; */ public interface Signal extends DBusInterface { - long sendMessage( - String message, List attachments, String recipient + + long sendMessageWithDBusAttachments( + String message, List dBusAttachments, String recipient + ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; + + long sendMessageWithDBusAttachments( + 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( @@ -42,13 +52,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 sendNoteToSelfMessageWithDBusAttachments( + 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 sendGroupMessageWithDBusAttachments( + String message, List dBusAttachments, byte[] groupId ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid; long sendGroupMessageReaction( @@ -59,6 +77,8 @@ public interface Signal extends DBusInterface { void setContactName(String number, String name) throws Error.InvalidNumber; + void setExpirationTimer(final String number, final int expiration) throws Error.InvalidNumber; + void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber; void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound; @@ -101,7 +121,7 @@ public interface Signal extends DBusInterface { private final String sender; private final byte[] groupId; private final String message; - private final List attachments; + private final List dBusAttachments; public MessageReceived( String objectpath, @@ -109,14 +129,14 @@ public interface Signal extends DBusInterface { String sender, byte[] groupId, String message, - List attachments + List dBusAttachments ) throws DBusException { - super(objectpath, timestamp, sender, groupId, message, attachments); + super(objectpath, timestamp, sender, groupId, message, dBusAttachments); this.timestamp = timestamp; this.sender = sender; this.groupId = groupId; this.message = message; - this.attachments = attachments; + this.dBusAttachments = dBusAttachments; } public long getTimestamp() { @@ -135,9 +155,10 @@ public interface Signal extends DBusInterface { return message; } - public List getAttachments() { - return attachments; - } + public List getAttachments() { + return dBusAttachments; + } + } class ReceiptReceived extends DBusSignal { @@ -167,7 +188,7 @@ public interface Signal extends DBusInterface { private final String destination; private final byte[] groupId; private final String message; - private final List attachments; + private final List dBusAttachments; public SyncMessageReceived( String objectpath, @@ -176,15 +197,15 @@ public interface Signal extends DBusInterface { String destination, byte[] groupId, String message, - List attachments + List dBusAttachments ) throws DBusException { - super(objectpath, timestamp, source, destination, groupId, message, attachments); + super(objectpath, timestamp, source, destination, groupId, message, dBusAttachments); this.timestamp = timestamp; this.source = source; this.destination = destination; this.groupId = groupId; this.message = message; - this.attachments = attachments; + this.dBusAttachments = dBusAttachments; } public long getTimestamp() { @@ -207,8 +228,8 @@ public interface Signal extends DBusInterface { return message; } - public List getAttachments() { - return attachments; + public List getAttachments() { + return dBusAttachments; } } diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index 6cc73655..a7e69129 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 5ed6af00..5f7eb940 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.manager.Manager; import org.asamk.signal.manager.groups.GroupUtils; import org.freedesktop.dbus.connections.impl.DBusConnection; @@ -9,6 +10,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; @@ -71,12 +73,15 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { || message.getGroupContext().get().getGroupV1Type() == SignalServiceGroup.Type.DELIVER )) { try { + List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); + List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); conn.sendMessage(new Signal.MessageReceived(objectPath, message.getTimestamp(), getLegacyIdentifier(sender), groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", - JsonDbusReceiveMessageHandler.getAttachments(message, m))); + dBusAttachments + )); } catch (DBusException e) { e.printStackTrace(); } @@ -93,6 +98,8 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { var groupId = getGroupId(message); try { + List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); + List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); conn.sendMessage(new Signal.SyncMessageReceived(objectPath, transcript.getTimestamp(), getLegacyIdentifier(sender), @@ -101,7 +108,8 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { : "", groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", - JsonDbusReceiveMessageHandler.getAttachments(message, m))); + dBusAttachments + )); } catch (DBusException e) { e.printStackTrace(); } @@ -116,7 +124,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()) { @@ -128,10 +136,34 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { return attachments; } + + 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 7988c8ef..dfd15c24 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/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index af512def..26b2f543 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -11,6 +11,8 @@ 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.manager.groups.GroupIdFormatException; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; @@ -18,9 +20,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; public class SendCommand implements DbusCommand { @@ -87,13 +91,21 @@ public class SendCommand implements DbusCommand { } } - List attachments = ns.getList("attachment"); - if (attachments == null) { - attachments = List.of(); + List attachmentNames = ns.getList("attachment"); + if (attachmentNames == null) { + attachmentNames = List.of(); } final var writer = (PlainTextWriterImpl) outputWriter; + ArrayList dBusAttachments = new ArrayList<>(); + if (!attachmentNames.isEmpty()) { + for (var attachmentName : attachmentNames) { + DbusAttachment dBusAttachment = new DbusAttachment(attachmentName); + dBusAttachments.add(dBusAttachment); + } + } + if (groupIdString != null) { byte[] groupId; try { @@ -103,7 +115,7 @@ public class SendCommand implements DbusCommand { } try { - var timestamp = signal.sendGroupMessage(messageText, attachments, groupId); + var timestamp = signal.sendGroupMessage(messageText, attachmentNames, groupId); writer.println("{}", timestamp); return; } catch (DBusExecutionException e) { @@ -113,25 +125,26 @@ public class SendCommand implements DbusCommand { if (isNoteToSelf) { try { - var timestamp = signal.sendNoteToSelfMessage(messageText, attachments); + var timestamp = signal.sendNoteToSelfMessage(messageText, attachmentNames); writer.println("{}", 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.sendMessageWithDBusAttachments(messageText, dBusAttachments, recipients); writer.println("{}", 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/DbusAttachment.java b/src/main/java/org/asamk/signal/dbus/DbusAttachment.java new file mode 100644 index 00000000..b5060339 --- /dev/null +++ b/src/main/java/org/asamk/signal/dbus/DbusAttachment.java @@ -0,0 +1,238 @@ +package org.asamk.signal.dbus; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; +import org.whispersystems.signalservice.api.util.StreamDetails; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.annotations.Position; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + +import org.asamk.signal.manager.util.Utils; +import org.freedesktop.dbus.Struct; + +public final class DbusAttachment extends Struct +{ + @Position(0) + private String contentType; + @Position(1) + private String fileName; + @Position(2) + private String id; + @Position(3) + private Long size; + @Position(4) + private Integer keyLength; + @Position(5) + private boolean voiceNote; + @Position(6) + private Integer width; + @Position(7) + private Integer height; + @Position(8) + private String caption; + @Position(9) + private String blurHash; + +/* + * API = 2.15.3 from https://github.com/Turasa/libsignal-service-java (nonstandard) + public SignalServiceAttachmentStream(InputStream inputStream, + String contentType, + long length, + Optional fileName, + boolean voiceNote, + boolean borderless, + boolean gif, //nonstandard + Optional preview, + int width, + int height, + long uploadTimestamp, + Optional caption, + Optional blurHash, + ProgressListener listener, //Android OS + CancellationSignal cancellationSignal, //Android OS, Signal developers misspelled class name + Optional resumableUploadSpec) + + + public SignalServiceAttachmentPointer(int cdnNumber, + SignalServiceAttachmentRemoteId remoteId, + String contentType, + byte[] key, + Optional size, + Optional preview, + int width, + int height, + Optional digest, + Optional fileName, + boolean voiceNote, + boolean borderless, + Optional caption, + Optional blurHash, + long uploadTimestamp) + +other stuff : + private long id; // used by v2 attachments, see note + private int keyLength; //TODO: if you're going to do that, probably should have previewLength and digestLength + +notes : +"size" appears to be the same as "length" but is int rather than long +"length" represents file size (or stream/attachment size) +"preview" is also known as "thumbnail" + +from SignalServiceAttachmentRemoteId.java : + * Represents a signal service attachment identifier. This can be either a CDN key or a long, but + * not both at once. Attachments V2 used a long as an attachment identifier. This lacks sufficient + * entropy to reduce the likelihood of any two uploads going to the same location within a 30-day + * window. Attachments V3 uses an opaque string as an attachment identifier which provides more + * flexibility in the amount of entropy present. + + */ + + public DbusAttachment(SignalServiceAttachment attachment) { + this.contentType = attachment.getContentType(); + + if (attachment.isPointer()) { + final var pointer = attachment.asPointer(); + this.id = pointer.getRemoteId().toString(); + this.fileName = pointer.getFileName().orNull(); + if (this.fileName == null) { + this.fileName = ""; + } + this.size = pointer.getSize().transform(Integer::longValue).orNull(); + if (this.size == null) { + this.size = 0L; + } + this.setKeyLength(pointer.getKey().length); + this.setWidth(pointer.getWidth()); + this.setHeight(pointer.getHeight()); + this.setVoiceNote(pointer.getVoiceNote()); + if (pointer.getCaption().isPresent()) { + this.setCaption(pointer.getCaption().get()); + } else { + this.setCaption(""); + } + this.setBlurHash(""); + } else { + final var stream = attachment.asStream(); + this.fileName = stream.getFileName().orNull(); + if (this.fileName == null) { + this.fileName = ""; + } + this.id = ""; + this.size = stream.getLength(); + this.setKeyLength(0); + this.setWidth(0); + this.setHeight(0); + this.setVoiceNote(false); + this.setCaption(""); + this.setBlurHash(""); + } + } + + public DbusAttachment(String fileName) { + this.contentType = "application/octet-stream"; + try { + final File file = new File(fileName); + this.contentType = Utils.getFileMimeType(file, "application/octet-stream"); + this.size = file.length(); + } catch (IOException e) { + //no such file, try URL + try { + final URL aURL = new URL(fileName); + this.contentType = aURL.openConnection().getContentType(); + this.size = aURL.openConnection().getContentLengthLong(); + } catch (IOException f) { + f.printStackTrace(); + } + } + this.fileName = fileName; + this.id = ""; + this.setKeyLength(0); + this.setWidth(0); + this.setHeight(0); + this.setVoiceNote(false); + this.setCaption(""); + this.setBlurHash(""); + } + + public String getContentType() { + return contentType; + } + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + + public String getFileName() { + return fileName; + } + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Long getFileSize() { + return size; + } + public void setFileSize(Long size) { + this.size = size; + } + + public Integer getKeyLength() { + return keyLength; + } + public void setKeyLength(Integer keyLength) { + this.keyLength = keyLength; + } + + public Integer getWidth() { + return width; + } + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + public void setHeight(Integer height) { + this.height = height; + } + + public boolean isVoiceNote() { + return voiceNote; + } + public boolean getVoiceNote() { + return voiceNote; + } + public void setVoiceNote(boolean voiceNote) { + this.voiceNote = voiceNote; + } + + public String getCaption() { + return caption; + } + public void setCaption(String caption) { + this.caption = caption; + } + + public String getBlurHash() { + return blurHash; + } + public void setBlurHash(String blurHash) { + this.blurHash = blurHash; + } + +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index b88aa81e..ae79c315 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -2,6 +2,8 @@ package org.asamk.signal.dbus; import org.asamk.Signal; import org.asamk.signal.BaseConfig; +import org.asamk.signal.PlainTextWriterImpl; +import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotMasterDeviceException; @@ -52,10 +54,36 @@ public class DbusSignalImpl implements Signal { } @Override - public long sendMessage(final String message, final List attachments, final String recipient) { + public long sendMessageWithDBusAttachments(final String message, final List dBusAttachments, final String recipient) { var recipients = new ArrayList(1); recipients.add(recipient); - return sendMessage(message, attachments, recipients); + return sendMessageWithDBusAttachments(message, dBusAttachments, recipients); + } + + @Override + public long sendMessageWithDBusAttachments(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 { @@ -98,9 +126,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) { @@ -185,10 +213,10 @@ 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) { @@ -198,6 +226,25 @@ public class DbusSignalImpl implements Signal { } } + @Override + public long sendNoteToSelfMessageWithDBusAttachments( + 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) { + throw new Error.AttachmentInvalid(e.getMessage()); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } + } + @Override public void sendEndSessionMessage(final List recipients) { try { @@ -211,9 +258,9 @@ 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) { @@ -225,6 +272,26 @@ public class DbusSignalImpl implements Signal { } } + @Override + public long sendGroupMessageWithDBusAttachments(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) { + 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 sendGroupMessageReaction( final String emoji, @@ -272,6 +339,17 @@ public class DbusSignalImpl implements Signal { } } + @Override + public void setExpirationTimer(final String number, final int expiration) { + try { + m.setExpirationTimer(number, expiration); + } catch (IOException e) { + throw new Error.Failure(e.getMessage()); + } catch (InvalidNumberException e) { + throw new Error.InvalidNumber(e.getMessage()); + } + } + @Override public void setContactBlocked(final String number, final boolean blocked) { try { 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..f2c702f8 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; @@ -126,6 +127,7 @@ class JsonDataMessage { sticker = null; contacts = null; attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList()); + } public JsonDataMessage(Signal.SyncMessageReceived messageReceived) { diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index a9d2bb8f..5001c4ae 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -1,8 +1,14 @@ package org.asamk.signal.util; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupIdFormatException; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.push.SignalServiceAddress; public class Util {