1 package org
.asamk
.signal
.manager
.api
;
3 import org
.asamk
.signal
.manager
.groups
.GroupId
;
4 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
5 import org
.asamk
.signal
.manager
.helper
.RecipientAddressResolver
;
6 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientAddress
;
7 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
8 import org
.signal
.libsignal
.metadata
.ProtocolException
;
9 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
10 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
11 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
12 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
13 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
14 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
15 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupContext
;
16 import org
.whispersystems
.signalservice
.api
.messages
.SignalServicePreview
;
17 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
18 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStoryMessage
;
19 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTextAttachment
;
20 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.calls
.AnswerMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.calls
.BusyMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.calls
.HangupMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.calls
.IceUpdateMessage
;
25 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OfferMessage
;
26 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OpaqueMessage
;
27 import org
.whispersystems
.signalservice
.api
.messages
.calls
.SignalServiceCallMessage
;
28 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
29 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.MessageRequestResponseMessage
;
31 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ReadMessage
;
32 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
33 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
34 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewOnceOpenMessage
;
35 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewedMessage
;
38 import java
.util
.List
;
39 import java
.util
.Optional
;
41 import java
.util
.stream
.Collectors
;
43 public record MessageEnvelope(
44 Optional
<RecipientAddress
> sourceAddress
,
47 long serverReceivedTimestamp
,
48 long serverDeliveredTimestamp
,
49 boolean isUnidentifiedSender
,
50 Optional
<Receipt
> receipt
,
51 Optional
<Typing
> typing
,
58 public record Receipt(long when, Type type
, List
<Long
> timestamps
) {
60 static Receipt
from(final SignalServiceReceiptMessage receiptMessage
) {
61 return new Receipt(receiptMessage
.getWhen(),
62 Type
.from(receiptMessage
.getType()),
63 receiptMessage
.getTimestamps());
72 static Type
from(SignalServiceReceiptMessage
.Type type
) {
73 return switch (type
) {
74 case DELIVERY
-> DELIVERY
;
76 case VIEWED
-> VIEWED
;
77 case UNKNOWN
-> UNKNOWN
;
83 public record Typing(long timestamp
, Type type
, Optional
<GroupId
> groupId
) {
85 public static Typing
from(final SignalServiceTypingMessage typingMessage
) {
86 return new Typing(typingMessage
.getTimestamp(),
87 typingMessage
.isTypingStarted() ? Type
.STARTED
: Type
.STOPPED
,
88 typingMessage
.getGroupId().map(GroupId
::unknownVersion
));
99 Optional
<GroupContext
> groupContext
,
100 Optional
<StoryContext
> storyContext
,
101 Optional
<GroupCallUpdate
> groupCallUpdate
,
102 Optional
<String
> body
,
103 int expiresInSeconds
,
104 boolean isExpirationUpdate
,
106 boolean isEndSession
,
107 boolean isProfileKeyUpdate
,
108 boolean hasProfileKey
,
109 Optional
<Reaction
> reaction
,
110 Optional
<Quote
> quote
,
111 Optional
<Payment
> payment
,
112 List
<Attachment
> attachments
,
113 Optional
<Long
> remoteDeleteId
,
114 Optional
<Sticker
> sticker
,
115 List
<SharedContact
> sharedContacts
,
116 List
<Mention
> mentions
,
117 List
<Preview
> previews
121 final SignalServiceDataMessage dataMessage
,
122 RecipientResolver recipientResolver
,
123 RecipientAddressResolver addressResolver
,
124 final AttachmentFileProvider fileProvider
126 return new Data(dataMessage
.getTimestamp(),
127 dataMessage
.getGroupContext().map(GroupContext
::from
),
128 dataMessage
.getStoryContext()
129 .map((SignalServiceDataMessage
.StoryContext storyContext
) -> StoryContext
.from(storyContext
,
132 dataMessage
.getGroupCallUpdate().map(GroupCallUpdate
::from
),
133 dataMessage
.getBody(),
134 dataMessage
.getExpiresInSeconds(),
135 dataMessage
.isExpirationUpdate(),
136 dataMessage
.isViewOnce(),
137 dataMessage
.isEndSession(),
138 dataMessage
.isProfileKeyUpdate(),
139 dataMessage
.getProfileKey().isPresent(),
140 dataMessage
.getReaction().map(r
-> Reaction
.from(r
, recipientResolver
, addressResolver
)),
141 dataMessage
.getQuote().map(q
-> Quote
.from(q
, recipientResolver
, addressResolver
, fileProvider
)),
142 dataMessage
.getPayment().map(p
-> p
.getPaymentNotification().isPresent() ? Payment
.from(p
) : null),
143 dataMessage
.getAttachments()
144 .map(a
-> a
.stream().map(as -> Attachment
.from(as, fileProvider
)).toList())
146 dataMessage
.getRemoteDelete().map(SignalServiceDataMessage
.RemoteDelete
::getTargetSentTimestamp
),
147 dataMessage
.getSticker().map(Sticker
::from
),
148 dataMessage
.getSharedContacts()
150 .map(sharedContact
-> SharedContact
.from(sharedContact
, fileProvider
))
153 dataMessage
.getMentions()
154 .map(a
-> a
.stream().map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
)).toList())
156 dataMessage
.getPreviews()
157 .map(a
-> a
.stream().map(preview
-> Preview
.from(preview
, fileProvider
)).toList())
161 public record GroupContext(GroupId groupId
, boolean isGroupUpdate
, int revision
) {
163 static GroupContext
from(SignalServiceGroupContext groupContext
) {
164 if (groupContext
.getGroupV1().isPresent()) {
165 return new GroupContext(GroupId
.v1(groupContext
.getGroupV1().get().getGroupId()),
166 groupContext
.getGroupV1Type() == SignalServiceGroup
.Type
.UPDATE
,
168 } else if (groupContext
.getGroupV2().isPresent()) {
169 final var groupV2
= groupContext
.getGroupV2().get();
170 return new GroupContext(GroupUtils
.getGroupIdV2(groupV2
.getMasterKey()),
171 groupV2
.hasSignedGroupChange(),
172 groupV2
.getRevision());
174 throw new RuntimeException("Invalid group context state");
179 public record StoryContext(RecipientAddress author
, long sentTimestamp
) {
181 static StoryContext
from(
182 SignalServiceDataMessage
.StoryContext storyContext
,
183 RecipientResolver recipientResolver
,
184 RecipientAddressResolver addressResolver
186 return new StoryContext(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
187 storyContext
.getAuthorServiceId())), storyContext
.getSentTimestamp());
191 public record GroupCallUpdate(String eraId
) {
193 static GroupCallUpdate
from(SignalServiceDataMessage
.GroupCallUpdate groupCallUpdate
) {
194 return new GroupCallUpdate(groupCallUpdate
.getEraId());
198 public record Reaction(
199 long targetSentTimestamp
, RecipientAddress targetAuthor
, String emoji
, boolean isRemove
202 static Reaction
from(
203 SignalServiceDataMessage
.Reaction reaction
,
204 RecipientResolver recipientResolver
,
205 RecipientAddressResolver addressResolver
207 return new Reaction(reaction
.getTargetSentTimestamp(),
208 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(reaction
.getTargetAuthor())),
210 reaction
.isRemove());
216 RecipientAddress author
,
217 Optional
<String
> text
,
218 List
<Mention
> mentions
,
219 List
<Attachment
> attachments
223 SignalServiceDataMessage
.Quote quote
,
224 RecipientResolver recipientResolver
,
225 RecipientAddressResolver addressResolver
,
226 final AttachmentFileProvider fileProvider
228 return new Quote(quote
.getId(),
229 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(quote
.getAuthor())),
230 Optional
.ofNullable(quote
.getText()),
231 quote
.getMentions() == null
233 : quote
.getMentions()
235 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
237 quote
.getAttachments() == null
239 : quote
.getAttachments().stream().map(a
-> Attachment
.from(a
, fileProvider
)).toList());
243 public record Payment(String note
, byte[] receipt
) {
245 static Payment
from(SignalServiceDataMessage
.Payment payment
) {
246 return new Payment(payment
.getPaymentNotification().get().getNote(),
247 payment
.getPaymentNotification().get().getReceipt());
251 public record Mention(RecipientAddress recipient
, int start
, int length
) {
254 SignalServiceDataMessage
.Mention mention
,
255 RecipientResolver recipientResolver
,
256 RecipientAddressResolver addressResolver
258 return new Mention(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(mention
.getServiceId())),
260 mention
.getLength());
264 public record Attachment(
267 Optional
<String
> fileName
,
269 Optional
<Long
> uploadTimestamp
,
271 Optional
<byte[]> preview
,
272 Optional
<Attachment
> thumbnail
,
273 Optional
<String
> caption
,
274 Optional
<Integer
> width
,
275 Optional
<Integer
> height
,
281 static Attachment
from(SignalServiceAttachment attachment
, AttachmentFileProvider fileProvider
) {
282 if (attachment
.isPointer()) {
283 final var a
= attachment
.asPointer();
284 return new Attachment(Optional
.of(a
.getRemoteId().toString()),
285 Optional
.of(fileProvider
.getFile(a
.getRemoteId())),
288 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
289 a
.getSize().map(Integer
::longValue
),
293 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
294 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
299 final var a
= attachment
.asStream();
300 return new Attachment(Optional
.empty(),
304 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
305 Optional
.of(a
.getLength()),
309 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
310 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
317 static Attachment
from(
318 SignalServiceDataMessage
.Quote
.QuotedAttachment a
, final AttachmentFileProvider fileProvider
320 return new Attachment(Optional
.empty(),
322 Optional
.ofNullable(a
.getFileName()),
327 a
.getThumbnail() == null
329 : Optional
.of(Attachment
.from(a
.getThumbnail(), fileProvider
)),
339 public record Sticker(StickerPackId packId
, byte[] packKey
, int stickerId
) {
341 static Sticker
from(SignalServiceDataMessage
.Sticker sticker
) {
342 return new Sticker(StickerPackId
.deserialize(sticker
.getPackId()),
343 sticker
.getPackKey(),
344 sticker
.getStickerId());
348 public record SharedContact(
350 Optional
<Avatar
> avatar
,
353 List
<Address
> address
,
354 Optional
<String
> organization
357 static SharedContact
from(
358 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact sharedContact
,
359 final AttachmentFileProvider fileProvider
361 return new SharedContact(Name
.from(sharedContact
.getName()),
362 sharedContact
.getAvatar().map(avatar1
-> Avatar
.from(avatar1
, fileProvider
)),
363 sharedContact
.getPhone().map(p
-> p
.stream().map(Phone
::from
).toList()).orElse(List
.of()),
364 sharedContact
.getEmail().map(p
-> p
.stream().map(Email
::from
).toList()).orElse(List
.of()),
365 sharedContact
.getAddress().map(p
-> p
.stream().map(Address
::from
).toList()).orElse(List
.of()),
366 sharedContact
.getOrganization());
370 Optional
<String
> display
,
371 Optional
<String
> given
,
372 Optional
<String
> family
,
373 Optional
<String
> prefix
,
374 Optional
<String
> suffix
,
375 Optional
<String
> middle
378 static Name
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Name name
) {
379 return new Name(name
.getDisplay(),
388 public record Avatar(Attachment attachment
, boolean isProfile
) {
391 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Avatar avatar
,
392 final AttachmentFileProvider fileProvider
394 return new Avatar(Attachment
.from(avatar
.getAttachment(), fileProvider
), avatar
.isProfile());
399 String value
, Type type
, Optional
<String
> label
402 static Phone
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone phone
) {
403 return new Phone(phone
.getValue(), Type
.from(phone
.getType()), phone
.getLabel());
412 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone
.Type type
) {
413 return switch (type
) {
416 case MOBILE
-> MOBILE
;
417 case CUSTOM
-> CUSTOM
;
424 String value
, Type type
, Optional
<String
> label
427 static Email
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email email
) {
428 return new Email(email
.getValue(), Type
.from(email
.getType()), email
.getLabel());
437 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email
.Type type
) {
438 return switch (type
) {
441 case MOBILE
-> MOBILE
;
442 case CUSTOM
-> CUSTOM
;
448 public record Address(
450 Optional
<String
> label
,
451 Optional
<String
> street
,
452 Optional
<String
> pobox
,
453 Optional
<String
> neighborhood
,
454 Optional
<String
> city
,
455 Optional
<String
> region
,
456 Optional
<String
> postcode
,
457 Optional
<String
> country
460 static Address
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress address
) {
461 return new Address(Address
.Type
.from(address
.getType()),
477 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress
.Type type
) {
478 return switch (type
) {
481 case CUSTOM
-> CUSTOM
;
488 public record Preview(String title
, String description
, long date
, String url
, Optional
<Attachment
> image
) {
491 SignalServicePreview preview
, final AttachmentFileProvider fileProvider
493 return new Preview(preview
.getTitle(),
494 preview
.getDescription(),
497 preview
.getImage().map(as -> Attachment
.from(as, fileProvider
)));
504 Optional
<Blocked
> blocked
,
507 Optional
<ViewOnceOpen
> viewOnceOpen
,
508 Optional
<Contacts
> contacts
,
509 Optional
<Groups
> groups
,
510 Optional
<MessageRequestResponse
> messageRequestResponse
513 public static Sync
from(
514 final SignalServiceSyncMessage syncMessage
,
515 RecipientResolver recipientResolver
,
516 RecipientAddressResolver addressResolver
,
517 final AttachmentFileProvider fileProvider
519 return new Sync(syncMessage
.getSent()
520 .map(s
-> Sent
.from(s
, recipientResolver
, addressResolver
, fileProvider
)),
521 syncMessage
.getBlockedList().map(b
-> Blocked
.from(b
, recipientResolver
, addressResolver
)),
522 syncMessage
.getRead()
523 .map(r
-> r
.stream().map(rm
-> Read
.from(rm
, recipientResolver
, addressResolver
)).toList())
525 syncMessage
.getViewed()
527 .map(rm
-> Viewed
.from(rm
, recipientResolver
, addressResolver
))
530 syncMessage
.getViewOnceOpen().map(rm
-> ViewOnceOpen
.from(rm
, recipientResolver
, addressResolver
)),
531 syncMessage
.getContacts().map(Contacts
::from
),
532 syncMessage
.getGroups().map(Groups
::from
),
533 syncMessage
.getMessageRequestResponse()
534 .map(m
-> MessageRequestResponse
.from(m
, recipientResolver
, addressResolver
)));
539 long expirationStartTimestamp
,
540 Optional
<RecipientAddress
> destination
,
541 Set
<RecipientAddress
> recipients
,
542 Optional
<Data
> message
,
543 Optional
<Story
> story
547 SentTranscriptMessage sentMessage
,
548 RecipientResolver recipientResolver
,
549 RecipientAddressResolver addressResolver
,
550 final AttachmentFileProvider fileProvider
552 return new Sent(sentMessage
.getTimestamp(),
553 sentMessage
.getExpirationStartTimestamp(),
554 sentMessage
.getDestination()
555 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
))),
556 sentMessage
.getRecipients()
558 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
559 .collect(Collectors
.toSet()),
560 sentMessage
.getDataMessage()
561 .map(message
-> Data
.from(message
, recipientResolver
, addressResolver
, fileProvider
)),
562 sentMessage
.getStoryMessage().map(s
-> Story
.from(s
, fileProvider
)));
566 public record Blocked(List
<RecipientAddress
> recipients
, List
<GroupId
> groupIds
) {
569 BlockedListMessage blockedListMessage
,
570 RecipientResolver recipientResolver
,
571 RecipientAddressResolver addressResolver
573 return new Blocked(blockedListMessage
.getAddresses()
575 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
576 .toList(), blockedListMessage
.getGroupIds().stream().map(GroupId
::unknownVersion
).toList());
580 public record Read(RecipientAddress sender
, long timestamp
) {
583 ReadMessage readMessage
,
584 RecipientResolver recipientResolver
,
585 RecipientAddressResolver addressResolver
587 return new Read(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
588 readMessage
.getTimestamp());
592 public record Viewed(RecipientAddress sender
, long timestamp
) {
595 ViewedMessage readMessage
,
596 RecipientResolver recipientResolver
,
597 RecipientAddressResolver addressResolver
599 return new Viewed(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
600 readMessage
.getTimestamp());
604 public record ViewOnceOpen(RecipientAddress sender
, long timestamp
) {
606 static ViewOnceOpen
from(
607 ViewOnceOpenMessage readMessage
,
608 RecipientResolver recipientResolver
,
609 RecipientAddressResolver addressResolver
611 return new ViewOnceOpen(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
612 readMessage
.getSender())), readMessage
.getTimestamp());
616 public record Contacts(boolean isComplete
) {
618 static Contacts
from(ContactsMessage contactsMessage
) {
619 return new Contacts(contactsMessage
.isComplete());
623 public record Groups() {
625 static Groups
from(SignalServiceAttachment groupsMessage
) {
630 public record MessageRequestResponse(Type type
, Optional
<GroupId
> groupId
, Optional
<RecipientAddress
> person
) {
632 static MessageRequestResponse
from(
633 MessageRequestResponseMessage messageRequestResponse
,
634 RecipientResolver recipientResolver
,
635 RecipientAddressResolver addressResolver
637 return new MessageRequestResponse(Type
.from(messageRequestResponse
.getType()),
638 messageRequestResponse
.getGroupId().map(GroupId
::unknownVersion
),
639 messageRequestResponse
.getPerson()
640 .map(p
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(p
))));
651 static Type
from(MessageRequestResponseMessage
.Type type
) {
652 return switch (type
) {
653 case UNKNOWN
-> UNKNOWN
;
654 case ACCEPT
-> ACCEPT
;
655 case DELETE
-> DELETE
;
657 case BLOCK_AND_DELETE
-> BLOCK_AND_DELETE
;
658 case UNBLOCK_AND_ACCEPT
-> UNBLOCK_AND_ACCEPT
;
666 Optional
<Integer
> destinationDeviceId
,
667 Optional
<GroupId
> groupId
,
668 Optional
<Long
> timestamp
,
669 Optional
<Offer
> offer
,
670 Optional
<Answer
> answer
,
671 Optional
<Hangup
> hangup
,
673 List
<IceUpdate
> iceUpdate
,
674 Optional
<Opaque
> opaque
677 public static Call
from(final SignalServiceCallMessage callMessage
) {
678 return new Call(callMessage
.getDestinationDeviceId(),
679 callMessage
.getGroupId().map(GroupId
::unknownVersion
),
680 callMessage
.getTimestamp(),
681 callMessage
.getOfferMessage().map(Offer
::from
),
682 callMessage
.getAnswerMessage().map(Answer
::from
),
683 callMessage
.getHangupMessage().map(Hangup
::from
),
684 callMessage
.getBusyMessage().map(Busy
::from
),
685 callMessage
.getIceUpdateMessages()
686 .map(m
-> m
.stream().map(IceUpdate
::from
).toList())
688 callMessage
.getOpaqueMessage().map(Opaque
::from
));
691 public record Offer(long id
, String sdp
, Type type
, byte[] opaque
) {
693 static Offer
from(OfferMessage offerMessage
) {
694 return new Offer(offerMessage
.getId(),
695 offerMessage
.getSdp(),
696 Type
.from(offerMessage
.getType()),
697 offerMessage
.getOpaque());
704 static Type
from(OfferMessage
.Type type
) {
705 return switch (type
) {
706 case AUDIO_CALL
-> AUDIO_CALL
;
707 case VIDEO_CALL
-> VIDEO_CALL
;
713 public record Answer(long id
, String sdp
, byte[] opaque
) {
715 static Answer
from(AnswerMessage answerMessage
) {
716 return new Answer(answerMessage
.getId(), answerMessage
.getSdp(), answerMessage
.getOpaque());
720 public record Busy(long id
) {
722 static Busy
from(BusyMessage busyMessage
) {
723 return new Busy(busyMessage
.getId());
727 public record Hangup(long id
, Type type
, int deviceId
, boolean isLegacy
) {
729 static Hangup
from(HangupMessage hangupMessage
) {
730 return new Hangup(hangupMessage
.getId(),
731 Type
.from(hangupMessage
.getType()),
732 hangupMessage
.getDeviceId(),
733 hangupMessage
.isLegacy());
743 static Type
from(HangupMessage
.Type type
) {
744 return switch (type
) {
745 case NORMAL
-> NORMAL
;
746 case ACCEPTED
-> ACCEPTED
;
747 case DECLINED
-> DECLINED
;
749 case NEED_PERMISSION
-> NEED_PERMISSION
;
755 public record IceUpdate(long id
, String sdp
, byte[] opaque
) {
757 static IceUpdate
from(IceUpdateMessage iceUpdateMessage
) {
758 return new IceUpdate(iceUpdateMessage
.getId(), iceUpdateMessage
.getSdp(), iceUpdateMessage
.getOpaque());
762 public record Opaque(byte[] opaque
, Urgency urgency
) {
764 static Opaque
from(OpaqueMessage opaqueMessage
) {
765 return new Opaque(opaqueMessage
.getOpaque(), Urgency
.from(opaqueMessage
.getUrgency()));
768 public enum Urgency
{
772 static Urgency
from(OpaqueMessage
.Urgency urgency
) {
773 return switch (urgency
) {
774 case DROPPABLE
-> DROPPABLE
;
775 case HANDLE_IMMEDIATELY
-> HANDLE_IMMEDIATELY
;
783 boolean allowsReplies
,
784 Optional
<GroupId
> groupId
,
785 Optional
<Data
.Attachment
> fileAttachment
,
786 Optional
<TextAttachment
> textAttachment
789 public static Story
from(
790 SignalServiceStoryMessage storyMessage
, final AttachmentFileProvider fileProvider
792 return new Story(storyMessage
.getAllowsReplies().orElse(false),
793 storyMessage
.getGroupContext().map(c
-> GroupUtils
.getGroupIdV2(c
.getMasterKey())),
794 storyMessage
.getFileAttachment().map(f
-> Data
.Attachment
.from(f
, fileProvider
)),
795 storyMessage
.getTextAttachment().map(t
-> TextAttachment
.from(t
, fileProvider
)));
798 public record TextAttachment(
799 Optional
<String
> text
,
800 Optional
<Style
> style
,
801 Optional
<Color
> textForegroundColor
,
802 Optional
<Color
> textBackgroundColor
,
803 Optional
<Data
.Preview
> preview
,
804 Optional
<Gradient
> backgroundGradient
,
805 Optional
<Color
> backgroundColor
808 static TextAttachment
from(
809 SignalServiceTextAttachment textAttachment
, final AttachmentFileProvider fileProvider
811 return new TextAttachment(textAttachment
.getText(),
812 textAttachment
.getStyle().map(Style
::from
),
813 textAttachment
.getTextForegroundColor().map(Color
::new),
814 textAttachment
.getTextBackgroundColor().map(Color
::new),
815 textAttachment
.getPreview().map(p
-> Data
.Preview
.from(p
, fileProvider
)),
816 textAttachment
.getBackgroundGradient().map(Gradient
::from
),
817 textAttachment
.getBackgroundColor().map(Color
::new));
828 static Style
from(SignalServiceTextAttachment
.Style style
) {
829 return switch (style
) {
830 case DEFAULT
-> DEFAULT
;
831 case REGULAR
-> REGULAR
;
834 case SCRIPT
-> SCRIPT
;
835 case CONDENSED
-> CONDENSED
;
840 public record Gradient(Optional
<Color
> startColor
, Optional
<Color
> endColor
, Optional
<Integer
> angle
) {
842 static Gradient
from(SignalServiceTextAttachment
.Gradient gradient
) {
843 return new Gradient(gradient
.getStartColor().map(Color
::new),
844 gradient
.getEndColor().map(Color
::new),
845 gradient
.getAngle());
851 public static MessageEnvelope
from(
852 SignalServiceEnvelope envelope
,
853 SignalServiceContent content
,
854 RecipientResolver recipientResolver
,
855 RecipientAddressResolver addressResolver
,
856 final AttachmentFileProvider fileProvider
,
859 final var source
= !envelope
.isUnidentifiedSender() && envelope
.hasSourceUuid()
860 ? recipientResolver
.resolveRecipient(envelope
.getSourceAddress())
861 : envelope
.isUnidentifiedSender() && content
!= null
862 ? recipientResolver
.resolveRecipient(content
.getSender())
863 : exception
instanceof ProtocolException e
864 ? recipientResolver
.resolveRecipient(e
.getSender())
866 final var sourceDevice
= envelope
.hasSourceDevice()
867 ? envelope
.getSourceDevice()
869 ? content
.getSenderDevice()
870 : exception
instanceof ProtocolException e ? e
.getSenderDevice() : 0;
872 Optional
<Receipt
> receipt
;
873 Optional
<Typing
> typing
;
877 Optional
<Story
> story
;
878 if (content
!= null) {
879 receipt
= content
.getReceiptMessage().map(Receipt
::from
);
880 typing
= content
.getTypingMessage().map(Typing
::from
);
881 data
= content
.getDataMessage()
882 .map(dataMessage
-> Data
.from(dataMessage
, recipientResolver
, addressResolver
, fileProvider
));
883 sync
= content
.getSyncMessage().map(s
-> Sync
.from(s
, recipientResolver
, addressResolver
, fileProvider
));
884 call
= content
.getCallMessage().map(Call
::from
);
885 story
= content
.getStoryMessage().map(s
-> Story
.from(s
, fileProvider
));
887 receipt
= envelope
.isReceipt() ? Optional
.of(new Receipt(envelope
.getServerReceivedTimestamp(),
888 Receipt
.Type
.DELIVERY
,
889 List
.of(envelope
.getTimestamp()))) : Optional
.empty();
890 typing
= Optional
.empty();
891 data
= Optional
.empty();
892 sync
= Optional
.empty();
893 call
= Optional
.empty();
894 story
= Optional
.empty();
897 return new MessageEnvelope(source
== null
899 : Optional
.of(addressResolver
.resolveRecipientAddress(source
)),
901 envelope
.getTimestamp(),
902 envelope
.getServerReceivedTimestamp(),
903 envelope
.getServerDeliveredTimestamp(),
904 envelope
.isUnidentifiedSender(),
913 public interface AttachmentFileProvider
{
915 File
getFile(SignalServiceAttachmentRemoteId attachmentRemoteId
);