mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Implement receive handling for story messages
This commit is contained in:
parent
81e36d4f9b
commit
a593051512
12 changed files with 443 additions and 47 deletions
24
lib/src/main/java/org/asamk/signal/manager/api/Color.java
Normal file
24
lib/src/main/java/org/asamk/signal/manager/api/Color.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
|||
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;
|
||||
|
@ -49,7 +51,8 @@ public record MessageEnvelope(
|
|||
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) {
|
||||
|
@ -94,6 +97,7 @@ public record MessageEnvelope(
|
|||
public record Data(
|
||||
long timestamp,
|
||||
Optional<GroupContext> groupContext,
|
||||
Optional<StoryContext> storyContext,
|
||||
Optional<GroupCallUpdate> groupCallUpdate,
|
||||
Optional<String> body,
|
||||
int expiresInSeconds,
|
||||
|
@ -121,6 +125,10 @@ public record MessageEnvelope(
|
|||
) {
|
||||
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(),
|
||||
|
@ -168,6 +176,18 @@ public record MessageEnvelope(
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -519,7 +539,8 @@ public record MessageEnvelope(
|
|||
long expirationStartTimestamp,
|
||||
Optional<RecipientAddress> destination,
|
||||
Set<RecipientAddress> recipients,
|
||||
Optional<Data> message
|
||||
Optional<Data> message,
|
||||
Optional<Story> story
|
||||
) {
|
||||
|
||||
static Sent from(
|
||||
|
@ -537,7 +558,8 @@ public record MessageEnvelope(
|
|||
.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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -757,6 +779,75 @@ public record MessageEnvelope(
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -783,6 +874,7 @@ public record MessageEnvelope(
|
|||
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);
|
||||
|
@ -790,6 +882,7 @@ public record MessageEnvelope(
|
|||
.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,
|
||||
|
@ -798,6 +891,7 @@ public record MessageEnvelope(
|
|||
data = Optional.empty();
|
||||
sync = Optional.empty();
|
||||
call = Optional.empty();
|
||||
story = Optional.empty();
|
||||
}
|
||||
|
||||
return new MessageEnvelope(source == null
|
||||
|
@ -812,7 +906,8 @@ public record MessageEnvelope(
|
|||
typing,
|
||||
data,
|
||||
sync,
|
||||
call);
|
||||
call,
|
||||
story);
|
||||
}
|
||||
|
||||
public interface AttachmentFileProvider {
|
||||
|
|
|
@ -52,7 +52,9 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
|||
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;
|
||||
|
@ -276,6 +278,11 @@ public final class IncomingMessageHandler {
|
|||
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()));
|
||||
|
@ -347,6 +354,11 @@ public final class IncomingMessageHandler {
|
|||
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();
|
||||
|
@ -566,8 +578,9 @@ public final class IncomingMessageHandler {
|
|||
) {
|
||||
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) {
|
||||
|
@ -620,14 +633,8 @@ public final class IncomingMessageHandler {
|
|||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -637,8 +644,9 @@ public final class IncomingMessageHandler {
|
|||
}
|
||||
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()) {
|
||||
|
@ -646,7 +654,7 @@ public final class IncomingMessageHandler {
|
|||
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) {
|
||||
|
@ -686,17 +694,8 @@ public final class IncomingMessageHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
@ -711,6 +710,62 @@ public final class IncomingMessageHandler {
|
|||
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()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue