]> nmode's Git Repositories - signal-cli/commitdiff
Implement textStyles for sending and receiving
authorAsamK <asamk@gmx.de>
Sat, 20 May 2023 10:47:35 +0000 (12:47 +0200)
committerAsamK <asamk@gmx.de>
Sat, 20 May 2023 10:49:57 +0000 (12:49 +0200)
Fixes #1250

13 files changed:
CHANGELOG.md
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
lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
lib/src/main/java/org/asamk/signal/manager/api/TextStyle.java [new file with mode: 0644]
man/signal-cli.1.adoc
src/main/java/org/asamk/signal/ReceiveMessageHandler.java
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/JsonQuote.java
src/main/java/org/asamk/signal/json/JsonTextStyle.java [new file with mode: 0644]

index fe72cadfc8a5ad4ebd8dcae498a00c84f1f0b8de..6419350ee1c12c3212e38c989ec83d1af7c7165d 100644 (file)
@@ -3,6 +3,9 @@
 ## [Unreleased]
 **Attention**: Now requires native libsignal-client version 0.24.0
 
+### Added
+- New `--text-style` and `--quote-text-style` flags for `send` command
+
 ## [0.11.10] - 2023-05-11
 **Attention**: Now requires native libsignal-client version 0.23.1
 
index 5ace3d390023d0534f455279d7b80d0892f6ac21..240edebd2859f2c9c2885f671a2f91a2ec0f6c14 100644 (file)
     {"name":"remoteDelete","parameterTypes":[] }, 
     {"name":"sticker","parameterTypes":[] }, 
     {"name":"storyContext","parameterTypes":[] }, 
+    {"name":"textStyles","parameterTypes":[] }, 
     {"name":"timestamp","parameterTypes":[] }, 
     {"name":"viewOnce","parameterTypes":[] }
   ]
     {"name":"authorUuid","parameterTypes":[] }, 
     {"name":"id","parameterTypes":[] }, 
     {"name":"mentions","parameterTypes":[] }, 
-    {"name":"text","parameterTypes":[] }
+    {"name":"text","parameterTypes":[] }, 
+    {"name":"textStyles","parameterTypes":[] }
   ]
 },
 {
     {"name":"destinationUuid","parameterTypes":[] }
   ]
 },
