]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
Update libsignal-service
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / api / MessageEnvelope.java
index dd40c81bf7842bdea764574ed8bc93e8a98c7a49..aefaa800333000db7d557fa3eafd6f917cbc5736 100644 (file)
@@ -1,17 +1,21 @@
 package org.asamk.signal.manager.api;
 
-import org.asamk.signal.manager.groups.GroupId;
 import org.asamk.signal.manager.groups.GroupUtils;
 import org.asamk.signal.manager.helper.RecipientAddressResolver;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
+import org.signal.libsignal.metadata.ProtocolException;
 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
+import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
 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;
@@ -28,7 +32,10 @@ import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptM
 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
 import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
+import org.whispersystems.signalservice.api.push.ServiceId;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -44,8 +51,10 @@ public record MessageEnvelope(
         Optional<Receipt> receipt,
         Optional<Typing> typing,
         Optional<Data> data,
+        Optional<Edit> edit,
         Optional<Sync> sync,
-        Optional<Call> call
+        Optional<Call> call,
+        Optional<Story> story
 ) {
 
     public record Receipt(long when, Type type, List<Long> timestamps) {
@@ -78,7 +87,7 @@ public record MessageEnvelope(
         public static Typing from(final SignalServiceTypingMessage typingMessage) {
             return new Typing(typingMessage.getTimestamp(),
                     typingMessage.isTypingStarted() ? Type.STARTED : Type.STOPPED,
-                    Optional.ofNullable(typingMessage.getGroupId().transform(GroupId::unknownVersion).orNull()));
+                    typingMessage.getGroupId().map(GroupId::unknownVersion));
         }
 
         public enum Type {
@@ -90,61 +99,71 @@ public record MessageEnvelope(
     public record Data(
             long timestamp,
             Optional<GroupContext> groupContext,
+            Optional<StoryContext> storyContext,
             Optional<GroupCallUpdate> groupCallUpdate,
             Optional<String> body,
             int expiresInSeconds,
             boolean isExpirationUpdate,
             boolean isViewOnce,
             boolean isEndSession,
+            boolean isProfileKeyUpdate,
             boolean hasProfileKey,
             Optional<Reaction> reaction,
             Optional<Quote> quote,
+            Optional<Payment> payment,
             List<Attachment> attachments,
             Optional<Long> remoteDeleteId,
             Optional<Sticker> sticker,
             List<SharedContact> sharedContacts,
             List<Mention> mentions,
-            List<Preview> previews
+            List<Preview> previews,
+            List<TextStyle> textStyles
     ) {
 
         static Data from(
                 final SignalServiceDataMessage dataMessage,
                 RecipientResolver recipientResolver,
-                RecipientAddressResolver addressResolver
+                RecipientAddressResolver addressResolver,
+                final AttachmentFileProvider fileProvider
         ) {
             return new Data(dataMessage.getTimestamp(),
-                    Optional.ofNullable(dataMessage.getGroupContext().transform(GroupContext::from).orNull()),
-                    Optional.ofNullable(dataMessage.getGroupCallUpdate().transform(GroupCallUpdate::from).orNull()),
-                    Optional.ofNullable(dataMessage.getBody().orNull()),
+                    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(),
                     dataMessage.isExpirationUpdate(),
                     dataMessage.isViewOnce(),
                     dataMessage.isEndSession(),
+                    dataMessage.isProfileKeyUpdate(),
                     dataMessage.getProfileKey().isPresent(),
-                    Optional.ofNullable(dataMessage.getReaction()
-                            .transform(r -> Reaction.from(r, recipientResolver, addressResolver))
-                            .orNull()),
-                    Optional.ofNullable(dataMessage.getQuote()
-                            .transform(q -> Quote.from(q, recipientResolver, addressResolver))
-                            .orNull()),
+                    dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
+                    dataMessage.getQuote()
+                            .filter(q -> q.getAuthor() != null && q.getAuthor().isValid())
+                            .map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)),
+                    dataMessage.getPayment().map(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null),
                     dataMessage.getAttachments()
-                            .transform(a -> a.stream().map(Attachment::from).collect(Collectors.toList()))
-                            .or(List.of()),
-                    Optional.ofNullable(dataMessage.getRemoteDelete()
-                            .transform(SignalServiceDataMessage.RemoteDelete::getTargetSentTimestamp)
-                            .orNull()),
-                    Optional.ofNullable(dataMessage.getSticker().transform(Sticker::from).orNull()),
+                            .map(a -> a.stream().map(as -> Attachment.from(as, fileProvider)).toList())
+                            .orElse(List.of()),
+                    dataMessage.getRemoteDelete().map(SignalServiceDataMessage.RemoteDelete::getTargetSentTimestamp),
+                    dataMessage.getSticker().map(Sticker::from),
                     dataMessage.getSharedContacts()
-                            .transform(a -> a.stream().map(SharedContact::from).collect(Collectors.toList()))
-                            .or(List.of()),
+                            .map(a -> a.stream()
+                                    .map(sharedContact -> SharedContact.from(sharedContact, fileProvider))
+                                    .toList())
+                            .orElse(List.of()),
                     dataMessage.getMentions()
-                            .transform(a -> a.stream()
-                                    .map(m -> Mention.from(m, recipientResolver, addressResolver))
-                                    .collect(Collectors.toList()))
-                            .or(List.of()),
+                            .map(a -> a.stream().map(m -> Mention.from(m, recipientResolver, addressResolver)).toList())
+                            .orElse(List.of()),
                     dataMessage.getPreviews()
-                            .transform(a -> a.stream().map(Preview::from).collect(Collectors.toList()))
-                            .or(List.of()));
+                            .map(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList())
+                            .orElse(List.of()),
+                    dataMessage.getBodyRanges()
+                            .map(a -> a.stream().filter(r -> r.style != null).map(TextStyle::from).toList())
+                            .orElse(List.of()));
         }
 
         public record GroupContext(GroupId groupId, boolean isGroupUpdate, int revision) {
@@ -165,6 +184,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())).toApiRecipientAddress(), storyContext.getSentTimestamp());
+            }
+        }
+
         public record GroupCallUpdate(String eraId) {
 
             static GroupCallUpdate from(SignalServiceDataMessage.GroupCallUpdate groupCallUpdate) {
@@ -182,7 +213,8 @@ public record MessageEnvelope(
                     RecipientAddressResolver addressResolver
             ) {
                 return new Reaction(reaction.getTargetSentTimestamp(),
-                        addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())),
+                        addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor()))
+                                .toApiRecipientAddress(),
                         reaction.getEmoji(),
                         reaction.isRemove());
             }
