]> nmode's Git Repositories - signal-cli/commitdiff
Implement sending link previews
authorAsamK <asamk@gmx.de>
Wed, 25 May 2022 21:23:33 +0000 (23:23 +0200)
committerAsamK <asamk@gmx.de>
Wed, 25 May 2022 21:23:33 +0000 (23:23 +0200)
Fixes #276

graalvm-config-dir/reflect-config.json
lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java
lib/src/main/java/org/asamk/signal/manager/api/Message.java
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/commands/SendCommand.java
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
src/main/java/org/asamk/signal/json/JsonDataMessage.java
src/main/java/org/asamk/signal/json/JsonPreview.java [new file with mode: 0644]

index 503f17f56aee257d133953d285a583b206db5877..62612d442449d28f608e0b6160a35f3da30368ba 100644 (file)
     {"name":"receipt","parameterTypes":[] }
   ]
 },
     {"name":"receipt","parameterTypes":[] }
   ]
 },
+{
+  "name":"org.asamk.signal.json.JsonPreview",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
+  "methods":[
+    {"name":"date","parameterTypes":[] }, 
+    {"name":"description","parameterTypes":[] }, 
+    {"name":"image","parameterTypes":[] }, 
+    {"name":"title","parameterTypes":[] }, 
+    {"name":"url","parameterTypes":[] }
+  ]
+},
 {
   "name":"org.asamk.signal.json.JsonQuote",
   "allDeclaredFields":true,
 {
   "name":"org.asamk.signal.json.JsonQuote",
   "allDeclaredFields":true,
     {"name":"bitField0_"}, 
     {"name":"bodyRanges_"}, 
     {"name":"id_"}, 
     {"name":"bitField0_"}, 
     {"name":"bodyRanges_"}, 
     {"name":"id_"}, 
-    {"name":"text_"}
+    {"name":"text_"}, 
+    {"name":"type_"}
   ]
 },
 {
   ]
 },
 {
index 40bd7240a9da9485f22a4880adb65ce39c7ea161..b5dcb5e0de851ad1e4c4a84b0f8a71bb373f390c 100644 (file)
@@ -65,6 +65,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
 import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.SignalSessionLock;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
+import org.whispersystems.signalservice.api.messages.SignalServicePreview;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
 import org.whispersystems.signalservice.api.push.ACI;
 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
 import org.whispersystems.signalservice.api.push.ACI;
@@ -551,9 +552,8 @@ class ManagerImpl implements Manager {
             final SignalServiceDataMessage.Builder messageBuilder, final Message message
     ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {
         messageBuilder.withBody(message.messageText());
             final SignalServiceDataMessage.Builder messageBuilder, final Message message
     ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {
         messageBuilder.withBody(message.messageText());
-        final var attachments = message.attachments();
-        if (attachments != null) {
-            messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(attachments));
+        if (message.attachments().size() > 0) {
+            messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments()));
         }
         if (message.mentions().size() > 0) {
             messageBuilder.withMentions(resolveMentions(message.mentions()));
         }
         if (message.mentions().size() > 0) {
             messageBuilder.withMentions(resolveMentions(message.mentions()));
@@ -592,6 +592,19 @@ class ManagerImpl implements Manager {
                     manifestSticker.emoji(),
                     AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty())));
         }
                     manifestSticker.emoji(),
                     AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty())));
         }
