1 package org
.asamk
.signal
.manager
.api
;
3 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
4 import org
.asamk
.signal
.manager
.helper
.RecipientAddressResolver
;
5 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientResolver
;
6 import org
.signal
.libsignal
.metadata
.ProtocolException
;
7 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
8 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
9 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
10 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
11 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEditMessage
;
12 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceEnvelope
;
13 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroup
;
14 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceGroupContext
;
15 import org
.whispersystems
.signalservice
.api
.messages
.SignalServicePreview
;
16 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceReceiptMessage
;
17 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceStoryMessage
;
18 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTextAttachment
;
19 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
20 import org
.whispersystems
.signalservice
.api
.messages
.calls
.AnswerMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.calls
.BusyMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.calls
.HangupMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.calls
.IceUpdateMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OfferMessage
;
25 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OpaqueMessage
;
26 import org
.whispersystems
.signalservice
.api
.messages
.calls
.SignalServiceCallMessage
;
27 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
28 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
29 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.MessageRequestResponseMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ReadMessage
;
31 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
32 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
33 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewOnceOpenMessage
;
34 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewedMessage
;
35 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
38 import java
.io
.IOException
;
39 import java
.util
.List
;
40 import java
.util
.Optional
;
42 import java
.util
.stream
.Collectors
;
44 public record MessageEnvelope(
45 Optional
<RecipientAddress
> sourceAddress
,
48 long serverReceivedTimestamp
,
49 long serverDeliveredTimestamp
,
50 boolean isUnidentifiedSender
,
51 Optional
<Receipt
> receipt
,
52 Optional
<Typing
> typing
,
60 public record Receipt(long when, Type type
, List
<Long
> timestamps
) {
62 static Receipt
from(final SignalServiceReceiptMessage receiptMessage
) {
63 return new Receipt(receiptMessage
.getWhen(),
64 Type
.from(receiptMessage
.getType()),
65 receiptMessage
.getTimestamps());
74 static Type
from(SignalServiceReceiptMessage
.Type type
) {
75 return switch (type
) {
76 case DELIVERY
-> DELIVERY
;
78 case VIEWED
-> VIEWED
;
79 case UNKNOWN
-> UNKNOWN
;
85 public record Typing(long timestamp
, Type type
, Optional
<GroupId
> groupId
) {
87 public static Typing
from(final SignalServiceTypingMessage typingMessage
) {
88 return new Typing(typingMessage
.getTimestamp(),
89 typingMessage
.isTypingStarted() ? Type
.STARTED
: Type
.STOPPED
,
90 typingMessage
.getGroupId().map(GroupId
::unknownVersion
));
101 Optional
<GroupContext
> groupContext
,
102 Optional
<StoryContext
> storyContext
,
103 Optional
<GroupCallUpdate
> groupCallUpdate
,
104 Optional
<String
> body
,
105 int expiresInSeconds
,
106 boolean isExpirationUpdate
,
108 boolean isEndSession
,
109 boolean isProfileKeyUpdate
,
110 boolean hasProfileKey
,
111 Optional
<Reaction
> reaction
,
112 Optional
<Quote
> quote
,
113 Optional
<Payment
> payment
,
114 List
<Attachment
> attachments
,
115 Optional
<Long
> remoteDeleteId
,
116 Optional
<Sticker
> sticker
,
117 List
<SharedContact
> sharedContacts
,
118 List
<Mention
> mentions
,
119 List
<Preview
> previews
,
120 List
<TextStyle
> textStyles
124 final SignalServiceDataMessage dataMessage
,
125 RecipientResolver recipientResolver
,
126 RecipientAddressResolver addressResolver
,
127 final AttachmentFileProvider fileProvider
129 return new Data(dataMessage
.getTimestamp(),
130 dataMessage
.getGroupContext().map(GroupContext
::from
),
131 dataMessage
.getStoryContext()
132 .map((SignalServiceDataMessage
.StoryContext storyContext
) -> StoryContext
.from(storyContext
,
135 dataMessage
.getGroupCallUpdate().map(GroupCallUpdate
::from
),
136 dataMessage
.getBody(),
137 dataMessage
.getExpiresInSeconds(),
138 dataMessage
.isExpirationUpdate(),
139 dataMessage
.isViewOnce(),
140 dataMessage
.isEndSession(),
141 dataMessage
.isProfileKeyUpdate(),
142 dataMessage
.getProfileKey().isPresent(),
143 dataMessage
.getReaction().map(r
-> Reaction
.from(r
, recipientResolver
, addressResolver
)),
144 dataMessage
.getQuote()
145 .filter(q
-> q
.getAuthor() != null && q
.getAuthor().isValid())
146 .map(q
-> Quote
.from(q
, recipientResolver
, addressResolver
, fileProvider
)),
147 dataMessage
.getPayment().map(p
-> p
.getPaymentNotification().isPresent() ? Payment
.from(p
) : null),
148 dataMessage
.getAttachments()
149 .map(a
-> a
.stream().map(as -> Attachment
.from(as, fileProvider
)).toList())
151 dataMessage
.getRemoteDelete().map(SignalServiceDataMessage
.RemoteDelete
::getTargetSentTimestamp
),
152 dataMessage
.getSticker().map(Sticker
::from
),
153 dataMessage
.getSharedContacts()
155 .map(sharedContact
-> SharedContact
.from(sharedContact
, fileProvider
))
158 dataMessage
.getMentions()
159 .map(a
-> a
.stream().map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
)).toList())
161 dataMessage
.getPreviews()
162 .map(a
-> a
.stream().map(preview
-> Preview
.from(preview
, fileProvider
)).toList())
164 dataMessage
.getBodyRanges()
165 .map(a
-> a
.stream().filter(r
-> r
.style
!= null).map(TextStyle
::from
).toList())
169 public record GroupContext(GroupId groupId
, boolean isGroupUpdate
, int revision
) {
171 static GroupContext
from(SignalServiceGroupContext groupContext
) {
172 if (groupContext
.getGroupV1().isPresent()) {
173 return new GroupContext(GroupId
.v1(groupContext
.getGroupV1().get().getGroupId()),
174 groupContext
.getGroupV1Type() == SignalServiceGroup
.Type
.UPDATE
,
176 } else if (groupContext
.getGroupV2().isPresent()) {
177 final var groupV2
= groupContext
.getGroupV2().get();
178 return new GroupContext(GroupUtils
.getGroupIdV2(groupV2
.getMasterKey()),
179 groupV2
.hasSignedGroupChange(),
180 groupV2
.getRevision());
182 throw new RuntimeException("Invalid group context state");
187 public record StoryContext(RecipientAddress author
, long sentTimestamp
) {
189 static StoryContext
from(
190 SignalServiceDataMessage
.StoryContext storyContext
,
191 RecipientResolver recipientResolver
,
192 RecipientAddressResolver addressResolver
194 return new StoryContext(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
195 storyContext
.getAuthorServiceId())).toApiRecipientAddress(), storyContext
.getSentTimestamp());
199 public record GroupCallUpdate(String eraId
) {
201 static GroupCallUpdate
from(SignalServiceDataMessage
.GroupCallUpdate groupCallUpdate
) {
202 return new GroupCallUpdate(groupCallUpdate
.getEraId());
206 public record Reaction(
207 long targetSentTimestamp
, RecipientAddress targetAuthor
, String emoji
, boolean isRemove
210 static Reaction
from(
211 SignalServiceDataMessage
.Reaction reaction
,
212 RecipientResolver recipientResolver
,
213 RecipientAddressResolver addressResolver
215 return new Reaction(reaction
.getTargetSentTimestamp(),
216 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(reaction
.getTargetAuthor()))
217 .toApiRecipientAddress(),
219 reaction
.isRemove());
225 RecipientAddress author
,
226 Optional
<String
> text
,
227 List
<Mention
> mentions
,
228 List
<Attachment
> attachments
,
229 List
<TextStyle
> textStyles
233 SignalServiceDataMessage
.Quote quote
,
234 RecipientResolver recipientResolver
,
235 RecipientAddressResolver addressResolver
,
236 final AttachmentFileProvider fileProvider
238 return new Quote(quote
.getId(),
239 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(quote
.getAuthor()))
240 .toApiRecipientAddress(),
241 Optional
.of(quote
.getText()),
242 quote
.getMentions() == null
244 : quote
.getMentions()
246 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
248 quote
.getAttachments() == null
250 : quote
.getAttachments().stream().map(a
-> Attachment
.from(a
, fileProvider
)).toList(),
251 quote
.getBodyRanges() == null
253 : quote
.getBodyRanges()
255 .filter(r
-> r
.style
!= null)
256 .map(TextStyle
::from
)
261 public record Payment(String note
, byte[] receipt
) {
263 static Payment
from(SignalServiceDataMessage
.Payment payment
) {
264 return new Payment(payment
.getPaymentNotification().get().getNote(),
265 payment
.getPaymentNotification().get().getReceipt());
269 public record Mention(RecipientAddress recipient
, int start
, int length
) {
272 SignalServiceDataMessage
.Mention mention
,
273 RecipientResolver recipientResolver
,
274 RecipientAddressResolver addressResolver
276 return new Mention(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(mention
.getServiceId()))
277 .toApiRecipientAddress(), mention
.getStart(), mention
.getLength());
281 public record Attachment(
284 Optional
<String
> fileName
,
286 Optional
<Long
> uploadTimestamp
,
288 Optional
<byte[]> preview
,
289 Optional
<Attachment
> thumbnail
,
290 Optional
<String
> caption
,
291 Optional
<Integer
> width
,
292 Optional
<Integer
> height
,
298 static Attachment
from(SignalServiceAttachment signalAttachment
, AttachmentFileProvider fileProvider
) {
299 if (signalAttachment
.isPointer()) {
300 final var a
= signalAttachment
.asPointer();
301 final var attachmentFile
= fileProvider
.getFile(a
);
302 return new Attachment(Optional
.of(attachmentFile
.getName()),
303 Optional
.of(attachmentFile
),
306 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
307 a
.getSize().map(Integer
::longValue
),
310 a
.getCaption().map(c
-> c
.isEmpty() ?
null : c
),
311 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
312 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
317 Attachment attachment
= null;
318 try (final var a
= signalAttachment
.asStream()) {
319 attachment
= new Attachment(Optional
.empty(),
323 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
324 Optional
.of(a
.getLength()),
328 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
329 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
334 } catch (IOException e
) {
340 static Attachment
from(
341 SignalServiceDataMessage
.Quote
.QuotedAttachment a
,
342 final AttachmentFileProvider fileProvider
344 return new Attachment(Optional
.empty(),
346 Optional
.ofNullable(a
.getFileName()),
351 a
.getThumbnail() == null
353 : Optional
.of(Attachment
.from(a
.getThumbnail(), fileProvider
)),
363 public record Sticker(StickerPackId packId
, byte[] packKey
, int stickerId
) {
365 static Sticker
from(SignalServiceDataMessage
.Sticker sticker
) {
366 return new Sticker(StickerPackId
.deserialize(sticker
.getPackId()),
367 sticker
.getPackKey(),
368 sticker
.getStickerId());
372 public record SharedContact(
374 Optional
<Avatar
> avatar
,
377 List
<Address
> address
,
378 Optional
<String
> organization
381 static SharedContact
from(
382 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact sharedContact
,
383 final AttachmentFileProvider fileProvider
385 return new SharedContact(Name
.from(sharedContact
.getName()),
386 sharedContact
.getAvatar().map(avatar1
-> Avatar
.from(avatar1
, fileProvider
)),
387 sharedContact
.getPhone().map(p
-> p
.stream().map(Phone
::from
).toList()).orElse(List
.of()),
388 sharedContact
.getEmail().map(p
-> p
.stream().map(Email
::from
).toList()).orElse(List
.of()),
389 sharedContact
.getAddress().map(p
-> p
.stream().map(Address
::from
).toList()).orElse(List
.of()),
390 sharedContact
.getOrganization());
394 Optional
<String
> nickname
,
395 Optional
<String
> given
,
396 Optional
<String
> family
,
397 Optional
<String
> prefix
,
398 Optional
<String
> suffix
,
399 Optional
<String
> middle
402 static Name
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Name name
) {
403 return new Name(name
.getNickname(),
412 public record Avatar(Attachment attachment
, boolean isProfile
) {
415 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Avatar avatar
,
416 final AttachmentFileProvider fileProvider
418 return new Avatar(Attachment
.from(avatar
.getAttachment(), fileProvider
), avatar
.isProfile());
423 String value
, Type type
, Optional
<String
> label
426 static Phone
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone phone
) {
427 return new Phone(phone
.getValue(), Type
.from(phone
.getType()), phone
.getLabel());
436 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone
.Type type
) {
437 return switch (type
) {
440 case MOBILE
-> MOBILE
;
441 case CUSTOM
-> CUSTOM
;
448 String value
, Type type
, Optional
<String
> label
451 static Email
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email email
) {
452 return new Email(email
.getValue(), Type
.from(email
.getType()), email
.getLabel());
461 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email
.Type type
) {
462 return switch (type
) {
465 case MOBILE
-> MOBILE
;
466 case CUSTOM
-> CUSTOM
;
472 public record Address(
474 Optional
<String
> label
,
475 Optional
<String
> street
,
476 Optional
<String
> pobox
,
477 Optional
<String
> neighborhood
,
478 Optional
<String
> city
,
479 Optional
<String
> region
,
480 Optional
<String
> postcode
,
481 Optional
<String
> country
484 static Address
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress address
) {
485 return new Address(Type
.from(address
.getType()),
489 address
.getNeighborhood(),
492 address
.getPostcode(),
493 address
.getCountry());
501 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress
.Type type
) {
502 return switch (type
) {
505 case CUSTOM
-> CUSTOM
;
512 public record Preview(String title
, String description
, long date
, String url
, Optional
<Attachment
> image
) {
514 static Preview
from(SignalServicePreview preview
, final AttachmentFileProvider fileProvider
) {
515 return new Preview(preview
.getTitle(),
516 preview
.getDescription(),
519 preview
.getImage().map(as -> Attachment
.from(as, fileProvider
)));
525 public record Edit(long targetSentTimestamp
, Data dataMessage
) {
527 public static Edit
from(
528 final SignalServiceEditMessage editMessage
,
529 RecipientResolver recipientResolver
,
530 RecipientAddressResolver addressResolver
,
531 final AttachmentFileProvider fileProvider
533 return new Edit(editMessage
.getTargetSentTimestamp(),
534 Data
.from(editMessage
.getDataMessage(), recipientResolver
, addressResolver
, fileProvider
));
540 Optional
<Blocked
> blocked
,
543 Optional
<ViewOnceOpen
> viewOnceOpen
,
544 Optional
<Contacts
> contacts
,
545 Optional
<Groups
> groups
,
546 Optional
<MessageRequestResponse
> messageRequestResponse
549 public static Sync
from(
550 final SignalServiceSyncMessage syncMessage
,
551 RecipientResolver recipientResolver
,
552 RecipientAddressResolver addressResolver
,
553 final AttachmentFileProvider fileProvider
555 return new Sync(syncMessage
.getSent()
556 .map(s
-> Sent
.from(s
, recipientResolver
, addressResolver
, fileProvider
)),
557 syncMessage
.getBlockedList().map(b
-> Blocked
.from(b
, recipientResolver
, addressResolver
)),
558 syncMessage
.getRead()
559 .map(r
-> r
.stream().map(rm
-> Read
.from(rm
, recipientResolver
, addressResolver
)).toList())
561 syncMessage
.getViewed()
563 .map(rm
-> Viewed
.from(rm
, recipientResolver
, addressResolver
))
566 syncMessage
.getViewOnceOpen().map(rm
-> ViewOnceOpen
.from(rm
, recipientResolver
, addressResolver
)),
567 syncMessage
.getContacts().map(Contacts
::from
),
568 syncMessage
.getGroups().map(Groups
::from
),
569 syncMessage
.getMessageRequestResponse()
570 .map(m
-> MessageRequestResponse
.from(m
, recipientResolver
, addressResolver
)));
575 long expirationStartTimestamp
,
576 Optional
<RecipientAddress
> destination
,
577 Set
<RecipientAddress
> recipients
,
578 Optional
<Data
> message
,
579 Optional
<Edit
> editMessage
,
580 Optional
<Story
> story
584 SentTranscriptMessage sentMessage
,
585 RecipientResolver recipientResolver
,
586 RecipientAddressResolver addressResolver
,
587 final AttachmentFileProvider fileProvider
589 return new Sent(sentMessage
.getTimestamp(),
590 sentMessage
.getExpirationStartTimestamp(),
591 sentMessage
.getDestination()
592 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
))
593 .toApiRecipientAddress()),
594 sentMessage
.getRecipients()
596 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
))
597 .toApiRecipientAddress())
598 .collect(Collectors
.toSet()),
599 sentMessage
.getDataMessage()
600 .map(message
-> Data
.from(message
, recipientResolver
, addressResolver
, fileProvider
)),
601 sentMessage
.getEditMessage()
602 .map(message
-> Edit
.from(message
, recipientResolver
, addressResolver
, fileProvider
)),
603 sentMessage
.getStoryMessage().map(s
-> Story
.from(s
, fileProvider
)));
607 public record Blocked(List
<RecipientAddress
> recipients
, List
<GroupId
> groupIds
) {
610 BlockedListMessage blockedListMessage
,
611 RecipientResolver recipientResolver
,
612 RecipientAddressResolver addressResolver
614 return new Blocked(blockedListMessage
.getAddresses()
616 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
))
617 .toApiRecipientAddress())
618 .toList(), blockedListMessage
.getGroupIds().stream().map(GroupId
::unknownVersion
).toList());
622 public record Read(RecipientAddress sender
, long timestamp
) {
625 ReadMessage readMessage
,
626 RecipientResolver recipientResolver
,
627 RecipientAddressResolver addressResolver
629 return new Read(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender()))
630 .toApiRecipientAddress(), readMessage
.getTimestamp());
634 public record Viewed(RecipientAddress sender
, long timestamp
) {
637 ViewedMessage readMessage
,
638 RecipientResolver recipientResolver
,
639 RecipientAddressResolver addressResolver
641 return new Viewed(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender()))
642 .toApiRecipientAddress(), readMessage
.getTimestamp());
646 public record ViewOnceOpen(RecipientAddress sender
, long timestamp
) {
648 static ViewOnceOpen
from(
649 ViewOnceOpenMessage readMessage
,
650 RecipientResolver recipientResolver
,
651 RecipientAddressResolver addressResolver
653 return new ViewOnceOpen(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
654 readMessage
.getSender())).toApiRecipientAddress(), readMessage
.getTimestamp());
658 public record Contacts(boolean isComplete
) {
660 static Contacts
from(ContactsMessage contactsMessage
) {
661 return new Contacts(contactsMessage
.isComplete());
665 public record Groups() {
667 static Groups
from(SignalServiceAttachment groupsMessage
) {
672 public record MessageRequestResponse(Type type
, Optional
<GroupId
> groupId
, Optional
<RecipientAddress
> person
) {
674 static MessageRequestResponse
from(
675 MessageRequestResponseMessage messageRequestResponse
,
676 RecipientResolver recipientResolver
,
677 RecipientAddressResolver addressResolver
679 return new MessageRequestResponse(Type
.from(messageRequestResponse
.getType()),
680 messageRequestResponse
.getGroupId().map(GroupId
::unknownVersion
),
681 messageRequestResponse
.getPerson()
682 .map(p
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(p
))
683 .toApiRecipientAddress()));
696 static Type
from(MessageRequestResponseMessage
.Type type
) {
697 return switch (type
) {
698 case UNKNOWN
-> UNKNOWN
;
699 case ACCEPT
-> ACCEPT
;
700 case DELETE
-> DELETE
;
702 case BLOCK_AND_DELETE
-> BLOCK_AND_DELETE
;
703 case UNBLOCK_AND_ACCEPT
-> UNBLOCK_AND_ACCEPT
;
705 case BLOCK_AND_SPAM
-> BLOCK_AND_SPAM
;
713 Optional
<Integer
> destinationDeviceId
,
714 Optional
<GroupId
> groupId
,
715 Optional
<Long
> timestamp
,
716 Optional
<Offer
> offer
,
717 Optional
<Answer
> answer
,
718 Optional
<Hangup
> hangup
,
720 List
<IceUpdate
> iceUpdate
,
721 Optional
<Opaque
> opaque
,
725 public static Call
from(final SignalServiceCallMessage callMessage
) {
726 return new Call(callMessage
.getDestinationDeviceId(),
727 callMessage
.getGroupId().map(GroupId
::unknownVersion
),
728 callMessage
.getTimestamp(),
729 callMessage
.getOfferMessage().map(Offer
::from
),
730 callMessage
.getAnswerMessage().map(Answer
::from
),
731 callMessage
.getHangupMessage().map(Hangup
::from
),
732 callMessage
.getBusyMessage().map(Busy
::from
),
733 callMessage
.getIceUpdateMessages()
734 .map(m
-> m
.stream().map(IceUpdate
::from
).toList())
736 callMessage
.getOpaqueMessage().map(Opaque
::from
),
737 callMessage
.isUrgent());
740 public record Offer(long id
, Type type
, byte[] opaque
) {
742 static Offer
from(OfferMessage offerMessage
) {
743 return new Offer(offerMessage
.getId(), Type
.from(offerMessage
.getType()), offerMessage
.getOpaque());
750 static Type
from(OfferMessage
.Type type
) {
751 return switch (type
) {
752 case AUDIO_CALL
-> AUDIO_CALL
;
753 case VIDEO_CALL
-> VIDEO_CALL
;
759 public record Answer(long id
, byte[] opaque
) {
761 static Answer
from(AnswerMessage answerMessage
) {
762 return new Answer(answerMessage
.getId(), answerMessage
.getOpaque());
766 public record Busy(long id
) {
768 static Busy
from(BusyMessage busyMessage
) {
769 return new Busy(busyMessage
.getId());
773 public record Hangup(long id
, Type type
, int deviceId
) {
775 static Hangup
from(HangupMessage hangupMessage
) {
776 return new Hangup(hangupMessage
.getId(),
777 Type
.from(hangupMessage
.getType()),
778 hangupMessage
.getDeviceId());
788 static Type
from(HangupMessage
.Type type
) {
789 return switch (type
) {
790 case NORMAL
-> NORMAL
;
791 case ACCEPTED
-> ACCEPTED
;
792 case DECLINED
-> DECLINED
;
794 case NEED_PERMISSION
-> NEED_PERMISSION
;
800 public record IceUpdate(long id
, byte[] opaque
) {
802 static IceUpdate
from(IceUpdateMessage iceUpdateMessage
) {
803 return new IceUpdate(iceUpdateMessage
.getId(), iceUpdateMessage
.getOpaque());
807 public record Opaque(byte[] opaque
, Urgency urgency
) {
809 static Opaque
from(OpaqueMessage opaqueMessage
) {
810 return new Opaque(opaqueMessage
.getOpaque(), Urgency
.from(opaqueMessage
.getUrgency()));
813 public enum Urgency
{
817 static Urgency
from(OpaqueMessage
.Urgency urgency
) {
818 return switch (urgency
) {
819 case DROPPABLE
-> DROPPABLE
;
820 case HANDLE_IMMEDIATELY
-> HANDLE_IMMEDIATELY
;
828 boolean allowsReplies
,
829 Optional
<GroupId
> groupId
,
830 Optional
<Data
.Attachment
> fileAttachment
,
831 Optional
<TextAttachment
> textAttachment
834 public static Story
from(SignalServiceStoryMessage storyMessage
, final AttachmentFileProvider fileProvider
) {
835 return new Story(storyMessage
.getAllowsReplies().orElse(false),
836 storyMessage
.getGroupContext().map(c
-> GroupUtils
.getGroupIdV2(c
.getMasterKey())),
837 storyMessage
.getFileAttachment().map(f
-> Data
.Attachment
.from(f
, fileProvider
)),
838 storyMessage
.getTextAttachment().map(t
-> TextAttachment
.from(t
, fileProvider
)));
841 public record TextAttachment(
842 Optional
<String
> text
,
843 Optional
<Style
> style
,
844 Optional
<Color
> textForegroundColor
,
845 Optional
<Color
> textBackgroundColor
,
846 Optional
<Data
.Preview
> preview
,
847 Optional
<Gradient
> backgroundGradient
,
848 Optional
<Color
> backgroundColor
851 static TextAttachment
from(
852 SignalServiceTextAttachment textAttachment
,
853 final AttachmentFileProvider fileProvider
855 return new TextAttachment(textAttachment
.getText(),
856 textAttachment
.getStyle().map(Style
::from
),
857 textAttachment
.getTextForegroundColor().map(Color
::new),
858 textAttachment
.getTextBackgroundColor().map(Color
::new),
859 textAttachment
.getPreview().map(p
-> Data
.Preview
.from(p
, fileProvider
)),
860 textAttachment
.getBackgroundGradient().map(Gradient
::from
),
861 textAttachment
.getBackgroundColor().map(Color
::new));
872 static Style
from(SignalServiceTextAttachment
.Style style
) {
873 return switch (style
) {
874 case DEFAULT
-> DEFAULT
;
875 case REGULAR
-> REGULAR
;
878 case SCRIPT
-> SCRIPT
;
879 case CONDENSED
-> CONDENSED
;
884 public record Gradient(
885 List
<Color
> colors
, List
<Float
> positions
, Optional
<Integer
> angle
888 static Gradient
from(SignalServiceTextAttachment
.Gradient gradient
) {
889 return new Gradient(gradient
.getColors().stream().map(Color
::new).toList(),
890 gradient
.getPositions(),
891 gradient
.getAngle());
897 public static MessageEnvelope
from(
898 SignalServiceEnvelope envelope
,
899 SignalServiceContent content
,
900 RecipientResolver recipientResolver
,
901 RecipientAddressResolver addressResolver
,
902 final AttachmentFileProvider fileProvider
,
905 final var serviceId
= envelope
.getSourceServiceId().map(ServiceId
::parseOrNull
).orElse(null);
906 final var source
= !envelope
.isUnidentifiedSender() && serviceId
!= null
907 ? recipientResolver
.resolveRecipient(serviceId
)
908 : envelope
.isUnidentifiedSender() && content
!= null
909 ? recipientResolver
.resolveRecipient(content
.getSender())
910 : exception
instanceof ProtocolException e
911 ? recipientResolver
.resolveRecipient(e
.getSender())
913 final var sourceDevice
= envelope
.hasSourceDevice()
914 ? envelope
.getSourceDevice()
916 ? content
.getSenderDevice()
917 : exception
instanceof ProtocolException e ? e
.getSenderDevice() : 0;
919 Optional
<Receipt
> receipt
;
920 Optional
<Typing
> typing
;
925 Optional
<Story
> story
;
926 if (content
!= null) {
927 receipt
= content
.getReceiptMessage().map(Receipt
::from
);
928 typing
= content
.getTypingMessage().map(Typing
::from
);
929 data
= content
.getDataMessage()
930 .map(dataMessage
-> Data
.from(dataMessage
, recipientResolver
, addressResolver
, fileProvider
));
931 edit
= content
.getEditMessage().map(s
-> Edit
.from(s
, recipientResolver
, addressResolver
, fileProvider
));
932 sync
= content
.getSyncMessage().map(s
-> Sync
.from(s
, recipientResolver
, addressResolver
, fileProvider
));
933 call
= content
.getCallMessage().map(Call
::from
);
934 story
= content
.getStoryMessage().map(s
-> Story
.from(s
, fileProvider
));
936 receipt
= envelope
.isReceipt() ? Optional
.of(new Receipt(envelope
.getServerReceivedTimestamp(),
937 Receipt
.Type
.DELIVERY
,
938 List
.of(envelope
.getTimestamp()))) : Optional
.empty();
939 typing
= Optional
.empty();
940 data
= Optional
.empty();
941 edit
= Optional
.empty();
942 sync
= Optional
.empty();
943 call
= Optional
.empty();
944 story
= Optional
.empty();
947 return new MessageEnvelope(source
== null
949 : Optional
.of(addressResolver
.resolveRecipientAddress(source
).toApiRecipientAddress()),
951 envelope
.getTimestamp(),
952 envelope
.getServerReceivedTimestamp(),
953 envelope
.getServerDeliveredTimestamp(),
954 envelope
.isUnidentifiedSender(),
964 public interface AttachmentFileProvider
{
966 File
getFile(SignalServiceAttachmentPointer pointer
);