@@ -193,26 +225,44 @@ public record MessageEnvelope(
                 RecipientAddress author,
                 Optional<String> text,
                 List<Mention> mentions,
-                List<Attachment> attachments
+                List<Attachment> attachments,
+                List<TextStyle> textStyles
         ) {
 
             static Quote from(
                     SignalServiceDataMessage.Quote quote,
                     RecipientResolver recipientResolver,
-                    RecipientAddressResolver addressResolver
+                    RecipientAddressResolver addressResolver,
+                    final AttachmentFileProvider fileProvider
             ) {
                 return new Quote(quote.getId(),
-                        addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
-                        Optional.ofNullable(quote.getText()),
+                        addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor()))
+                                .toApiRecipientAddress(),
+                        Optional.of(quote.getText()),
                         quote.getMentions() == null
                                 ? List.of()
                                 : quote.getMentions()
                                         .stream()
                                         .map(m -> Mention.from(m, recipientResolver, addressResolver))
-                                        .collect(Collectors.toList()),
+                                        .toList(),
                         quote.getAttachments() == null
                                 ? List.of()
-                                : quote.getAttachments().stream().map(Attachment::from).collect(Collectors.toList()));
+                                : quote.getAttachments().stream().map(a -> Attachment.from(a, fileProvider)).toList(),
+                        quote.getBodyRanges() == null
+                                ? List.of()
+                                : quote.getBodyRanges()
+                                        .stream()
+                                        .filter(r -> r.style != null)
+                                        .map(TextStyle::from)
+                                        .toList());
+            }
+        }
+
+        public record Payment(String note, byte[] receipt) {
+
+            static Payment from(SignalServiceDataMessage.Payment payment) {
+                return new Payment(payment.getPaymentNotification().get().getNote(),
+                        payment.getPaymentNotification().get().getReceipt());
             }
         }
 