+        if (message.previews().size() > 0) {
+            final var previews = new ArrayList<SignalServicePreview>(message.previews().size());
+            for (final var p : message.previews()) {
+                final var image = p.image().isPresent() ? context.getAttachmentHelper()
+                        .uploadAttachment(p.image().get()) : null;
+                previews.add(new SignalServicePreview(p.url(),
+                        p.title(),
+                        p.description(),
+                        0,
+                        Optional.ofNullable(image)));
+            }
+            messageBuilder.withPreviews(previews);
+        }
     }
 
     private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException {
     }
 
     private ArrayList<SignalServiceDataMessage.Mention> resolveMentions(final List<Message.Mention> mentionList) throws UnregisteredRecipientException {
index 3df110cdd8464848c26411a09be2f1e3c0a58ae4..1e76faeaa041b8b3795c94dd5ef8537fda7cc120 100644 (file)
@@ -8,7 +8,8 @@ public record Message(
         List<String> attachments,
         List<Mention> mentions,
         Optional<Quote> quote,
         List<String> attachments,
         List<Mention> mentions,
         Optional<Quote> quote,
-        Optional<Sticker> sticker
+        Optional<Sticker> sticker,
+        List<Preview> previews
 ) {
 
     public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
 ) {
 
     public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
@@ -16,4 +17,6 @@ public record Message(
     public record Quote(long timestamp, RecipientIdentifier.Single author, String message, List<Mention> mentions) {}
 
     public record Sticker(byte[] packId, int stickerId) {}
     public record Quote(long timestamp, RecipientIdentifier.Single author, String message, List<Mention> mentions) {}
 
     public record Sticker(byte[] packId, int stickerId) {}
+
+    public record Preview(String url, String title, String description, Optional<String> image) {}
 }
 }
index 9b54635e5cdac53b8f36de1517f3c4fdbf925483..1ccb067c279fa73ee22532e52e4516d9b9942841 100644 (file)
@@ -253,6 +253,20 @@ Specify the message of the original message.
 *--quote-mention*::
 Specify the mentions of the original message (same format as `--mention`).
 
 *--quote-mention*::
 Specify the mentions of the original message (same format as `--mention`).
 
+*--preview-url*::
+Specify the url for the link preview.
+The same url must also appear in the message body, otherwise the preview won't be
+displayed by the apps.
+
+*--preview-title*::
+Specify the title for the link preview (mandatory).
+
+*--preview-description*::
+Specify the description for the link preview (optional).
+
+*--preview-image*::
+Specify the image file for the link preview (optional).
+
 *-e*, *--end-session*::
 Clear session state and send end session message.
 
 *-e*, *--end-session*::
 Clear session state and send end session message.
 
index 5e81047ae520ccd5f175523a83d0b47e4661a81c..4ad75e956dd322dfc405c4fbc0ebc16fc82c4c81 100644 (file)
@@ -72,6 +72,11 @@ public class SendCommand implements JsonRpcLocalCommand {
                 .nargs("*")
                 .help("Quote with mention of another group member (syntax: start:length:recipientNumber)");
         subparser.addArgument("--sticker").help("Send a sticker (syntax: stickerPackId:stickerId)");
                 .nargs("*")
                 .help("Quote with mention of another group member (syntax: start:length:recipientNumber)");
         subparser.addArgument("--sticker").help("Send a sticker (syntax: stickerPackId:stickerId)");
+        subparser.addArgument("--preview-url")
+                .help("Specify the url for the link preview (the same url must also appear in the message body).");
+        subparser.addArgument("--preview-title").help("Specify the title for the link preview (mandatory).");
+        subparser.addArgument("--preview-description").help("Specify the description for the link preview (optional).");
+        subparser.addArgument("--preview-image").help("Specify the image file for the link preview (optional).");
     }
 
     @Override
     }
 
     @Override
@@ -146,12 +151,27 @@ public class SendCommand implements JsonRpcLocalCommand {
             quote = null;
         }
 
             quote = null;
         }
 
