Implement sending link previews

Fixes #276
This commit is contained in:
AsamK 2022-05-25 23:23:33 +02:00
parent 0f701df91f
commit b178c7c67a
8 changed files with 95 additions and 9 deletions

View file

@ -755,6 +755,19 @@
{"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", "name":"org.asamk.signal.json.JsonQuote",
"allDeclaredFields":true, "allDeclaredFields":true,
@ -2721,7 +2734,8 @@
{"name":"bitField0_"}, {"name":"bitField0_"},
{"name":"bodyRanges_"}, {"name":"bodyRanges_"},
{"name":"id_"}, {"name":"id_"},
{"name":"text_"} {"name":"text_"},
{"name":"type_"}
] ]
}, },
{ {

View file

@ -65,6 +65,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; 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.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.push.ACI; import org.whispersystems.signalservice.api.push.ACI;
@ -551,9 +552,8 @@ class ManagerImpl implements Manager {
final SignalServiceDataMessage.Builder messageBuilder, final Message message final SignalServiceDataMessage.Builder messageBuilder, final Message message
) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException {
messageBuilder.withBody(message.messageText()); messageBuilder.withBody(message.messageText());
final var attachments = message.attachments(); if (message.attachments().size() > 0) {
if (attachments != null) { messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments()));
messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(attachments));
} }
if (message.mentions().size() > 0) { if (message.mentions().size() > 0) {
messageBuilder.withMentions(resolveMentions(message.mentions())); messageBuilder.withMentions(resolveMentions(message.mentions()));
@ -592,6 +592,19 @@ class ManagerImpl implements Manager {
manifestSticker.emoji(), manifestSticker.emoji(),
AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()))); 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 {

View file

@ -8,7 +8,8 @@ public record Message(
List<String> attachments, List<String> attachments,
List<Mention> mentions, List<Mention> mentions,
Optional<Quote> quote, 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 Quote(long timestamp, RecipientIdentifier.Single author, String message, List<Mention> mentions) {}
public record Sticker(byte[] packId, int stickerId) {} public record Sticker(byte[] packId, int stickerId) {}
public record Preview(String url, String title, String description, Optional<String> image) {}
} }

View file

@ -253,6 +253,20 @@ Specify the message of the original message.
*--quote-mention*:: *--quote-mention*::
Specify the mentions of the original message (same format as `--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*:: *-e*, *--end-session*::
Clear session state and send end session message. Clear session state and send end session message.

View file

@ -72,6 +72,11 @@ public class SendCommand implements JsonRpcLocalCommand {
.nargs("*") .nargs("*")
.help("Quote with mention of another group member (syntax: start:length:recipientNumber)"); .help("Quote with mention of another group member (syntax: start:length:recipientNumber)");
subparser.addArgument("--sticker").help("Send a sticker (syntax: stickerPackId:stickerId)"); 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 { try {
var results = m.sendMessage(new Message(messageText == null ? "" : messageText, var results = m.sendMessage(new Message(messageText == null ? "" : messageText,
attachments, attachments,
mentions, mentions,
Optional.ofNullable(quote), Optional.ofNullable(quote),
Optional.ofNullable(sticker)), recipientIdentifiers); Optional.ofNullable(sticker),
previews), recipientIdentifiers);
outputResult(outputWriter, results); outputResult(outputWriter, results);
} catch (AttachmentInvalidException | IOException e) { } catch (AttachmentInvalidException | IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()

View file

@ -216,7 +216,8 @@ public class DbusSignalImpl implements Signal {
attachments, attachments,
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty()), Optional.empty(),
List.of()),
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast) .map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
@ -367,7 +368,8 @@ public class DbusSignalImpl implements Signal {
attachments, attachments,
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE)); Optional.empty(),
List.of()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (AttachmentInvalidException e) { } catch (AttachmentInvalidException e) {
@ -408,7 +410,8 @@ public class DbusSignalImpl implements Signal {
attachments, attachments,
List.of(), List.of(),
Optional.empty(), Optional.empty(),
Optional.empty()), Set.of(getGroupRecipientIdentifier(groupId))); Optional.empty(),
List.of()), Set.of(getGroupRecipientIdentifier(groupId)));
checkSendMessageResults(results); checkSendMessageResults(results);
return results.timestamp(); return results.timestamp();
} catch (IOException | InvalidStickerException e) { } catch (IOException | InvalidStickerException e) {

View file

@ -15,6 +15,7 @@ record JsonDataMessage(
@JsonInclude(JsonInclude.Include.NON_NULL) JsonQuote quote, @JsonInclude(JsonInclude.Include.NON_NULL) JsonQuote quote,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonPayment payment, @JsonInclude(JsonInclude.Include.NON_NULL) JsonPayment payment,
@JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions, @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) List<JsonAttachment> attachments,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker, @JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete, @JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete,
@ -36,6 +37,10 @@ record JsonDataMessage(
.stream() .stream()
.map(JsonMention::from) .map(JsonMention::from)
.toList() : null; .toList() : null;
final var previews = dataMessage.previews().size() > 0 ? dataMessage.previews()
.stream()
.map(JsonPreview::from)
.toList() : null;
final var remoteDelete = dataMessage.remoteDeleteId().isPresent() final var remoteDelete = dataMessage.remoteDeleteId().isPresent()
? new JsonRemoteDelete(dataMessage.remoteDeleteId().get()) ? new JsonRemoteDelete(dataMessage.remoteDeleteId().get())
: null; : null;
@ -57,6 +62,7 @@ record JsonDataMessage(
quote, quote,
payment, payment,
mentions, mentions,
previews,
attachments, attachments,
sticker, sticker,
remoteDelete, remoteDelete,

View file

@ -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));
}
}