@@ -223,14 +273,14 @@ public record MessageEnvelope(
                     RecipientResolver recipientResolver,
                     RecipientAddressResolver addressResolver
             ) {
-                return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getUuid())),
-                        mention.getStart(),
-                        mention.getLength());
+                return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId()))
+                        .toApiRecipientAddress(), mention.getStart(), mention.getLength());
             }
         }
 
         public record Attachment(
                 Optional<String> id,
+                Optional<File> file,
                 Optional<String> fileName,
                 String contentType,
                 Optional<Long> uploadTimestamp,
@@ -245,48 +295,61 @@ public record MessageEnvelope(
                 boolean isBorderless
         ) {
 
-            static Attachment from(SignalServiceAttachment attachment) {
-                if (attachment.isPointer()) {
-                    final var a = attachment.asPointer();
-                    return new Attachment(Optional.of(a.getRemoteId().toString()),
-                            Optional.ofNullable(a.getFileName().orNull()),
+            static Attachment from(SignalServiceAttachment signalAttachment, AttachmentFileProvider fileProvider) {
+                if (signalAttachment.isPointer()) {
+                    final var a = signalAttachment.asPointer();
+                    final var attachmentFile = fileProvider.getFile(a);
+                    return new Attachment(Optional.of(attachmentFile.getName()),
+                            Optional.of(attachmentFile),
+                            a.getFileName(),
                             a.getContentType(),
                             a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
-                            Optional.ofNullable(a.getSize().transform(Integer::longValue).orNull()),
-                            Optional.ofNullable(a.getPreview().orNull()),
+                            a.getSize().map(Integer::longValue),
+                            a.getPreview(),
                             Optional.empty(),
-                            Optional.ofNullable(a.getCaption().orNull()),
+                            a.getCaption().map(c -> c.isEmpty() ? null : c),
                             a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
                             a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
                             a.getVoiceNote(),
                             a.isGif(),
                             a.isBorderless());
                 } else {
-                    final var a = attachment.asStream();
-                    return new Attachment(Optional.empty(),
-                            Optional.ofNullable(a.getFileName().orNull()),
-                            a.getContentType(),
-                            a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
-                            Optional.of(a.getLength()),
-                            Optional.ofNullable(a.getPreview().orNull()),
-                            Optional.empty(),
-                            Optional.ofNullable(a.getCaption().orNull()),
-                            a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
-                            a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
-                            a.getVoiceNote(),
-                            a.isGif(),
-                            a.isBorderless());
+                    Attachment attachment = null;
+                    try (final var a = signalAttachment.asStream()) {
+                        attachment = new Attachment(Optional.empty(),
+                                Optional.empty(),
+                                a.getFileName(),
+                                a.getContentType(),
+                                a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
+                                Optional.of(a.getLength()),
+                                a.getPreview(),
+                                Optional.empty(),
+                                a.getCaption(),
+                                a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
+                                a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
+                                a.getVoiceNote(),
+                                a.isGif(),
+                                a.isBorderless());
+                        return attachment;
+                    } catch (IOException e) {
+                        return attachment;
+                    }
                 }
             }
 
-            static Attachment from(SignalServiceDataMessage.Quote.QuotedAttachment a) {
+            static Attachment from(
+                    SignalServiceDataMessage.Quote.QuotedAttachment a, final AttachmentFileProvider fileProvider
+            ) {
                 return new Attachment(Optional.empty(),
+                        Optional.empty(),
                         Optional.ofNullable(a.getFileName()),
                         a.getContentType(),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
-                        a.getThumbnail() == null ? Optional.empty() : Optional.of(Attachment.from(a.getThumbnail())),
+                        a.getThumbnail() == null
+                                ? Optional.empty()
+                                : Optional.of(Attachment.from(a.getThumbnail(), fileProvider)),
                         Optional.empty(),
                         Optional.empty(),
                         Optional.empty(),
@@ -296,10 +359,12 @@ public record MessageEnvelope(
             }
         }
 
-        public record Sticker(byte[] packId, byte[] packKey, int stickerId) {
+        public record Sticker(StickerPackId packId, byte[] packKey, int stickerId) {
 
             static Sticker from(SignalServiceDataMessage.Sticker sticker) {
-                return new Sticker(sticker.getPackId(), sticker.getPackKey(), sticker.getStickerId());
+                return new Sticker(StickerPackId.deserialize(sticker.getPackId()),
+                        sticker.getPackKey(),
+                        sticker.getStickerId());
             }
         }
 
@@ -312,23 +377,20 @@ public record MessageEnvelope(
                 Optional<String> organization
         ) {
 
-            static SharedContact from(org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact) {
+            static SharedContact from(
+                    org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact,
+                    final AttachmentFileProvider fileProvider
+            ) {
                 return new SharedContact(Name.from(sharedContact.getName()),
-                        Optional.ofNullable(sharedContact.getAvatar().transform(Avatar::from).orNull()),
-                        sharedContact.getPhone()
-                                .transform(p -> p.stream().map(Phone::from).collect(Collectors.toList()))
-                                .or(List.of()),
-                        sharedContact.getEmail()
-                                .transform(p -> p.stream().map(Email::from).collect(Collectors.toList()))
-                                .or(List.of()),
-                        sharedContact.getAddress()
-                                .transform(p -> p.stream().map(Address::from).collect(Collectors.toList()))
-                                .or(List.of()),
-                        Optional.ofNullable(sharedContact.getOrganization().orNull()));
+                        sharedContact.getAvatar().map(avatar1 -> Avatar.from(avatar1, fileProvider)),
+                        sharedContact.getPhone().map(p -> p.stream().map(Phone::from).toList()).orElse(List.of()),
+                        sharedContact.getEmail().map(p -> p.stream().map(Email::from).toList()).orElse(List.of()),
+                        sharedContact.getAddress().map(p -> p.stream().map(Address::from).toList()).orElse(List.of()),
+                        sharedContact.getOrganization());
             }
 
             public record Name(
-                    Optional<String> display,
+                    Optional<String> nickname,
                     Optional<String> given,
                     Optional<String> family,
                     Optional<String> prefix,
@@ -337,19 +399,22 @@ public record MessageEnvelope(
             ) {
 
                 static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) {
-                    return new Name(Optional.ofNullable(name.getDisplay().orNull()),
-                            Optional.ofNullable(name.getGiven().orNull()),
-                            Optional.ofNullable(name.getFamily().orNull()),
-                            Optional.ofNullable(name.getPrefix().orNull()),
-                            Optional.ofNullable(name.getSuffix().orNull()),
-                            Optional.ofNullable(name.getMiddle().orNull()));
+                    return new Name(name.getNickname(),
+                            name.getGiven(),
+                            name.getFamily(),
+                            name.getPrefix(),
+                            name.getSuffix(),
+                            name.getMiddle());
                 }
             }
 
             public record Avatar(Attachment attachment, boolean isProfile) {
 
-                static Avatar from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar) {
-                    return new Avatar(Attachment.from(avatar.getAttachment()), avatar.isProfile());
+                static Avatar from(
+                        org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar,
+                        final AttachmentFileProvider fileProvider
+                ) {
+                    return new Avatar(Attachment.from(avatar.getAttachment(), fileProvider), avatar.isProfile());
                 }
             }
 
@@ -358,9 +423,7 @@ public record MessageEnvelope(
             ) {
 
                 static Phone from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone phone) {
-                    return new Phone(phone.getValue(),
-                            Type.from(phone.getType()),
-                            Optional.ofNullable(phone.getLabel().orNull()));
+                    return new Phone(phone.getValue(), Type.from(phone.getType()), phone.getLabel());
                 }
 
                 public enum Type {
@@ -385,9 +448,7 @@ public record MessageEnvelope(
             ) {
 
                 static Email from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email email) {
-                    return new Email(email.getValue(),
-                            Type.from(email.getType()),
-                            Optional.ofNullable(email.getLabel().orNull()));
+                    return new Email(email.getValue(), Type.from(email.getType()), email.getLabel());
                 }
 
                 public enum Type {
@@ -420,15 +481,15 @@ public record MessageEnvelope(
             ) {
 
                 static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) {
-                    return new Address(Address.Type.from(address.getType()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()),
-                            Optional.ofNullable(address.getLabel().orNull()));
+                    return new Address(Type.from(address.getType()),
+                            address.getLabel(),
+                            address.getStreet(),
+                            address.getPobox(),
+                            address.getNeighborhood(),
+                            address.getCity(),
+                            address.getRegion(),
+                            address.getPostcode(),
+                            address.getCountry());
                 }
 
                 public enum Type {
@@ -449,14 +510,30 @@ public record MessageEnvelope(
 
         public record Preview(String title, String description, long date, String url, Optional<Attachment> image) {
 
-            static Preview from(SignalServiceDataMessage.Preview preview) {
+            static Preview from(
+                    SignalServicePreview preview, final AttachmentFileProvider fileProvider
+            ) {
                 return new Preview(preview.getTitle(),
                         preview.getDescription(),
                         preview.getDate(),
                         preview.getUrl(),
-                        Optional.ofNullable(preview.getImage().transform(Attachment::from).orNull()));
+                        preview.getImage().map(as -> Attachment.from(as, fileProvider)));
             }
         }
+
+    }
+
+    public record Edit(long targetSentTimestamp, Data dataMessage) {
+
+        public static Edit from(
+                final SignalServiceEditMessage editMessage,
+                RecipientResolver recipientResolver,
+                RecipientAddressResolver addressResolver,
+                final AttachmentFileProvider fileProvider
+        ) {
+            return new Edit(editMessage.getTargetSentTimestamp(),
+                    Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider));
+        }
     }
 
     public record Sync(
@@ -473,32 +550,25 @@ public record MessageEnvelope(
         public static Sync from(
                 final SignalServiceSyncMessage syncMessage,
                 RecipientResolver recipientResolver,
-                RecipientAddressResolver addressResolver
+                RecipientAddressResolver addressResolver,
+                final AttachmentFileProvider fileProvider
         ) {
-            return new Sync(Optional.ofNullable(syncMessage.getSent()
-                    .transform(s -> Sent.from(s, recipientResolver, addressResolver))
-                    .orNull()),
-                    Optional.ofNullable(syncMessage.getBlockedList()
-                            .transform(b -> Blocked.from(b, recipientResolver, addressResolver))
-                            .orNull()),
+            return new Sync(syncMessage.getSent()
+                    .map(s -> Sent.from(s, recipientResolver, addressResolver, fileProvider)),
+                    syncMessage.getBlockedList().map(b -> Blocked.from(b, recipientResolver, addressResolver)),
                     syncMessage.getRead()
-                            .transform(r -> r.stream()
-                                    .map(rm -> Read.from(rm, recipientResolver, addressResolver))
-                                    .collect(Collectors.toList()))
-                            .or(List.of()),
+                            .map(r -> r.stream().map(rm -> Read.from(rm, recipientResolver, addressResolver)).toList())
+                            .orElse(List.of()),
                     syncMessage.getViewed()
-                            .transform(r -> r.stream()
+                            .map(r -> r.stream()
                                     .map(rm -> Viewed.from(rm, recipientResolver, addressResolver))
-                                    .collect(Collectors.toList()))
-                            .or(List.of()),
-                    Optional.ofNullable(syncMessage.getViewOnceOpen()
-                            .transform(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver))
-                            .orNull()),
-                    Optional.ofNullable(syncMessage.getContacts().transform(Contacts::from).orNull()),
-                    Optional.ofNullable(syncMessage.getGroups().transform(Groups::from).orNull()),
-                    Optional.ofNullable(syncMessage.getMessageRequestResponse()
-                            .transform(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver))
-                            .orNull()));
+                                    .toList())
+                            .orElse(List.of()),
+                    syncMessage.getViewOnceOpen().map(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver)),
+                    syncMessage.getContacts().map(Contacts::from),
+                    syncMessage.getGroups().map(Groups::from),
+                    syncMessage.getMessageRequestResponse()
+                            .map(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver)));
         }
 
         public record Sent(
@@ -506,25 +576,32 @@ public record MessageEnvelope(
                 long expirationStartTimestamp,
                 Optional<RecipientAddress> destination,
                 Set<RecipientAddress> recipients,
-                Data message
+                Optional<Data> message,
+                Optional<Edit> editMessage,
+                Optional<Story> story
         ) {
 
             static Sent from(
                     SentTranscriptMessage sentMessage,
                     RecipientResolver recipientResolver,
-                    RecipientAddressResolver addressResolver
+                    RecipientAddressResolver addressResolver,
+                    final AttachmentFileProvider fileProvider
             ) {
                 return new Sent(sentMessage.getTimestamp(),
                         sentMessage.getExpirationStartTimestamp(),
-                        Optional.ofNullable(sentMessage.getDestination()
-                                .transform(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
-                                        d)))
-                                .orNull()),
+                        sentMessage.getDestination()
+                                .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+                                        .toApiRecipientAddress()),
                         sentMessage.getRecipients()
                                 .stream()
-                                .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
+                                .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+                                        .toApiRecipientAddress())
                                 .collect(Collectors.toSet()),
-                        Data.from(sentMessage.getMessage(), recipientResolver, addressResolver));
+                        sentMessage.getDataMessage()
+                                .map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
+                        sentMessage.getEditMessage()
+                                .map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)),
+                        sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
             }
         }
 
@@ -537,12 +614,9 @@ public record MessageEnvelope(
             ) {
                 return new Blocked(blockedListMessage.getAddresses()
                         .stream()
-                        .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
-                        .collect(Collectors.toList()),
-                        blockedListMessage.getGroupIds()
-                                .stream()
-                                .map(GroupId::unknownVersion)
-                                .collect(Collectors.toList()));
+                        .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+                                .toApiRecipientAddress())
+                        .toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList());
             }
         }
 
@@ -553,8 +627,8 @@ public record MessageEnvelope(
                     RecipientResolver recipientResolver,
                     RecipientAddressResolver addressResolver
             ) {
-                return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
-                        readMessage.getTimestamp());
+                return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
+                        .toApiRecipientAddress(), readMessage.getTimestamp());
             }
         }
 
