"allDeclaredMethods":true,
"allDeclaredConstructors":true
},
+{
+ "name":"org.asamk.signal.json.JsonStoryContext",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[
+ {"name":"authorNumber","parameterTypes":[] },
+ {"name":"authorUuid","parameterTypes":[] },
+ {"name":"sentTimestamp","parameterTypes":[] }
+ ]
+},
+{
+ "name":"org.asamk.signal.json.JsonStoryMessage",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[
+ {"name":"allowsReplies","parameterTypes":[] },
+ {"name":"fileAttachment","parameterTypes":[] },
+ {"name":"groupId","parameterTypes":[] },
+ {"name":"textAttachment","parameterTypes":[] }
+ ]
+},
+{
+ "name":"org.asamk.signal.json.JsonStoryMessage$TextAttachment",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[
+ {"name":"backgroundColor","parameterTypes":[] },
+ {"name":"backgroundGradient","parameterTypes":[] },
+ {"name":"preview","parameterTypes":[] },
+ {"name":"style","parameterTypes":[] },
+ {"name":"text","parameterTypes":[] },
+ {"name":"textBackgroundColor","parameterTypes":[] },
+ {"name":"textForegroundColor","parameterTypes":[] }
+ ]
+},
+{
+ "name":"org.asamk.signal.json.JsonStoryMessage$TextAttachment$Gradient",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[
+ {"name":"angle","parameterTypes":[] },
+ {"name":"endColor","parameterTypes":[] },
+ {"name":"startColor","parameterTypes":[] }
+ ]
+},
{
"name":"org.asamk.signal.json.JsonSyncDataMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true
},
+{
+ "name":"org.asamk.signal.json.JsonSyncStoryMessage",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true,
+ "methods":[
+ {"name":"dataMessage","parameterTypes":[] },
+ {"name":"destinationNumber","parameterTypes":[] },
+ {"name":"destinationUuid","parameterTypes":[] }
+ ]
+},
{
"name":"org.asamk.signal.json.JsonTypingMessage",
"allDeclaredFields":true,
{"name":"timestamp_"}
]
},
+{
+ "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TextAttachment",
+ "fields":[
+ {"name":"backgroundCase_"},
+ {"name":"background_"},
+ {"name":"bitField0_"},
+ {"name":"preview_"},
+ {"name":"textBackgroundColor_"},
+ {"name":"textForegroundColor_"},
+ {"name":"textStyle_"},
+ {"name":"text_"}
+ ]
+},
+{
+ "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TextAttachment$Gradient",
+ "fields":[
+ {"name":"angle_"},
+ {"name":"bitField0_"},
+ {"name":"endColor_"},
+ {"name":"startColor_"}
+ ]
+},
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TypingMessage",
"fields":[
--- /dev/null
+package org.asamk.signal.manager.api;
+
+public record Color(int color) {
+
+ public int alpha() {
+ return color >>> 24;
+ }
+
+ public int red() {
+ return (color >> 16) & 0xFF;
+ }
+
+ public int green() {
+ return (color >> 8) & 0xFF;
+ }
+
+ public int blue() {
+ return color & 0xFF;
+ }
+
+ public String toHexColor() {
+ return String.format("#%08x", color);
+ }
+}
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceTextAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
Optional<Typing> typing,
Optional<Data> data,
Optional<Sync> sync,
- Optional<Call> call
+ Optional<Call> call,
+ Optional<Story> story
) {
public record Receipt(long when, Type type, List<Long> timestamps) {
public record Data(
long timestamp,
Optional<GroupContext> groupContext,
+ Optional<StoryContext> storyContext,
Optional<GroupCallUpdate> groupCallUpdate,
Optional<String> body,
int expiresInSeconds,
) {
return new Data(dataMessage.getTimestamp(),
dataMessage.getGroupContext().map(GroupContext::from),
+ dataMessage.getStoryContext()
+ .map((SignalServiceDataMessage.StoryContext storyContext) -> StoryContext.from(storyContext,
+ recipientResolver,
+ addressResolver)),
dataMessage.getGroupCallUpdate().map(GroupCallUpdate::from),
dataMessage.getBody(),
dataMessage.getExpiresInSeconds(),
}
}
+ public record StoryContext(RecipientAddress author, long sentTimestamp) {
+
+ static StoryContext from(
+ SignalServiceDataMessage.StoryContext storyContext,
+ RecipientResolver recipientResolver,
+ RecipientAddressResolver addressResolver
+ ) {
+ return new StoryContext(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
+ storyContext.getAuthorServiceId())), storyContext.getSentTimestamp());
+ }
+ }
+
public record GroupCallUpdate(String eraId) {
static GroupCallUpdate from(SignalServiceDataMessage.GroupCallUpdate groupCallUpdate) {
long expirationStartTimestamp,
Optional<RecipientAddress> destination,
Set<RecipientAddress> recipients,
- Optional<Data> message
+ Optional<Data> message,
+ Optional<Story> story
) {
static Sent from(
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
.collect(Collectors.toSet()),
sentMessage.getDataMessage()
- .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)));
+ .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
+ sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
}
}
}
}
+ public record Story(
+ boolean allowsReplies,
+ Optional<GroupId> groupId,
+ Optional<Data.Attachment> fileAttachment,
+ Optional<TextAttachment> textAttachment
+ ) {
+
+ public static Story from(
+ SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider
+ ) {
+ return new Story(storyMessage.getAllowsReplies().orElse(false),
+ storyMessage.getGroupContext().map(c -> GroupUtils.getGroupIdV2(c.getMasterKey())),
+ storyMessage.getFileAttachment().map(f -> Data.Attachment.from(f, fileProvider)),
+ storyMessage.getTextAttachment().map(t -> TextAttachment.from(t, fileProvider)));
+ }
+
+ public record TextAttachment(
+ Optional<String> text,
+ Optional<Style> style,
+ Optional<Color> textForegroundColor,
+ Optional<Color> textBackgroundColor,
+ Optional<Data.Preview> preview,
+ Optional<Gradient> backgroundGradient,
+ Optional<Color> backgroundColor
+ ) {
+
+ static TextAttachment from(
+ SignalServiceTextAttachment textAttachment, final AttachmentFileProvider fileProvider
+ ) {
+ return new TextAttachment(textAttachment.getText(),
+ textAttachment.getStyle().map(Style::from),
+ textAttachment.getTextForegroundColor().map(Color::new),
+ textAttachment.getTextBackgroundColor().map(Color::new),
+ textAttachment.getPreview().map(p -> Data.Preview.from(p, fileProvider)),
+ textAttachment.getBackgroundGradient().map(Gradient::from),
+ textAttachment.getBackgroundColor().map(Color::new));
+ }
+
+ public enum Style {
+ DEFAULT,
+ REGULAR,
+ BOLD,
+ SERIF,
+ SCRIPT,
+ CONDENSED;
+
+ static Style from(SignalServiceTextAttachment.Style style) {
+ return switch (style) {
+ case DEFAULT -> DEFAULT;
+ case REGULAR -> REGULAR;
+ case BOLD -> BOLD;
+ case SERIF -> SERIF;
+ case SCRIPT -> SCRIPT;
+ case CONDENSED -> CONDENSED;
+ };
+ }
+ }
+
+ public record Gradient(Optional<Color> startColor, Optional<Color> endColor, Optional<Integer> angle) {
+
+ static Gradient from(SignalServiceTextAttachment.Gradient gradient) {
+ return new Gradient(gradient.getStartColor().map(Color::new),
+ gradient.getEndColor().map(Color::new),
+ gradient.getAngle());
+ }
+ }
+ }
+ }
+
public static MessageEnvelope from(
SignalServiceEnvelope envelope,
SignalServiceContent content,
Optional<Data> data;
Optional<Sync> sync;
Optional<Call> call;
+ Optional<Story> story;
if (content != null) {
receipt = content.getReceiptMessage().map(Receipt::from);
typing = content.getTypingMessage().map(Typing::from);
.map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider));
sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider));
call = content.getCallMessage().map(Call::from);
+ story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
} else {
receipt = envelope.isReceipt() ? Optional.of(new Receipt(envelope.getServerReceivedTimestamp(),
Receipt.Type.DELIVERY,
data = Optional.empty();
sync = Optional.empty();
call = Optional.empty();
+ story = Optional.empty();
}
return new MessageEnvelope(source == null
typing,
data,
sync,
- call);
+ call,
+ story);
}
public interface AttachmentFileProvider {
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
receiveConfig.ignoreAttachments()));
}
+ if (content.getStoryMessage().isPresent()) {
+ final var message = content.getStoryMessage().get();
+ actions.addAll(handleSignalServiceStoryMessage(message, sender, receiveConfig.ignoreAttachments()));
+ }
+
if (content.getSyncMessage().isPresent()) {
var syncMessage = content.getSyncMessage().get();
actions.addAll(handleSyncMessage(syncMessage, sender, receiveConfig.ignoreAttachments()));
destination == null ? null : context.getRecipientHelper().resolveRecipient(destination),
ignoreAttachments));
}
+ if (message.getStoryMessage().isPresent()) {
+ actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
+ sender,
+ ignoreAttachments));
+ }
}
if (syncMessage.getRequest().isPresent() && account.isPrimaryDevice()) {
var rm = syncMessage.getRequest().get();
) {
var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) {
- if (message.getGroupContext().get().getGroupV1().isPresent()) {
- var groupInfo = message.getGroupContext().get().getGroupV1().get();
+ final var groupContext = message.getGroupContext().get();
+ if (groupContext.getGroupV1().isPresent()) {
+ var groupInfo = groupContext.getGroupV1().get();
var groupId = GroupId.v1(groupInfo.getGroupId());
var group = context.getGroupHelper().getGroup(groupId);
if (group == null || group instanceof GroupInfoV1) {
// Received a group v1 message for a v2 group
}
}
- if (message.getGroupContext().get().getGroupV2().isPresent()) {
- final var groupContext = message.getGroupContext().get().getGroupV2().get();
- final var groupMasterKey = groupContext.getMasterKey();
-
- context.getGroupHelper()
- .getOrMigrateGroup(groupMasterKey,
- groupContext.getRevision(),
- groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
+ if (groupContext.getGroupV2().isPresent()) {
+ handleGroupV2Context(groupContext.getGroupV2().get());
}
}
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent()) {
- if (message.getGroupContext().get().getGroupV1().isPresent()) {
- var groupInfo = message.getGroupContext().get().getGroupV1().get();
+ final var groupContext = message.getGroupContext().get();
+ if (groupContext.getGroupV1().isPresent()) {
+ var groupInfo = groupContext.getGroupV1().get();
var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
if (group != null) {
if (group.messageExpirationTime != message.getExpiresInSeconds()) {
account.getGroupStore().updateGroup(group);
}
}
- } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
+ } else if (groupContext.getGroupV2().isPresent()) {
// disappearing message timer already stored in the DecryptedGroup
}
} else if (conversationPartnerAddress != null) {
}
}
}
- if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
- final ProfileKey profileKey;
- try {
- profileKey = new ProfileKey(message.getProfileKey().get());
- } catch (InvalidInputException e) {
- throw new AssertionError(e);
- }
- if (account.getSelfRecipientId().equals(source)) {
- this.account.setProfileKey(profileKey);
- }
- this.account.getProfileStore().storeProfileKey(source, profileKey);
+ if (message.getProfileKey().isPresent()) {
+ handleIncomingProfileKey(message.getProfileKey().get(), source);
}
if (message.getSticker().isPresent()) {
final var messageSticker = message.getSticker().get();
return actions;
}
+ private List<HandleAction> handleSignalServiceStoryMessage(
+ SignalServiceStoryMessage message, RecipientId source, boolean ignoreAttachments
+ ) {
+ var actions = new ArrayList<HandleAction>();
+ if (message.getGroupContext().isPresent()) {
+ handleGroupV2Context(message.getGroupContext().get());
+ }
+
+ if (!ignoreAttachments) {
+ if (message.getFileAttachment().isPresent()) {
+ context.getAttachmentHelper().downloadAttachment(message.getFileAttachment().get());
+ }
+ if (message.getTextAttachment().isPresent()) {
+ final var textAttachment = message.getTextAttachment().get();
+ if (textAttachment.getPreview().isPresent()) {
+ final var preview = textAttachment.getPreview().get();
+ if (preview.getImage().isPresent()) {
+ context.getAttachmentHelper().downloadAttachment(preview.getImage().get());
+ }
+ }
+ }
+ }
+
+ if (message.getProfileKey().isPresent()) {
+ handleIncomingProfileKey(message.getProfileKey().get(), source);
+ }
+
+ return actions;
+ }
+
+ private void handleGroupV2Context(final SignalServiceGroupV2 groupContext) {
+ final var groupMasterKey = groupContext.getMasterKey();
+
+ context.getGroupHelper()
+ .getOrMigrateGroup(groupMasterKey,
+ groupContext.getRevision(),
+ groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
+ }
+
+ private void handleIncomingProfileKey(final byte[] profileKeyBytes, final RecipientId source) {
+ if (profileKeyBytes.length != 32) {
+ logger.debug("Received invalid profile key of length {}", profileKeyBytes.length);
+ return;
+ }
+ final ProfileKey profileKey;
+ try {
+ profileKey = new ProfileKey(profileKeyBytes);
+ } catch (InvalidInputException e) {
+ throw new AssertionError(e);
+ }
+ if (account.getSelfRecipientId().equals(source)) {
+ this.account.setProfileKey(profileKey);
+ }
+ this.account.getProfileStore().storeProfileKey(source, profileKey);
+ }
+
private Pair<RecipientId, Integer> getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
return new Pair<>(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
var message = envelope.data().get();
printDataMessage(writer, message);
}
+ if (envelope.story().isPresent()) {
+ var message = envelope.story().get();
+ printStoryMessage(writer.indentedWriter(), message);
+ }
if (envelope.sync().isPresent()) {
writer.println("Received a sync message");
var syncMessage = envelope.sync().get();
final var groupContext = message.groupContext().get();
printGroupContext(writer.indentedWriter(), groupContext);
}
+ if (message.storyContext().isPresent()) {
+ writer.println("Story reply:");
+ final var storyContext = message.storyContext().get();
+ printStoryContext(writer.indentedWriter(), storyContext);
+ }
if (message.groupCallUpdate().isPresent()) {
writer.println("Group call update:");
final var groupCallUpdate = message.groupCallUpdate().get();
}
}
+ private void printStoryMessage(
+ PlainTextWriter writer, MessageEnvelope.Story message
+ ) {
+ writer.println("Story: with replies: {}", message.allowsReplies());
+ if (message.groupId().isPresent()) {
+ writer.println("Group info:");
+ printGroupInfo(writer.indentedWriter(), message.groupId().get());
+ }
+ if (message.textAttachment().isPresent()) {
+ writer.println("Body: {}", message.textAttachment().get().text().orElse(""));
+
+ if (message.textAttachment().get().preview().isPresent()) {
+ writer.println("Preview:");
+ printPreview(writer.indentedWriter(), message.textAttachment().get().preview().get());
+ }
+ }
+ if (message.fileAttachment().isPresent()) {
+ writer.println("Attachments:");
+ printAttachment(writer.indentedWriter(), message.fileAttachment().get());
+ }
+ }
+
private void printTypingMessage(
final PlainTextWriter writer, final MessageEnvelope.Typing typingMessage
) {
var message = sentTranscriptMessage.message().get();
printDataMessage(writer.indentedWriter(), message);
}
+ if (sentTranscriptMessage.story().isPresent()) {
+ var message = sentTranscriptMessage.story().get();
+ printStoryMessage(writer.indentedWriter(), message);
+ }
}
if (syncMessage.blocked().isPresent()) {
writer.println("Received sync message with block list");
writer.println("Type: {}", groupContext.isGroupUpdate() ? "UPDATE" : "DELIVER");
}
+ private void printStoryContext(
+ final PlainTextWriter writer, final MessageEnvelope.Data.StoryContext storyContext
+ ) {
+ writer.println("Sender: {}", formatContact(storyContext.author()));
+ writer.println("Sent timestamp: {}", storyContext.sentTimestamp());
+ }
+
private void printGroupInfo(final PlainTextWriter writer, final GroupId groupId) {
writer.println("Id: {}", groupId.toBase64());
messageReceived.getGroupId()), false, 0))
: Optional.empty(),
Optional.empty(),
+ Optional.empty(),
Optional.of(messageReceived.getMessage()),
0,
false,
List.of(),
List.of())),
Optional.empty(),
+ Optional.empty(),
Optional.empty());
notifyMessageHandlers(envelope);
};
Optional.empty(),
Optional.empty(),
Optional.empty(),
+ Optional.empty(),
Optional.empty());
notifyMessageHandlers(envelope);
};
syncReceived.getGroupId()), false, 0))
: Optional.empty(),
Optional.empty(),
+ Optional.empty(),
Optional.of(syncReceived.getMessage()),
0,
false,
Optional.empty(),
List.of(),
List.of(),
- List.of())))),
+ List.of())),
+ Optional.empty())),
Optional.empty(),
List.of(),
List.of(),
Optional.empty(),
Optional.empty(),
Optional.empty())),
+ Optional.empty(),
Optional.empty());
notifyMessageHandlers(envelope);
};
@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) JsonGroupInfo groupInfo
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonGroupInfo groupInfo,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryContext storyContext
) {
static JsonDataMessage from(MessageEnvelope.Data dataMessage) {
final var timestamp = dataMessage.timestamp();
final var groupInfo = dataMessage.groupContext().isPresent() ? JsonGroupInfo.from(dataMessage.groupContext()
.get()) : null;
+ final var storyContext = dataMessage.storyContext().isPresent()
+ ? JsonStoryContext.from(dataMessage.storyContext().get())
+ : null;
final var message = dataMessage.body().orElse(null);
final var expiresInSeconds = dataMessage.expiresInSeconds();
final var viewOnce = dataMessage.isViewOnce();
sticker,
remoteDelete,
contacts,
- groupInfo);
+ groupInfo,
+ storyContext);
}
}
Integer sourceDevice,
long timestamp,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonStoryMessage storyMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonReceiptMessage receiptMessage,
final var typingMessage = envelope.typing().map(JsonTypingMessage::from).orElse(null);
final var dataMessage = envelope.data().map(JsonDataMessage::from).orElse(null);
+ final var storyMessage = envelope.story().map(JsonStoryMessage::from).orElse(null);
final var syncMessage = envelope.sync().map(JsonSyncMessage::from).orElse(null);
final var callMessage = envelope.call().map(JsonCallMessage::from).orElse(null);
sourceDevice,
timestamp,
dataMessage,
+ storyMessage,
syncMessage,
callMessage,
receiptMessage,
--- /dev/null
+package org.asamk.signal.json;
+
+import org.asamk.signal.manager.api.MessageEnvelope;
+
+import java.util.UUID;
+
+record JsonStoryContext(
+ String authorNumber, String authorUuid, long sentTimestamp
+) {
+
+ static JsonStoryContext from(MessageEnvelope.Data.StoryContext storyContext) {
+ return new JsonStoryContext(storyContext.author().number().orElse(null),
+ storyContext.author().uuid().map(UUID::toString).orElse(null),
+ storyContext.sentTimestamp());
+ }
+}
--- /dev/null
+package org.asamk.signal.json;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import org.asamk.signal.manager.api.Color;
+import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.groups.GroupId;
+
+record JsonStoryMessage(
+ boolean allowsReplies,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String groupId,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonAttachment fileAttachment,
+ @JsonInclude(JsonInclude.Include.NON_NULL) TextAttachment textAttachment
+) {
+
+ static JsonStoryMessage from(MessageEnvelope.Story storyMessage) {
+ return new JsonStoryMessage(storyMessage.allowsReplies(),
+ storyMessage.groupId().map(GroupId::toBase64).orElse(null),
+ storyMessage.fileAttachment().map(JsonAttachment::from).orElse(null),
+ storyMessage.textAttachment().map(TextAttachment::from).orElse(null));
+ }
+
+ public record TextAttachment(
+ String text,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String style,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String textForegroundColor,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String textBackgroundColor,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonPreview preview,
+ @JsonInclude(JsonInclude.Include.NON_NULL) Gradient backgroundGradient,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String backgroundColor
+ ) {
+
+ static TextAttachment from(MessageEnvelope.Story.TextAttachment textAttachment) {
+ return new TextAttachment(textAttachment.text().orElse(null),
+ textAttachment.style().map(MessageEnvelope.Story.TextAttachment.Style::name).orElse(null),
+ textAttachment.textForegroundColor().map(Color::toHexColor).orElse(null),
+ textAttachment.textBackgroundColor().map(Color::toHexColor).orElse(null),
+ textAttachment.preview().map(JsonPreview::from).orElse(null),
+ textAttachment.backgroundGradient().map(Gradient::from).orElse(null),
+ textAttachment.backgroundColor().map(Color::toHexColor).orElse(null));
+ }
+
+ public record Gradient(String startColor, String endColor, Integer angle) {
+
+ static Gradient from(MessageEnvelope.Story.TextAttachment.Gradient gradient) {
+ return new Gradient(gradient.startColor().map(Color::toHexColor).orElse(null),
+ gradient.endColor().map(Color::toHexColor).orElse(null),
+ gradient.angle().orElse(null));
+ }
+ }
+ }
+}
record JsonSyncMessage(
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncDataMessage sentMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncStoryMessage sentStoryMessage,
@JsonInclude(JsonInclude.Include.NON_NULL) List<String> blockedNumbers,
@JsonInclude(JsonInclude.Include.NON_NULL) List<String> blockedGroupIds,
@JsonInclude(JsonInclude.Include.NON_NULL) List<JsonSyncReadMessage> readMessages,
@JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessageType type
) {
- JsonSyncMessage(
- final JsonSyncDataMessage sentMessage,
- final List<String> blockedNumbers,
- final List<String> blockedGroupIds,
- final List<JsonSyncReadMessage> readMessages,
- final JsonSyncMessageType type
- ) {
- this.sentMessage = sentMessage;
- this.blockedNumbers = blockedNumbers;
- this.blockedGroupIds = blockedGroupIds;
- this.readMessages = readMessages;
- this.type = type;
- }
-
static JsonSyncMessage from(MessageEnvelope.Sync syncMessage) {
- final var sentMessage = syncMessage.sent().isPresent()
+ final var sentMessage = syncMessage.sent().isPresent() && syncMessage.sent().get().story().isEmpty()
? JsonSyncDataMessage.from(syncMessage.sent().get())
: null;
+ final var sentStoryMessage = syncMessage.sent().isPresent() && syncMessage.sent().get().story().isPresent()
+ ? JsonSyncStoryMessage.from(syncMessage.sent().get())
+ : null;
final List<String> blockedNumbers;
final List<String> blockedGroupIds;
if (syncMessage.blocked().isPresent()) {
} else {
type = null;
}
- return new JsonSyncMessage(sentMessage, blockedNumbers, blockedGroupIds, readMessages, type);
+ return new JsonSyncMessage(sentMessage, sentStoryMessage, blockedNumbers, blockedGroupIds, readMessages, type);
}
}
--- /dev/null
+package org.asamk.signal.json;
+
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+
+import org.asamk.signal.manager.api.MessageEnvelope;
+
+import java.util.UUID;
+
+record JsonSyncStoryMessage(
+ String destinationNumber, String destinationUuid, @JsonUnwrapped JsonStoryMessage dataMessage
+) {
+
+ static JsonSyncStoryMessage from(MessageEnvelope.Sync.Sent transcriptMessage) {
+ if (transcriptMessage.destination().isPresent()) {
+ final var address = transcriptMessage.destination().get();
+ return new JsonSyncStoryMessage(address.number().orElse(null),
+ address.uuid().map(UUID::toString).orElse(null),
+ transcriptMessage.story().map(JsonStoryMessage::from).orElse(null));
+
+ } else {
+ return new JsonSyncStoryMessage(null,
+ null,
+ transcriptMessage.story().map(JsonStoryMessage::from).orElse(null));
+ }
+ }
+}