Add text formatting send and receive support for conversations.

This commit is contained in:
Cody Henthorne
2023-01-25 10:31:36 -05:00
committed by Greyson Parrelli
parent aa2075c78f
commit cc490f4b73
73 changed files with 1664 additions and 516 deletions

View File

@@ -101,6 +101,7 @@ import org.whispersystems.signalservice.internal.push.SendGroupMessageResponse;
import org.whispersystems.signalservice.internal.push.SendMessageResponse;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage;
@@ -845,6 +846,10 @@ public class SignalServiceMessageSender {
builder.setTextAttachment(createTextAttachment(message.getTextAttachment().get()));
}
if (message.getBodyRanges().isPresent()) {
builder.addAllBodyRanges(message.getBodyRanges().get());
}
builder.setAllowsReplies(message.getAllowsReplies().orElse(true));
return container.setStoryMessage(builder).build();
@@ -929,15 +934,20 @@ public class SignalServiceMessageSender {
List<SignalServiceDataMessage.Mention> mentions = message.getQuote().get().getMentions();
if (mentions != null && !mentions.isEmpty()) {
for (SignalServiceDataMessage.Mention mention : mentions) {
quoteBuilder.addBodyRanges(DataMessage.BodyRange.newBuilder()
.setStart(mention.getStart())
.setLength(mention.getLength())
.setMentionUuid(mention.getServiceId().toString()));
quoteBuilder.addBodyRanges(BodyRange.newBuilder()
.setStart(mention.getStart())
.setLength(mention.getLength())
.setMentionUuid(mention.getServiceId().toString()));
}
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.MENTIONS_VALUE, builder.getRequiredProtocolVersion()));
}
List<BodyRange> bodyRanges = message.getQuote().get().getBodyRanges();
if (bodyRanges != null) {
quoteBuilder.addAllBodyRanges(bodyRanges);
}
List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = message.getQuote().get().getAttachments();
if (attachments != null) {
for (SignalServiceDataMessage.Quote.QuotedAttachment attachment : attachments) {
@@ -972,10 +982,10 @@ public class SignalServiceMessageSender {
if (message.getMentions().isPresent()) {
for (SignalServiceDataMessage.Mention mention : message.getMentions().get()) {
builder.addBodyRanges(DataMessage.BodyRange.newBuilder()
.setStart(mention.getStart())
.setLength(mention.getLength())
.setMentionUuid(mention.getServiceId().toString()));
builder.addBodyRanges(BodyRange.newBuilder()
.setStart(mention.getStart())
.setLength(mention.getLength())
.setMentionUuid(mention.getServiceId().toString()));
}
builder.setRequiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.MENTIONS_VALUE, builder.getRequiredProtocolVersion()));
}
@@ -1060,6 +1070,10 @@ public class SignalServiceMessageSender {
.setReceiptCredentialPresentation(ByteString.copyFrom(giftBadge.getReceiptCredentialPresentation().serialize())));
}
if (message.getBodyRanges().isPresent()) {
builder.addAllBodyRanges(message.getBodyRanges().get());
}
builder.setTimestamp(message.getTimestamp());
return enforceMaxContentSize(container.setDataMessage(builder).build());

View File

@@ -680,9 +680,9 @@ import javax.annotation.Nullable;
Optional<SignalServiceGroupV2> groupContext = Optional.ofNullable(groupInfoV2);
List<SignalServiceAttachment> attachments = new LinkedList<>();
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE ) != 0);
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE) != 0);
boolean expirationUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0);
boolean profileKeyUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
boolean profileKeyUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE) != 0);
boolean isGroupV2 = groupInfoV2 != null;
SignalServiceDataMessage.Quote quote = createQuote(content, isGroupV2);
List<SharedContact> sharedContacts = createSharedContacts(content);
@@ -694,6 +694,7 @@ import javax.annotation.Nullable;
SignalServiceDataMessage.GroupCallUpdate groupCallUpdate = createGroupCallUpdate(content);
SignalServiceDataMessage.StoryContext storyContext = createStoryContext(content);
SignalServiceDataMessage.GiftBadge giftBadge = createGiftBadge(content);
List<SignalServiceProtos.BodyRange> bodyRanges = createBodyRanges(content.getBodyRangesList(), content.getBody());
if (content.getRequiredProtocolVersion() > SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE) {
throw new UnsupportedDataMessageProtocolVersionException(SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT_VALUE,
@@ -745,6 +746,7 @@ import javax.annotation.Nullable;
.withPayment(payment)
.withStoryContext(storyContext)
.withGiftBadge(giftBadge)
.withBodyRanges(bodyRanges)
.build();
}
@@ -1092,12 +1094,14 @@ import javax.annotation.Nullable;
return SignalServiceStoryMessage.forFileAttachment(profileKey,
createGroupV2Info(content),
createAttachmentPointer(content.getFileAttachment()),
content.getAllowsReplies());
content.getAllowsReplies(),
content.getBodyRangesList());
} else {
return SignalServiceStoryMessage.forTextAttachment(profileKey,
createGroupV2Info(content),
createTextAttachment(content.getTextAttachment()),
content.getAllowsReplies());
content.getAllowsReplies(),
content.getBodyRangesList());
}
}
@@ -1121,7 +1125,8 @@ import javax.annotation.Nullable;
content.getQuote().getText(),
attachments,
createMentions(content.getQuote().getBodyRangesList(), content.getQuote().getText(), isGroupV2),
SignalServiceDataMessage.Quote.Type.fromProto(content.getQuote().getType()));
SignalServiceDataMessage.Quote.Type.fromProto(content.getQuote().getType()),
createBodyRanges(content.getQuote().getBodyRangesList(), content.getQuote().getText()));
} else {
Log.w(TAG, "Quote was missing an author! Returning null.");
return null;
@@ -1154,7 +1159,7 @@ import javax.annotation.Nullable;
Optional.ofNullable(attachment));
}
private static @Nullable List<SignalServiceDataMessage.Mention> createMentions(List<SignalServiceProtos.DataMessage.BodyRange> bodyRanges, String body, boolean isGroupV2)
private static @Nullable List<SignalServiceDataMessage.Mention> createMentions(List<SignalServiceProtos.BodyRange> bodyRanges, String body, boolean isGroupV2)
throws InvalidMessageStructureException
{
if (bodyRanges == null || bodyRanges.isEmpty() || body == null) {
@@ -1163,7 +1168,7 @@ import javax.annotation.Nullable;
List<SignalServiceDataMessage.Mention> mentions = new LinkedList<>();
for (SignalServiceProtos.DataMessage.BodyRange bodyRange : bodyRanges) {
for (SignalServiceProtos.BodyRange bodyRange : bodyRanges) {
if (bodyRange.hasMentionUuid()) {
try {
mentions.add(new SignalServiceDataMessage.Mention(ServiceId.parseOrThrow(bodyRange.getMentionUuid()), bodyRange.getStart(), bodyRange.getLength()));
@@ -1180,6 +1185,22 @@ import javax.annotation.Nullable;
return mentions;
}
private static @Nullable List<SignalServiceProtos.BodyRange> createBodyRanges(List<SignalServiceProtos.BodyRange> bodyRanges, String body) {
if (bodyRanges == null || bodyRanges.isEmpty() || body == null) {
return null;
}
List<SignalServiceProtos.BodyRange> ranges = new LinkedList<>();
for (SignalServiceProtos.BodyRange bodyRange : bodyRanges) {
if (bodyRange.hasStyle()) {
ranges.add(bodyRange);
}
}
return ranges;
}
private static @Nullable SignalServiceDataMessage.Sticker createSticker(SignalServiceProtos.DataMessage content) throws InvalidMessageStructureException {
if (!content.hasSticker() ||
!content.getSticker().hasPackId() ||

View File

@@ -11,6 +11,7 @@ import org.whispersystems.signalservice.api.messages.shared.SharedContact
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional
import org.whispersystems.signalservice.api.util.OptionalUtil.emptyIfStringEmpty
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.BodyRange
import java.util.LinkedList
import java.util.Optional
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Payment as PaymentProto
@@ -47,7 +48,8 @@ class SignalServiceDataMessage private constructor(
val groupCallUpdate: Optional<GroupCallUpdate>,
val payment: Optional<Payment>,
val storyContext: Optional<StoryContext>,
val giftBadge: Optional<GiftBadge>
val giftBadge: Optional<GiftBadge>,
val bodyRanges: Optional<List<BodyRange>>
) {
val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false)
val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false)
@@ -92,6 +94,7 @@ class SignalServiceDataMessage private constructor(
private var payment: Payment? = null
private var storyContext: StoryContext? = null
private var giftBadge: GiftBadge? = null
private var bodyRanges: MutableList<BodyRange> = LinkedList<BodyRange>()
fun withTimestamp(timestamp: Long): Builder {
this.timestamp = timestamp
@@ -210,6 +213,11 @@ class SignalServiceDataMessage private constructor(
return this
}
fun withBodyRanges(bodyRanges: List<BodyRange>?): Builder {
bodyRanges?.let { this.bodyRanges.addAll(bodyRanges) }
return this
}
fun build(): SignalServiceDataMessage {
if (timestamp == 0L) {
timestamp = System.currentTimeMillis()
@@ -236,7 +244,8 @@ class SignalServiceDataMessage private constructor(
groupCallUpdate = groupCallUpdate.asOptional(),
payment = payment.asOptional(),
storyContext = storyContext.asOptional(),
giftBadge = giftBadge.asOptional()
giftBadge = giftBadge.asOptional(),
bodyRanges = bodyRanges.asOptional()
)
}
}
@@ -247,7 +256,8 @@ class SignalServiceDataMessage private constructor(
val text: String,
val attachments: List<QuotedAttachment>?,
val mentions: List<Mention>?,
val type: Type
val type: Type,
val bodyRanges: List<BodyRange>?
) {
enum class Type(val protoType: QuoteProto.Type) {
NORMAL(QuoteProto.Type.NORMAL),

View File

@@ -1,39 +1,50 @@
package org.whispersystems.signalservice.api.messages;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.util.List;
import java.util.Optional;
public class SignalServiceStoryMessage {
private final Optional<byte[]> profileKey;
private final Optional<SignalServiceGroupV2> groupContext;
private final Optional<SignalServiceAttachment> fileAttachment;
private final Optional<SignalServiceTextAttachment> textAttachment;
private final Optional<Boolean> allowsReplies;
private final Optional<byte[]> profileKey;
private final Optional<SignalServiceGroupV2> groupContext;
private final Optional<SignalServiceAttachment> fileAttachment;
private final Optional<SignalServiceTextAttachment> textAttachment;
private final Optional<Boolean> allowsReplies;
private final Optional<List<SignalServiceProtos.BodyRange>> bodyRanges;
private SignalServiceStoryMessage(byte[] profileKey,
SignalServiceGroupV2 groupContext,
SignalServiceAttachment fileAttachment,
SignalServiceTextAttachment textAttachment,
boolean allowsReplies) {
boolean allowsReplies,
List<SignalServiceProtos.BodyRange> bodyRanges)
{
this.profileKey = Optional.ofNullable(profileKey);
this.groupContext = Optional.ofNullable(groupContext);
this.fileAttachment = Optional.ofNullable(fileAttachment);
this.textAttachment = Optional.ofNullable(textAttachment);
this.allowsReplies = Optional.of(allowsReplies);
this.bodyRanges = Optional.ofNullable(bodyRanges);
}
public static SignalServiceStoryMessage forFileAttachment(byte[] profileKey,
SignalServiceGroupV2 groupContext,
SignalServiceAttachment fileAttachment,
boolean allowsReplies) {
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null, allowsReplies);
boolean allowsReplies,
List<SignalServiceProtos.BodyRange> bodyRanges)
{
return new SignalServiceStoryMessage(profileKey, groupContext, fileAttachment, null, allowsReplies, bodyRanges);
}
public static SignalServiceStoryMessage forTextAttachment(byte[] profileKey,
SignalServiceGroupV2 groupContext,
SignalServiceTextAttachment textAttachment,
boolean allowsReplies) {
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment, allowsReplies);
boolean allowsReplies,
List<SignalServiceProtos.BodyRange> bodyRanges)
{
return new SignalServiceStoryMessage(profileKey, groupContext, null, textAttachment, allowsReplies, bodyRanges);
}
public Optional<byte[]> getProfileKey() {
@@ -55,4 +66,8 @@ public class SignalServiceStoryMessage {
public Optional<Boolean> getAllowsReplies() {
return allowsReplies;
}
public Optional<List<SignalServiceProtos.BodyRange>> getBodyRanges() {
return bodyRanges;
}
}

View File

@@ -125,6 +125,25 @@ message CallMessage {
optional Opaque opaque = 10;
}
message BodyRange {
enum Style {
NONE = 0;
BOLD = 1;
ITALIC = 2;
SPOILER = 3;
STRIKETHROUGH = 4;
MONOSPACE = 5;
}
optional uint32 start = 1;
optional uint32 length = 2;
oneof associatedValue {
string mentionUuid = 3;
Style style = 4;
}
}
message DataMessage {
enum Flags {
END_SESSION = 1;
@@ -132,15 +151,6 @@ message DataMessage {
PROFILE_KEY_UPDATE = 4;
}
message BodyRange {
optional uint32 start = 1;
optional uint32 length = 2;
oneof associatedValue {
string mentionUuid = 3;
}
}
message Quote {
enum Type {
NORMAL = 0;
@@ -382,6 +392,7 @@ message StoryMessage {
TextAttachment textAttachment = 4;
}
optional bool allowsReplies = 5;
repeated BodyRange bodyRanges = 6;
}
message Preview {