@@ -565,8 +639,8 @@ public record MessageEnvelope(
                     RecipientResolver recipientResolver,
                     RecipientAddressResolver addressResolver
             ) {
-                return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
-                        readMessage.getTimestamp());
+                return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
+                        .toApiRecipientAddress(), readMessage.getTimestamp());
             }
         }
 
@@ -578,7 +652,7 @@ public record MessageEnvelope(
                     RecipientAddressResolver addressResolver
             ) {
                 return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
-                        readMessage.getSender())), readMessage.getTimestamp());
+                        readMessage.getSender())).toApiRecipientAddress(), readMessage.getTimestamp());
             }
         }
 
@@ -604,13 +678,10 @@ public record MessageEnvelope(
                     RecipientAddressResolver addressResolver
             ) {
                 return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
-                        Optional.ofNullable(messageRequestResponse.getGroupId()
-                                .transform(GroupId::unknownVersion)
-                                .orNull()),
-                        Optional.ofNullable(messageRequestResponse.getPerson()
-                                .transform(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
-                                        p)))
-                                .orNull()));
+                        messageRequestResponse.getGroupId().map(GroupId::unknownVersion),
+                        messageRequestResponse.getPerson()
+                                .map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p))
+                                        .toApiRecipientAddress()));
             }
 
             public enum Type {
@@ -619,7 +690,9 @@ public record MessageEnvelope(
                 DELETE,
                 BLOCK,
                 BLOCK_AND_DELETE,
-                UNBLOCK_AND_ACCEPT;
+                UNBLOCK_AND_ACCEPT,
+                SPAM,
+                BLOCK_AND_SPAM;
 
                 static Type from(MessageRequestResponseMessage.Type type) {
                     return switch (type) {
@@ -629,6 +702,8 @@ public record MessageEnvelope(
                         case BLOCK -> BLOCK;
                         case BLOCK_AND_DELETE -> BLOCK_AND_DELETE;
                         case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT;
+                        case SPAM -> SPAM;
+                        case BLOCK_AND_SPAM -> BLOCK_AND_SPAM;
                     };
                 }
             }
@@ -644,30 +719,29 @@ public record MessageEnvelope(
             Optional<Hangup> hangup,
             Optional<Busy> busy,
             List<IceUpdate> iceUpdate,
-            Optional<Opaque> opaque
+            Optional<Opaque> opaque,
+            boolean isUrgent
     ) {
 
         public static Call from(final SignalServiceCallMessage callMessage) {
-            return new Call(Optional.ofNullable(callMessage.getDestinationDeviceId().orNull()),
-                    Optional.ofNullable(callMessage.getGroupId().transform(GroupId::unknownVersion).orNull()),
-                    Optional.ofNullable(callMessage.getTimestamp().orNull()),
-                    Optional.ofNullable(callMessage.getOfferMessage().transform(Offer::from).orNull()),
-                    Optional.ofNullable(callMessage.getAnswerMessage().transform(Answer::from).orNull()),
-                    Optional.ofNullable(callMessage.getHangupMessage().transform(Hangup::from).orNull()),
-                    Optional.ofNullable(callMessage.getBusyMessage().transform(Busy::from).orNull()),
+            return new Call(callMessage.getDestinationDeviceId(),
+                    callMessage.getGroupId().map(GroupId::unknownVersion),
+                    callMessage.getTimestamp(),
+                    callMessage.getOfferMessage().map(Offer::from),
+                    callMessage.getAnswerMessage().map(Answer::from),
+                    callMessage.getHangupMessage().map(Hangup::from),
+                    callMessage.getBusyMessage().map(Busy::from),
                     callMessage.getIceUpdateMessages()
-                            .transform(m -> m.stream().map(IceUpdate::from).collect(Collectors.toList()))
-                            .or(List.of()),
-                    Optional.ofNullable(callMessage.getOpaqueMessage().transform(Opaque::from).orNull()));
+                            .map(m -> m.stream().map(IceUpdate::from).toList())
+                            .orElse(List.of()),
+                    callMessage.getOpaqueMessage().map(Opaque::from),
+                    callMessage.isUrgent());
         }
 
-        public record Offer(long id, String sdp, Type type, byte[] opaque) {
+        public record Offer(long id, Type type, byte[] opaque) {
 
             static Offer from(OfferMessage offerMessage) {
-                return new Offer(offerMessage.getId(),
-                        offerMessage.getSdp(),
-                        Type.from(offerMessage.getType()),
-                        offerMessage.getOpaque());
+                return new Offer(offerMessage.getId(), Type.from(offerMessage.getType()), offerMessage.getOpaque());
             }
 
             public enum Type {
@@ -683,34 +757,26 @@ public record MessageEnvelope(
             }
         }
 
-        public record Answer(long id, String sdp, byte[] opaque) {
+        public record Answer(long id, byte[] opaque) {
 
             static Answer from(AnswerMessage answerMessage) {
-                return new Answer(answerMessage.getId(), answerMessage.getSdp(), answerMessage.getOpaque());
+                return new Answer(answerMessage.getId(), answerMessage.getOpaque());
             }
         }
 
         public record Busy(long id) {
 
-            static Offer from(OfferMessage offerMessage) {
-                return new Offer(offerMessage.getId(),
-                        offerMessage.getSdp(),
-                        Offer.Type.from(offerMessage.getType()),
-                        offerMessage.getOpaque());
-            }
-
             static Busy from(BusyMessage busyMessage) {
                 return new Busy(busyMessage.getId());
             }
         }
 
-        public record Hangup(long id, Type type, int deviceId, boolean isLegacy) {
+        public record Hangup(long id, Type type, int deviceId) {
 
             static Hangup from(HangupMessage hangupMessage) {
                 return new Hangup(hangupMessage.getId(),
                         Type.from(hangupMessage.getType()),
-                        hangupMessage.getDeviceId(),
-                        hangupMessage.isLegacy());
+                        hangupMessage.getDeviceId());
             }
 
             public enum Type {
@@ -732,10 +798,10 @@ public record MessageEnvelope(
             }
         }
 
-        public record IceUpdate(long id, String sdp, byte[] opaque) {
+        public record IceUpdate(long id, byte[] opaque) {
 
             static IceUpdate from(IceUpdateMessage iceUpdateMessage) {
-                return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getSdp(), iceUpdateMessage.getOpaque());
+                return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getOpaque());
             }
         }
 
@@ -759,47 +825,130 @@ 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(
+                    List<Color> colors, List<Float> positions, Optional<Integer> angle
+            ) {
+
+                static Gradient from(SignalServiceTextAttachment.Gradient gradient) {
+                    return new Gradient(gradient.getColors().stream().map(Color::new).toList(),
+                            gradient.getPositions(),
+                            gradient.getAngle());
+                }
+            }
+        }
+    }
+
     public static MessageEnvelope from(
             SignalServiceEnvelope envelope,
             SignalServiceContent content,
             RecipientResolver recipientResolver,
-            RecipientAddressResolver addressResolver
+            RecipientAddressResolver addressResolver,
+            final AttachmentFileProvider fileProvider,
+            Exception exception
     ) {
-        final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid()
-                ? recipientResolver.resolveRecipient(envelope.getSourceAddress())
+        final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
+        final var source = !envelope.isUnidentifiedSender() && serviceId != null
+                ? recipientResolver.resolveRecipient(serviceId)
                 : envelope.isUnidentifiedSender() && content != null
                         ? recipientResolver.resolveRecipient(content.getSender())
-                        : null;
+                        : exception instanceof ProtocolException e
+                                ? recipientResolver.resolveRecipient(e.getSender())
+                                : null;
         final var sourceDevice = envelope.hasSourceDevice()
                 ? envelope.getSourceDevice()
-                : content != null ? content.getSenderDevice() : 0;
+                : content != null
+                        ? content.getSenderDevice()
+                        : exception instanceof ProtocolException e ? e.getSenderDevice() : 0;
 
         Optional<Receipt> receipt;
         Optional<Typing> typing;
         Optional<Data> data;
+        Optional<Edit> edit;
         Optional<Sync> sync;
         Optional<Call> call;
+        Optional<Story> story;
         if (content != null) {
-            receipt = Optional.ofNullable(content.getReceiptMessage().transform(Receipt::from).orNull());
-            typing = Optional.ofNullable(content.getTypingMessage().transform(Typing::from).orNull());
-            data = Optional.ofNullable(content.getDataMessage()
-                    .transform(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver))
-                    .orNull());
-            sync = Optional.ofNullable(content.getSyncMessage()
-                    .transform(s -> Sync.from(s, recipientResolver, addressResolver))
-                    .orNull());
-            call = Optional.ofNullable(content.getCallMessage().transform(Call::from).orNull());
+            receipt = content.getReceiptMessage().map(Receipt::from);
+            typing = content.getTypingMessage().map(Typing::from);
+            data = content.getDataMessage()
+                    .map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider));
+            edit = content.getEditMessage().map(s -> Edit.from(s, 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 = Optional.empty();
+            receipt = envelope.isReceipt() ? Optional.of(new Receipt(envelope.getServerReceivedTimestamp(),
+                    Receipt.Type.DELIVERY,
+                    List.of(envelope.getTimestamp()))) : Optional.empty();
             typing = Optional.empty();
             data = Optional.empty();
+            edit = Optional.empty();
             sync = Optional.empty();
             call = Optional.empty();
+            story = Optional.empty();
         }
 
         return new MessageEnvelope(source == null
                 ? Optional.empty()
-                : Optional.of(addressResolver.resolveRecipientAddress(source)),
+                : Optional.of(addressResolver.resolveRecipientAddress(source).toApiRecipientAddress()),
                 sourceDevice,
                 envelope.getTimestamp(),
                 envelope.getServerReceivedTimestamp(),
@@ -808,7 +957,14 @@ public record MessageEnvelope(
                 receipt,
                 typing,
                 data,
+                edit,
                 sync,
-                call);
+                call,
+                story);
+    }
+
+    public interface AttachmentFileProvider {
+
+        File getFile(SignalServiceAttachmentPointer pointer);
     }
 }