+{
+  "name":"org.asamk.signal.json.JsonTextStyle",
+  "allDeclaredFields":true,
+  "queryAllDeclaredMethods":true,
+  "queryAllDeclaredConstructors":true,
+  "methods":[
+    {"name":"length","parameterTypes":[] }, 
+    {"name":"start","parameterTypes":[] }, 
+    {"name":"style","parameterTypes":[] }
+  ]
+},
 {
   "name":"org.asamk.signal.json.JsonTypingMessage",
   "allDeclaredFields":true,
index 1cc0629be198b449e0d985eb88be806b6b6cc0be..6895da639566e31e8f9f0bad5f7bc1447429c8cd 100644 (file)
@@ -40,6 +40,7 @@ import org.asamk.signal.manager.api.SendMessageResults;
 import org.asamk.signal.manager.api.StickerPackId;
 import org.asamk.signal.manager.api.StickerPackInvalidException;
 import org.asamk.signal.manager.api.StickerPackUrl;
+import org.asamk.signal.manager.api.TextStyle;
 import org.asamk.signal.manager.api.TypingAction;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.api.UpdateGroup;
@@ -618,6 +619,9 @@ class ManagerImpl implements Manager {
         if (message.mentions().size() > 0) {
             messageBuilder.withMentions(resolveMentions(message.mentions()));
         }
+        if (message.textStyles().size() > 0) {
+            messageBuilder.withBodyRanges(message.textStyles().stream().map(TextStyle::toBodyRange).toList());
+        }
         if (message.quote().isPresent()) {
             final var quote = message.quote().get();
             messageBuilder.withQuote(new SignalServiceDataMessage.Quote(quote.timestamp(),
@@ -628,7 +632,7 @@ class ManagerImpl implements Manager {
                     List.of(),
                     resolveMentions(quote.mentions()),
                     SignalServiceDataMessage.Quote.Type.NORMAL,
-                    List.of()));
+                    quote.textStyles().stream().map(TextStyle::toBodyRange).toList()));
         }
         if (message.sticker().isPresent()) {
             final var sticker = message.sticker().get();
index aba79cc56325a88983826fca984ef87a5d962b73..ac9e4999b6b717bd99987fe2e15f8081207969fa 100644 (file)
@@ -10,12 +10,19 @@ public record Message(
         Optional<Quote> quote,
         Optional<Sticker> sticker,
         List<Preview> previews,
-        Optional<StoryReply> storyReply
+        Optional<StoryReply> storyReply,
+        List<TextStyle> textStyles
 ) {
 
     public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
 
-    public record Quote(long timestamp, RecipientIdentifier.Single author, String message, List<Mention> mentions) {}
+    public record Quote(
+            long timestamp,
+            RecipientIdentifier.Single author,
+            String message,
+            List<Mention> mentions,
+            List<TextStyle> textStyles
+    ) {}
 
     public record Sticker(byte[] packId, int stickerId) {}
 
index a8f008754c3daa9c82afba9cb82f8af79c0b85b0..aba521796f47637a7e1ac0f171eb2d9dfc177a85 100644 (file)
@@ -515,32 +515,6 @@ public record MessageEnvelope(
             }
         }
 
-        public record TextStyle(Style style, int start, int length) {
-
-            public enum Style {
-                NONE,
-                BOLD,
-                ITALIC,
-                SPOILER,
-                STRIKETHROUGH,
-                MONOSPACE;
-
-                static Style from(BodyRange.Style style) {
-                    return switch (style) {
-                        case NONE -> NONE;
-                        case BOLD -> BOLD;
-                        case ITALIC -> ITALIC;
-                        case SPOILER -> SPOILER;
-                        case STRIKETHROUGH -> STRIKETHROUGH;
-                        case MONOSPACE -> MONOSPACE;
-                    };
-                }
-            }
-
-            static TextStyle from(BodyRange bodyRange) {
-                return new TextStyle(Style.from(bodyRange.getStyle()), bodyRange.getStart(), bodyRange.getLength());
-            }
-        }
     }
 
     public record Edit(long targetSentTimestamp, Data dataMessage) {
diff --git a/lib/src/main/java/org/asamk/signal/manager/api/TextStyle.java b/lib/src/main/java/org/asamk/signal/manager/api/TextStyle.java
new file mode 100644 (file)
index 0000000..4831630
--- /dev/null
@@ -0,0 +1,61 @@
+package org.asamk.signal.manager.api;
+
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
+
+public record TextStyle(Style style, int start, int length) {
+
+    public enum Style {
+        NONE,
+        BOLD,
+        ITALIC,
+        SPOILER,
+        STRIKETHROUGH,
+        MONOSPACE;
+
+        static Style fromInternal(SignalServiceProtos.BodyRange.Style style) {
+            return switch (style) {
+                case NONE -> NONE;
+                case BOLD -> BOLD;
+                case ITALIC -> ITALIC;
+                case SPOILER -> SPOILER;
+                case STRIKETHROUGH -> STRIKETHROUGH;
+                case MONOSPACE -> MONOSPACE;
+            };
+        }
+
+        public static Style from(String style) {
+            return switch (style) {
+                case "NONE" -> NONE;
+                case "BOLD" -> BOLD;
+                case "ITALIC" -> ITALIC;
+                case "SPOILER" -> SPOILER;
+                case "STRIKETHROUGH" -> STRIKETHROUGH;
+                case "MONOSPACE" -> MONOSPACE;
+                default -> null;
+            };
+        }
+
+        SignalServiceProtos.BodyRange.Style toBodyRangeStyle() {
+            return switch (this) {
+                case NONE -> SignalServiceProtos.BodyRange.Style.NONE;
+                case BOLD -> SignalServiceProtos.BodyRange.Style.BOLD;
+                case ITALIC -> SignalServiceProtos.BodyRange.Style.ITALIC;
+                case SPOILER -> SignalServiceProtos.BodyRange.Style.SPOILER;
+                case STRIKETHROUGH -> SignalServiceProtos.BodyRange.Style.STRIKETHROUGH;
+                case MONOSPACE -> SignalServiceProtos.BodyRange.Style.MONOSPACE;
+            };
+        }
+    }
+
+    static TextStyle from(SignalServiceProtos.BodyRange bodyRange) {
+        return new TextStyle(Style.fromInternal(bodyRange.getStyle()), bodyRange.getStart(), bodyRange.getLength());
+    }
+
+    public SignalServiceProtos.BodyRange toBodyRange() {
+        return SignalServiceProtos.BodyRange.newBuilder()
+                .setStart(this.start())
+                .setLength(this.length())
+                .setStyle(this.style().toBodyRangeStyle())
+                .build();
+    }
+}
index 57a10e5aa0d73a48295b6842da26638507887617..56787aee96e6e9cf3d25870c432d2516dcd3d907 100644 (file)
@@ -251,6 +251,12 @@ e.g.: `--sticker 00abac3bc18d7f599bff2325dc306d43:2`
 Mention another group member (syntax: start:length:recipientNumber) In the apps the mention replaces part of the message text, which is specified by the start and length values.
 e.g.: `-m "Hi X!" --mention "3:1:+123456789"`
 
+*--text-style*::
+Style parts of the message text (syntax: start:length:STYLE).
+Where STYLE is one of: BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE
+
+e.g.: `-m "Something BIG!" --mention "10:3:BOLD"`
+
 *--quote-timestamp*::
 Specify the timestamp of a previous message with the recipient or group to add a quote to the new message.
 
@@ -263,6 +269,9 @@ Specify the message of the original message.
 *--quote-mention*::
 Specify the mentions of the original message (same format as `--mention`).
 
+*--quote-text-style*::
+Style parts of the original message text (same format as `--text-style`).
+
 *--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.
index 1f4253acd68304389f1ef0a8b57c40ab4cac470b..c299ae3892b222403c77bf37da0f704ea378dc4d 100644 (file)
@@ -4,6 +4,7 @@ import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.api.MessageEnvelope;
 import org.asamk.signal.manager.api.RecipientAddress;
 import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.api.TextStyle;
 import org.asamk.signal.manager.api.UntrustedIdentityException;
 import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.output.PlainTextWriter;
@@ -573,7 +574,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
     }
 
     private void printTextStyle(
-            PlainTextWriter writer, MessageEnvelope.Data.TextStyle textStyle
+            PlainTextWriter writer, TextStyle textStyle
     ) {
         writer.println("- {}: {} (length: {})", textStyle.style().name(), textStyle.start(), textStyle.length());
     }
index 9534f182a2ccbc4a4865dd63ffbad090048170d9..c10817172e23ee5623de8e7bfb6b010ec63328a7 100644 (file)
@@ -12,6 +12,7 @@ import org.asamk.signal.manager.api.AttachmentInvalidException;
 import org.asamk.signal.manager.api.InvalidStickerException;
 import org.asamk.signal.manager.api.Message;
 import org.asamk.signal.manager.api.RecipientIdentifier;
+import org.asamk.signal.manager.api.TextStyle;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.groups.GroupNotFoundException;
 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
@@ -66,6 +67,9 @@ public class SendCommand implements JsonRpcLocalCommand {
         subparser.addArgument("--mention")
                 .nargs("*")
                 .help("Mention another group member (syntax: start:length:recipientNumber)");
+        subparser.addArgument("--text-style")
+                .nargs("*")
+                .help("Style parts of the message text (syntax: start:length:STYLE)");
         subparser.addArgument("--quote-timestamp")
                 .type(long.class)
                 .help("Specify the timestamp of a previous message with the recipient or group to add a quote to the new message.");
@@ -74,6 +78,9 @@ public class SendCommand implements JsonRpcLocalCommand {
         subparser.addArgument("--quote-mention")
                 .nargs("*")
                 .help("Quote with mention of another group member (syntax: start:length:recipientNumber)");
+        subparser.addArgument("--quote-text-style")
+                .nargs("*")
+                .help("Quote with style parts of the message text (syntax: start:length:STYLE)");
         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).");
@@ -146,6 +153,9 @@ public class SendCommand implements JsonRpcLocalCommand {
         List<String> mentionStrings = ns.getList("mention");
         final var mentions = mentionStrings == null ? List.<Message.Mention>of() : parseMentions(m, mentionStrings);
 
+        List<String> textStyleStrings = ns.getList("text-style");
+        final var textStyles = textStyleStrings == null ? List.<TextStyle>of() : parseTextStyles(textStyleStrings);
+
         final Message.Quote quote;
         final var quoteTimestamp = ns.getLong("quote-timestamp");
         if (quoteTimestamp != null) {
@@ -155,10 +165,15 @@ public class SendCommand implements JsonRpcLocalCommand {
             final var quoteMentions = quoteMentionStrings == null
                     ? List.<Message.Mention>of()
                     : parseMentions(m, quoteMentionStrings);
+            List<String> quoteTextStyleStrings = ns.getList("quote-text-style");
+            final var quoteTextStyles = quoteTextStyleStrings == null
+                    ? List.<TextStyle>of()
+                    : parseTextStyles(quoteTextStyleStrings);
             quote = new Message.Quote(quoteTimestamp,
                     CommandUtil.getSingleRecipientIdentifier(quoteAuthor, m.getSelfNumber()),
                     quoteMessage == null ? "" : quoteMessage,
-                    quoteMentions);
+                    quoteMentions,
+                    quoteTextStyles);
         } else {
             quote = null;
         }
@@ -201,7 +216,8 @@ public class SendCommand implements JsonRpcLocalCommand {
                     Optional.ofNullable(quote),
                     Optional.ofNullable(sticker),
                     previews,
-                    Optional.ofNullable((storyReply)));
+                    Optional.ofNullable((storyReply)),
+                    textStyles);
             var results = editTimestamp != null
                     ? m.sendEditMessage(message, recipientIdentifiers, editTimestamp)
                     : m.sendMessage(message, recipientIdentifiers);
@@ -237,6 +253,30 @@ public class SendCommand implements JsonRpcLocalCommand {
         return mentions;
     }
 
+    private List<TextStyle> parseTextStyles(
+            final List<String> textStylesStrings
+    ) throws UserErrorException {
+        List<TextStyle> textStyles;
+        final Pattern textStylePattern = Pattern.compile("(\\d+):(\\d+):(.+)");
+        textStyles = new ArrayList<>();
+        for (final var textStyle : textStylesStrings) {
+            final var matcher = textStylePattern.matcher(textStyle);
+            if (!matcher.matches()) {
+                throw new UserErrorException("Invalid textStyle syntax ("
+                        + textStyle
+                        + ") expected 'start:length:STYLE'");
+            }
+            final var style = TextStyle.Style.from(matcher.group(3));
+            if (style == null) {
+                throw new UserErrorException("Invalid style: " + matcher.group(3));
+            }
+            textStyles.add(new TextStyle(style,
+                    Integer.parseInt(matcher.group(1)),
+                    Integer.parseInt(matcher.group(2))));
+        }
+        return textStyles;
+    }
+
     private Message.Sticker parseSticker(final String stickerString) throws UserErrorException {
         final Pattern stickerPattern = Pattern.compile("([\\da-f]+):(\\d+)");
         final var matcher = stickerPattern.matcher(stickerString);
index dafcababe772fbfef6883294bac1d9c3f663c455..f91f8001746b1493a36305579894ffaa8126c1ff 100644 (file)
@@ -219,7 +219,8 @@ public class DbusSignalImpl implements Signal {
                             Optional.empty(),
                             Optional.empty(),
                             List.of(),
-                            Optional.empty()),
+                            Optional.empty(),
+                            List.of()),
                     getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
                             .map(RecipientIdentifier.class::cast)
                             .collect(Collectors.toSet()));
@@ -388,7 +389,8 @@ public class DbusSignalImpl implements Signal {
                     Optional.empty(),
                     Optional.empty(),
                     List.of(),
-                    Optional.empty()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
+                    Optional.empty(),
+                    List.of()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (AttachmentInvalidException e) {
@@ -431,7 +433,8 @@ public class DbusSignalImpl implements Signal {
                     Optional.empty(),
                     Optional.empty(),
                     List.of(),
-                    Optional.empty()), Set.of(getGroupRecipientIdentifier(groupId)));
+                    Optional.empty(),
+                    List.of()), Set.of(getGroupRecipientIdentifier(groupId)));
             checkSendMessageResults(results);
             return results.timestamp();
         } catch (IOException | InvalidStickerException e) {
index 5511f53193bfeb1a23704452c76c6120288ec4b7..6da7145a9ffbf66cd7eba432966fb6cd65200a26 100644 (file)
@@ -20,6 +20,7 @@ record JsonDataMessage(
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonSharedContact> contacts,
+        @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonTextStyle> textStyles,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonGroupInfo groupInfo,
         @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryContext storyContext
 ) {
@@ -53,11 +54,15 @@ record JsonDataMessage(
                 .map(JsonAttachment::from)
                 .toList() : null;
         final var sticker = dataMessage.sticker().isPresent() ? JsonSticker.from(dataMessage.sticker().get()) : null;
-
         final var contacts = dataMessage.sharedContacts().size() > 0 ? dataMessage.sharedContacts()
                 .stream()
                 .map(JsonSharedContact::from)
                 .toList() : null;
+        final var textStyles = dataMessage.textStyles().size() > 0 ? dataMessage.textStyles()
+                .stream()
+                .map(JsonTextStyle::from)
+                .toList() : null;
+
         return new JsonDataMessage(timestamp,
                 message,
                 expiresInSeconds,
@@ -71,6 +76,7 @@ record JsonDataMessage(
                 sticker,
                 remoteDelete,
                 contacts,
+                textStyles,
                 groupInfo,
                 storyContext);
     }
index 01e35eb732ce6164cdb80a7766aed2863514e502..94f3f52c04bc89e24a5466a1262dfe4bd1ef4b46 100644 (file)
@@ -14,7 +14,8 @@ public record JsonQuote(
         String authorUuid,
         String text,
         @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions,
-        List<JsonQuotedAttachment> attachments
+        List<JsonQuotedAttachment> attachments,
+        @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonTextStyle> textStyles
 ) {
 
     static JsonQuote from(MessageEnvelope.Data.Quote quote) {
@@ -34,6 +35,11 @@ public record JsonQuote(
                 .map(JsonQuotedAttachment::from)
                 .toList() : List.<JsonQuotedAttachment>of();
 
-        return new JsonQuote(id, author, authorNumber, authorUuid, text, mentions, attachments);
+        final var textStyles = quote.textStyles().size() > 0 ? quote.textStyles()
+                .stream()
+                .map(JsonTextStyle::from)
+                .toList() : null;
+
+        return new JsonQuote(id, author, authorNumber, authorUuid, text, mentions, attachments, textStyles);
     }
 }
diff --git a/src/main/java/org/asamk/signal/json/JsonTextStyle.java b/src/main/java/org/asamk/signal/json/JsonTextStyle.java
new file mode 100644 (file)
index 0000000..898e7db
--- /dev/null
@@ -0,0 +1,10 @@
+package org.asamk.signal.json;
+
+import org.asamk.signal.manager.api.TextStyle;
+
+public record JsonTextStyle(String style, int start, int length) {
+
+    static JsonTextStyle from(TextStyle textStyle) {
+        return new JsonTextStyle(textStyle.style().name(), textStyle.start(), textStyle.length());
+    }
+}