+        final List<Message.Preview> previews;
+        String previewUrl = ns.getString("preview-url");
+        if (previewUrl != null) {
+            String previewTitle = ns.getString("preview-title");
+            String previewDescription = ns.getString("preview-description");
+            String previewImage = ns.getString("preview-image");
+            previews = List.of(new Message.Preview(previewUrl,
+                    Optional.ofNullable(previewTitle).orElse(""),
+                    Optional.ofNullable(previewDescription).orElse(""),
+                    Optional.ofNullable(previewImage)));
+        } else {
+            previews = List.of();
+        }
+
         try {
             var results = m.sendMessage(new Message(messageText == null ? "" : messageText,
                     attachments,
                     mentions,
                     Optional.ofNullable(quote),
         try {
             var results = m.sendMessage(new Message(messageText == null ? "" : messageText,
                     attachments,
                     mentions,
                     Optional.ofNullable(quote),
-                    Optional.ofNullable(sticker)), recipientIdentifiers);
+                    Optional.ofNullable(sticker),
+                    previews), recipientIdentifiers);
             outputResult(outputWriter, results);
         } catch (AttachmentInvalidException | IOException e) {
             throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
             outputResult(outputWriter, results);
         } catch (AttachmentInvalidException | IOException e) {
             throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
index 18b53eeabf81703088c6d0810d67d96accc747df..ce35ef1442f8006c125d25acab6d9f7d113c06d8 100644 (file)
@@ -216,7 +216,8 @@ public class DbusSignalImpl implements Signal {
                             attachments,
                             List.of(),
                             Optional.empty(),
                             attachments,
                             List.of(),
                             Optional.empty(),
-                            Optional.empty()),
+                            Optional.empty(),
+                            List.of()),
                     getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
                             .map(RecipientIdentifier.class::cast)
                             .collect(Collectors.toSet()));
                     getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
                             .map(RecipientIdentifier.class::cast)
                             .collect(Collectors.toSet()));
@@ -367,7 +368,8 @@ public class DbusSignalImpl implements Signal {
                     attachments,
                     List.of(),
                     Optional.empty(),
                     attachments,
                     List.of(),
                     Optional.empty(),
-                    Optional.empty()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
+                    Optional.empty(),
+                    List.of()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (AttachmentInvalidException e) {
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (AttachmentInvalidException e) {
@@ -408,7 +410,8 @@ public class DbusSignalImpl implements Signal {
                     attachments,
                     List.of(),
                     Optional.empty(),
                     attachments,
                     List.of(),
                     Optional.empty(),
-                    Optional.empty()), Set.of(getGroupRecipientIdentifier(groupId)));
+                    Optional.empty(),
+                    List.of()), Set.of(getGroupRecipientIdentifier(groupId)));
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (IOException | InvalidStickerException e) {
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (IOException | InvalidStickerException e) {
index 46328466f92b8cda9507a45894dc0efd67eed6f2..74c07cd707d63d3a451d24a2e64e1cc53885459b 100644 (file)
@@ -15,6 +15,7 @@ record JsonDataMessage(
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonQuote quote,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonPayment payment,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonQuote quote,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonPayment payment,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions,
+        @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonPreview> previews,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonAttachment> attachments,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonAttachment> attachments,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete,
@@ -36,6 +37,10 @@ record JsonDataMessage(
                 .stream()
                 .map(JsonMention::from)
                 .toList() : null;
                 .stream()
                 .map(JsonMention::from)
                 .toList() : null;
+        final var previews = dataMessage.previews().size() > 0 ? dataMessage.previews()
+                .stream()
+                .map(JsonPreview::from)
+                .toList() : null;
         final var remoteDelete = dataMessage.remoteDeleteId().isPresent()
                 ? new JsonRemoteDelete(dataMessage.remoteDeleteId().get())
                 : null;
         final var remoteDelete = dataMessage.remoteDeleteId().isPresent()
                 ? new JsonRemoteDelete(dataMessage.remoteDeleteId().get())
                 : null;
@@ -57,6 +62,7 @@ record JsonDataMessage(
                 quote,
                 payment,
                 mentions,
                 quote,
                 payment,
                 mentions,
+                previews,
                 attachments,
                 sticker,
                 remoteDelete,
                 attachments,
                 sticker,
                 remoteDelete,
diff --git a/src/main/java/org/asamk/signal/json/JsonPreview.java b/src/main/java/org/asamk/signal/json/JsonPreview.java
new file mode 100644 (file)
index 0000000..c5e0d49
--- /dev/null
@@ -0,0 +1,13 @@
+package org.asamk.signal.json;
+
+import org.asamk.signal.manager.api.MessageEnvelope;
+
+public record JsonPreview(String url, String title, String description, JsonAttachment image) {
+
+    static JsonPreview from(MessageEnvelope.Data.Preview preview) {
+        return new JsonPreview(preview.url(),
+                preview.title(),
+                preview.description(),
+                preview.image().map(JsonAttachment::from).orElse(null));
+    }
+}