diff --git a/libsignal/service/protobuf/SignalService.proto b/libsignal/service/protobuf/SignalService.proto index 9e34b9ba9d..43c8ded269 100644 --- a/libsignal/service/protobuf/SignalService.proto +++ b/libsignal/service/protobuf/SignalService.proto @@ -176,6 +176,14 @@ message DataMessage { optional AttachmentPointer data = 4; } + message Reaction { + optional string emoji = 1; + optional bool remove = 2; + optional string targetAuthorE164 = 3; + optional string targetAuthorUuid = 4; + optional uint64 targetSentTimestamp = 5; + } + enum ProtocolVersion { option allow_alias = true; @@ -183,7 +191,8 @@ message DataMessage { MESSAGE_TIMERS = 1; VIEW_ONCE = 2; VIEW_ONCE_VIDEO = 3; - CURRENT = 3; + REACTIONS = 4; + CURRENT = 4; } optional string body = 1; @@ -199,6 +208,7 @@ message DataMessage { optional Sticker sticker = 11; optional uint32 requiredProtocolVersion = 12; optional bool isViewOnce = 14; + optional Reaction reaction = 16; } message NullMessage { diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index 4c15e2206c..46643ea2f0 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -543,6 +543,24 @@ public class SignalServiceMessageSender { builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.VIEW_ONCE_VIDEO_VALUE, builder.getRequiredProtocolVersion())); } + if (message.getReaction().isPresent()) { + DataMessage.Reaction.Builder reactionBuilder = DataMessage.Reaction.newBuilder() + .setEmoji(message.getReaction().get().getEmoji()) + .setRemove(message.getReaction().get().isRemove()) + .setTargetSentTimestamp(message.getReaction().get().getTargetSentTimestamp()); + + if (message.getReaction().get().getTargetAuthor().getNumber().isPresent()) { + reactionBuilder.setTargetAuthorE164(message.getReaction().get().getTargetAuthor().getNumber().get()); + } + + if (message.getReaction().get().getTargetAuthor().getUuid().isPresent()) { + reactionBuilder.setTargetAuthorUuid(message.getReaction().get().getTargetAuthor().getUuid().get().toString()); + } + + builder.setReaction(reactionBuilder.build()); + builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.REACTIONS_VALUE, builder.getRequiredProtocolVersion())); + } + builder.setTimestamp(message.getTimestamp()); return container.setDataMessage(builder).build().toByteArray(); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index d7cc397c27..f04f5cc5e8 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPoin import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview; +import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Reaction; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Sticker; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; @@ -308,6 +309,7 @@ public class SignalServiceCipher { List sharedContacts = createSharedContacts(content); List previews = createPreviews(content); Sticker sticker = createSticker(content); + Reaction reaction = createReaction(content); if (content.getRequiredProtocolVersion() > DataMessage.ProtocolVersion.CURRENT.getNumber()) { throw new UnsupportedDataMessageException(DataMessage.ProtocolVersion.CURRENT.getNumber(), @@ -340,7 +342,8 @@ public class SignalServiceCipher { sharedContacts, previews, sticker, - content.getIsViewOnce()); + content.getIsViewOnce(), + reaction); } private SignalServiceSyncMessage createSynchronizeMessage(Metadata metadata, SyncMessage content) @@ -618,6 +621,23 @@ public class SignalServiceCipher { createAttachmentPointer(sticker.getData())); } + private Reaction createReaction(DataMessage content) { + if (!content.hasReaction() || + !content.getReaction().hasEmoji() || + !(content.getReaction().hasTargetAuthorE164() || content.getReaction().hasTargetAuthorUuid()) || + !content.getReaction().hasTargetSentTimestamp()) + { + return null; + } + + DataMessage.Reaction reaction = content.getReaction(); + + return new Reaction(reaction.getEmoji(), + reaction.getRemove(), + new SignalServiceAddress(UuidUtil.parseOrNull(reaction.getTargetAuthorUuid()), reaction.getTargetAuthorE164()), + reaction.getTargetSentTimestamp()); + } + private List createSharedContacts(DataMessage content) { if (content.getContactCount() <= 0) return null; diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java index 2ac9450245..28b8a5a509 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java @@ -32,6 +32,7 @@ public class SignalServiceDataMessage { private final Optional> previews; private final Optional sticker; private final boolean viewOnce; + private final Optional reaction; /** * Construct a SignalServiceDataMessage with a body and no attachments. @@ -105,7 +106,7 @@ public class SignalServiceDataMessage { * @param expiresInSeconds The number of seconds in which a message should disappear after having been seen. */ public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, List attachments, String body, int expiresInSeconds) { - this(timestamp, group, attachments, body, false, expiresInSeconds, false, null, false, null, null, null, null, false); + this(timestamp, group, attachments, body, false, expiresInSeconds, false, null, false, null, null, null, null, false, null); } /** @@ -123,7 +124,7 @@ public class SignalServiceDataMessage { String body, boolean endSession, int expiresInSeconds, boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate, Quote quote, List sharedContacts, List previews, - Sticker sticker, boolean viewOnce) + Sticker sticker, boolean viewOnce, Reaction reaction) { this.timestamp = timestamp; this.body = Optional.fromNullable(body); @@ -136,6 +137,7 @@ public class SignalServiceDataMessage { this.quote = Optional.fromNullable(quote); this.sticker = Optional.fromNullable(sticker); this.viewOnce = viewOnce; + this.reaction = Optional.fromNullable(reaction); if (attachments != null && !attachments.isEmpty()) { this.attachments = Optional.of(attachments); @@ -232,6 +234,10 @@ public class SignalServiceDataMessage { return viewOnce; } + public Optional getReaction() { + return reaction; + } + public static class Builder { private List attachments = new LinkedList<>(); @@ -249,6 +255,7 @@ public class SignalServiceDataMessage { private Quote quote; private Sticker sticker; private boolean viewOnce; + private Reaction reaction; private Builder() {} @@ -340,12 +347,17 @@ public class SignalServiceDataMessage { return this; } + public Builder withReaction(Reaction reaction) { + this.reaction = reaction; + return this; + } + public SignalServiceDataMessage build() { if (timestamp == 0) timestamp = System.currentTimeMillis(); return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, - sticker, viewOnce); + sticker, viewOnce, reaction); } } @@ -456,4 +468,34 @@ public class SignalServiceDataMessage { return attachment; } } + + public static class Reaction { + private final String emoji; + private final boolean remove; + private final SignalServiceAddress targetAuthor; + private final long targetSentTimestamp; + + public Reaction(String emoji, boolean remove, SignalServiceAddress targetAuthor, long targetSentTimestamp) { + this.emoji = emoji; + this.remove = remove; + this.targetAuthor = targetAuthor; + this.targetSentTimestamp = targetSentTimestamp; + } + + public String getEmoji() { + return emoji; + } + + public boolean isRemove() { + return remove; + } + + public SignalServiceAddress getTargetAuthor() { + return targetAuthor; + } + + public long getTargetSentTimestamp() { + return targetSentTimestamp; + } + } }