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
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
9 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentRemoteId
;
10 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceContent
;
11 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceDataMessage
;
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
.SignalServiceReceiptMessage
;
16 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
17 import org
.whispersystems
.signalservice
.api
.messages
.calls
.AnswerMessage
;
18 import org
.whispersystems
.signalservice
.api
.messages
.calls
.BusyMessage
;
19 import org
.whispersystems
.signalservice
.api
.messages
.calls
.HangupMessage
;
20 import org
.whispersystems
.signalservice
.api
.messages
.calls
.IceUpdateMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OfferMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OpaqueMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.calls
.SignalServiceCallMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
25 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
26 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.MessageRequestResponseMessage
;
27 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ReadMessage
;
28 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
29 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewOnceOpenMessage
;
31 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewedMessage
;
34 import java
.util
.List
;
35 import java
.util
.Optional
;
37 import java
.util
.stream
.Collectors
;
39 public record MessageEnvelope(
40 Optional
<RecipientAddress
> sourceAddress
,
43 long serverReceivedTimestamp
,
44 long serverDeliveredTimestamp
,
45 boolean isUnidentifiedSender
,
46 Optional
<Receipt
> receipt
,
47 Optional
<Typing
> typing
,
53 public record Receipt(long when, Type type
, List
<Long
> timestamps
) {
55 static Receipt
from(final SignalServiceReceiptMessage receiptMessage
) {
56 return new Receipt(receiptMessage
.getWhen(),
57 Type
.from(receiptMessage
.getType()),
58 receiptMessage
.getTimestamps());
67 static Type
from(SignalServiceReceiptMessage
.Type type
) {
68 return switch (type
) {
69 case DELIVERY
-> DELIVERY
;
71 case VIEWED
-> VIEWED
;
72 case UNKNOWN
-> UNKNOWN
;
78 public record Typing(long timestamp
, Type type
, Optional
<GroupId
> groupId
) {
80 public static Typing
from(final SignalServiceTypingMessage typingMessage
) {
81 return new Typing(typingMessage
.getTimestamp(),
82 typingMessage
.isTypingStarted() ? Type
.STARTED
: Type
.STOPPED
,
83 Optional
.ofNullable(typingMessage
.getGroupId().transform(GroupId
::unknownVersion
).orNull()));
94 Optional
<GroupContext
> groupContext
,
95 Optional
<GroupCallUpdate
> groupCallUpdate
,
96 Optional
<String
> body
,
98 boolean isExpirationUpdate
,
100 boolean isEndSession
,
101 boolean hasProfileKey
,
102 Optional
<Reaction
> reaction
,
103 Optional
<Quote
> quote
,
104 Optional
<Payment
> payment
,
105 List
<Attachment
> attachments
,
106 Optional
<Long
> remoteDeleteId
,
107 Optional
<Sticker
> sticker
,
108 List
<SharedContact
> sharedContacts
,
109 List
<Mention
> mentions
,
110 List
<Preview
> previews
114 final SignalServiceDataMessage dataMessage
,
115 RecipientResolver recipientResolver
,
116 RecipientAddressResolver addressResolver
,
117 final AttachmentFileProvider fileProvider
119 return new Data(dataMessage
.getTimestamp(),
120 Optional
.ofNullable(dataMessage
.getGroupContext().transform(GroupContext
::from
).orNull()),
121 Optional
.ofNullable(dataMessage
.getGroupCallUpdate().transform(GroupCallUpdate
::from
).orNull()),
122 Optional
.ofNullable(dataMessage
.getBody().orNull()),
123 dataMessage
.getExpiresInSeconds(),
124 dataMessage
.isExpirationUpdate(),
125 dataMessage
.isViewOnce(),
126 dataMessage
.isEndSession(),
127 dataMessage
.getProfileKey().isPresent(),
128 Optional
.ofNullable(dataMessage
.getReaction()
129 .transform(r
-> Reaction
.from(r
, recipientResolver
, addressResolver
))
131 Optional
.ofNullable(dataMessage
.getQuote()
132 .transform(q
-> Quote
.from(q
, recipientResolver
, addressResolver
, fileProvider
))
134 Optional
.ofNullable(dataMessage
.getPayment()
135 .transform(p
-> p
.getPaymentNotification().isPresent() ? Payment
.from(p
) : null)
137 dataMessage
.getAttachments()
138 .transform(a
-> a
.stream()
139 .map(as -> Attachment
.from(as, fileProvider
))
140 .collect(Collectors
.toList()))
142 Optional
.ofNullable(dataMessage
.getRemoteDelete()
143 .transform(SignalServiceDataMessage
.RemoteDelete
::getTargetSentTimestamp
)
145 Optional
.ofNullable(dataMessage
.getSticker().transform(Sticker
::from
).orNull()),
146 dataMessage
.getSharedContacts()
147 .transform(a
-> a
.stream()
148 .map(sharedContact
-> SharedContact
.from(sharedContact
, fileProvider
))
149 .collect(Collectors
.toList()))
151 dataMessage
.getMentions()
152 .transform(a
-> a
.stream()
153 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
154 .collect(Collectors
.toList()))
156 dataMessage
.getPreviews()
157 .transform(a
-> a
.stream()
158 .map(preview
-> Preview
.from(preview
, fileProvider
))
159 .collect(Collectors
.toList()))
163 public record GroupContext(GroupId groupId
, boolean isGroupUpdate
, int revision
) {
165 static GroupContext
from(SignalServiceGroupContext groupContext
) {
166 if (groupContext
.getGroupV1().isPresent()) {
167 return new GroupContext(GroupId
.v1(groupContext
.getGroupV1().get().getGroupId()),
168 groupContext
.getGroupV1Type() == SignalServiceGroup
.Type
.UPDATE
,
170 } else if (groupContext
.getGroupV2().isPresent()) {
171 final var groupV2
= groupContext
.getGroupV2().get();
172 return new GroupContext(GroupUtils
.getGroupIdV2(groupV2
.getMasterKey()),
173 groupV2
.hasSignedGroupChange(),
174 groupV2
.getRevision());
176 throw new RuntimeException("Invalid group context state");
181 public record GroupCallUpdate(String eraId
) {
183 static GroupCallUpdate
from(SignalServiceDataMessage
.GroupCallUpdate groupCallUpdate
) {
184 return new GroupCallUpdate(groupCallUpdate
.getEraId());
188 public record Reaction(
189 long targetSentTimestamp
, RecipientAddress targetAuthor
, String emoji
, boolean isRemove
192 static Reaction
from(
193 SignalServiceDataMessage
.Reaction reaction
,
194 RecipientResolver recipientResolver
,
195 RecipientAddressResolver addressResolver
197 return new Reaction(reaction
.getTargetSentTimestamp(),
198 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(reaction
.getTargetAuthor())),
200 reaction
.isRemove());
206 RecipientAddress author
,
207 Optional
<String
> text
,
208 List
<Mention
> mentions
,
209 List
<Attachment
> attachments
213 SignalServiceDataMessage
.Quote quote
,
214 RecipientResolver recipientResolver
,
215 RecipientAddressResolver addressResolver
,
216 final AttachmentFileProvider fileProvider
218 return new Quote(quote
.getId(),
219 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(quote
.getAuthor())),
220 Optional
.ofNullable(quote
.getText()),
221 quote
.getMentions() == null
223 : quote
.getMentions()
225 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
226 .collect(Collectors
.toList()),
227 quote
.getAttachments() == null
229 : quote
.getAttachments()
231 .map(a
-> Attachment
.from(a
, fileProvider
))
232 .collect(Collectors
.toList()));
236 public record Payment(String note
, byte[] receipt
) {
237 static Payment
from(SignalServiceDataMessage
.Payment payment
) {
238 return new Payment(payment
.getPaymentNotification().get().getNote(), payment
.getPaymentNotification().get().getReceipt());
242 public record Mention(RecipientAddress recipient
, int start
, int length
) {
245 SignalServiceDataMessage
.Mention mention
,
246 RecipientResolver recipientResolver
,
247 RecipientAddressResolver addressResolver
249 return new Mention(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(mention
.getAci())),
251 mention
.getLength());
255 public record Attachment(
258 Optional
<String
> fileName
,
260 Optional
<Long
> uploadTimestamp
,
262 Optional
<byte[]> preview
,
263 Optional
<Attachment
> thumbnail
,
264 Optional
<String
> caption
,
265 Optional
<Integer
> width
,
266 Optional
<Integer
> height
,
272 static Attachment
from(SignalServiceAttachment attachment
, AttachmentFileProvider fileProvider
) {
273 if (attachment
.isPointer()) {
274 final var a
= attachment
.asPointer();
275 return new Attachment(Optional
.of(a
.getRemoteId().toString()),
276 Optional
.of(fileProvider
.getFile(a
.getRemoteId())),
277 Optional
.ofNullable(a
.getFileName().orNull()),
279 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
280 Optional
.ofNullable(a
.getSize().transform(Integer
::longValue
).orNull()),
281 Optional
.ofNullable(a
.getPreview().orNull()),
283 Optional
.ofNullable(a
.getCaption().orNull()),
284 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
285 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
290 final var a
= attachment
.asStream();
291 return new Attachment(Optional
.empty(),
293 Optional
.ofNullable(a
.getFileName().orNull()),
295 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
296 Optional
.of(a
.getLength()),
297 Optional
.ofNullable(a
.getPreview().orNull()),
299 Optional
.ofNullable(a
.getCaption().orNull()),
300 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
301 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
308 static Attachment
from(
309 SignalServiceDataMessage
.Quote
.QuotedAttachment a
, final AttachmentFileProvider fileProvider
311 return new Attachment(Optional
.empty(),
313 Optional
.ofNullable(a
.getFileName()),
318 a
.getThumbnail() == null
320 : Optional
.of(Attachment
.from(a
.getThumbnail(), fileProvider
)),
330 public record Sticker(byte[] packId
, byte[] packKey
, int stickerId
) {
332 static Sticker
from(SignalServiceDataMessage
.Sticker sticker
) {
333 return new Sticker(sticker
.getPackId(), sticker
.getPackKey(), sticker
.getStickerId());
337 public record SharedContact(
339 Optional
<Avatar
> avatar
,
342 List
<Address
> address
,
343 Optional
<String
> organization
346 static SharedContact
from(
347 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact sharedContact
,
348 final AttachmentFileProvider fileProvider
350 return new SharedContact(Name
.from(sharedContact
.getName()),
351 Optional
.ofNullable(sharedContact
.getAvatar()
352 .transform(avatar1
-> Avatar
.from(avatar1
, fileProvider
))
354 sharedContact
.getPhone()
355 .transform(p
-> p
.stream().map(Phone
::from
).collect(Collectors
.toList()))
357 sharedContact
.getEmail()
358 .transform(p
-> p
.stream().map(Email
::from
).collect(Collectors
.toList()))
360 sharedContact
.getAddress()
361 .transform(p
-> p
.stream().map(Address
::from
).collect(Collectors
.toList()))
363 Optional
.ofNullable(sharedContact
.getOrganization().orNull()));
367 Optional
<String
> display
,
368 Optional
<String
> given
,
369 Optional
<String
> family
,
370 Optional
<String
> prefix
,
371 Optional
<String
> suffix
,
372 Optional
<String
> middle
375 static Name
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Name name
) {
376 return new Name(Optional
.ofNullable(name
.getDisplay().orNull()),
377 Optional
.ofNullable(name
.getGiven().orNull()),
378 Optional
.ofNullable(name
.getFamily().orNull()),
379 Optional
.ofNullable(name
.getPrefix().orNull()),
380 Optional
.ofNullable(name
.getSuffix().orNull()),
381 Optional
.ofNullable(name
.getMiddle().orNull()));
385 public record Avatar(Attachment attachment
, boolean isProfile
) {
388 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Avatar avatar
,
389 final AttachmentFileProvider fileProvider
391 return new Avatar(Attachment
.from(avatar
.getAttachment(), fileProvider
), avatar
.isProfile());
396 String value
, Type type
, Optional
<String
> label
399 static Phone
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone phone
) {
400 return new Phone(phone
.getValue(),
401 Type
.from(phone
.getType()),
402 Optional
.ofNullable(phone
.getLabel().orNull()));
411 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone
.Type type
) {
412 return switch (type
) {
415 case MOBILE
-> MOBILE
;
416 case CUSTOM
-> CUSTOM
;
423 String value
, Type type
, Optional
<String
> label
426 static Email
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email email
) {
427 return new Email(email
.getValue(),
428 Type
.from(email
.getType()),
429 Optional
.ofNullable(email
.getLabel().orNull()));
438 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email
.Type type
) {
439 return switch (type
) {
442 case MOBILE
-> MOBILE
;
443 case CUSTOM
-> CUSTOM
;
449 public record Address(
451 Optional
<String
> label
,
452 Optional
<String
> street
,
453 Optional
<String
> pobox
,
454 Optional
<String
> neighborhood
,
455 Optional
<String
> city
,
456 Optional
<String
> region
,
457 Optional
<String
> postcode
,
458 Optional
<String
> country
461 static Address
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress address
) {
462 return new Address(Address
.Type
.from(address
.getType()),
463 Optional
.ofNullable(address
.getLabel().orNull()),
464 Optional
.ofNullable(address
.getLabel().orNull()),
465 Optional
.ofNullable(address
.getLabel().orNull()),
466 Optional
.ofNullable(address
.getLabel().orNull()),
467 Optional
.ofNullable(address
.getLabel().orNull()),
468 Optional
.ofNullable(address
.getLabel().orNull()),
469 Optional
.ofNullable(address
.getLabel().orNull()),
470 Optional
.ofNullable(address
.getLabel().orNull()));
478 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress
.Type type
) {
479 return switch (type
) {
482 case CUSTOM
-> CUSTOM
;
489 public record Preview(String title
, String description
, long date
, String url
, Optional
<Attachment
> image
) {
492 SignalServiceDataMessage
.Preview preview
, final AttachmentFileProvider fileProvider
494 return new Preview(preview
.getTitle(),
495 preview
.getDescription(),
498 Optional
.ofNullable(preview
.getImage()
499 .transform(as -> Attachment
.from(as, fileProvider
))
507 Optional
<Blocked
> blocked
,
510 Optional
<ViewOnceOpen
> viewOnceOpen
,
511 Optional
<Contacts
> contacts
,
512 Optional
<Groups
> groups
,
513 Optional
<MessageRequestResponse
> messageRequestResponse
516 public static Sync
from(
517 final SignalServiceSyncMessage syncMessage
,
518 RecipientResolver recipientResolver
,
519 RecipientAddressResolver addressResolver
,
520 final AttachmentFileProvider fileProvider
522 return new Sync(Optional
.ofNullable(syncMessage
.getSent()
523 .transform(s
-> Sent
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
525 Optional
.ofNullable(syncMessage
.getBlockedList()
526 .transform(b
-> Blocked
.from(b
, recipientResolver
, addressResolver
))
528 syncMessage
.getRead()
529 .transform(r
-> r
.stream()
530 .map(rm
-> Read
.from(rm
, recipientResolver
, addressResolver
))
531 .collect(Collectors
.toList()))
533 syncMessage
.getViewed()
534 .transform(r
-> r
.stream()
535 .map(rm
-> Viewed
.from(rm
, recipientResolver
, addressResolver
))
536 .collect(Collectors
.toList()))
538 Optional
.ofNullable(syncMessage
.getViewOnceOpen()
539 .transform(rm
-> ViewOnceOpen
.from(rm
, recipientResolver
, addressResolver
))
541 Optional
.ofNullable(syncMessage
.getContacts().transform(Contacts
::from
).orNull()),
542 Optional
.ofNullable(syncMessage
.getGroups().transform(Groups
::from
).orNull()),
543 Optional
.ofNullable(syncMessage
.getMessageRequestResponse()
544 .transform(m
-> MessageRequestResponse
.from(m
, recipientResolver
, addressResolver
))
550 long expirationStartTimestamp
,
551 Optional
<RecipientAddress
> destination
,
552 Set
<RecipientAddress
> recipients
,
557 SentTranscriptMessage sentMessage
,
558 RecipientResolver recipientResolver
,
559 RecipientAddressResolver addressResolver
,
560 final AttachmentFileProvider fileProvider
562 return new Sent(sentMessage
.getTimestamp(),
563 sentMessage
.getExpirationStartTimestamp(),
564 Optional
.ofNullable(sentMessage
.getDestination()
565 .transform(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
568 sentMessage
.getRecipients()
570 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
571 .collect(Collectors
.toSet()),
572 Data
.from(sentMessage
.getMessage(), recipientResolver
, addressResolver
, fileProvider
));
576 public record Blocked(List
<RecipientAddress
> recipients
, List
<GroupId
> groupIds
) {
579 BlockedListMessage blockedListMessage
,
580 RecipientResolver recipientResolver
,
581 RecipientAddressResolver addressResolver
583 return new Blocked(blockedListMessage
.getAddresses()
585 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
586 .collect(Collectors
.toList()),
587 blockedListMessage
.getGroupIds()
589 .map(GroupId
::unknownVersion
)
590 .collect(Collectors
.toList()));
594 public record Read(RecipientAddress sender
, long timestamp
) {
597 ReadMessage readMessage
,
598 RecipientResolver recipientResolver
,
599 RecipientAddressResolver addressResolver
601 return new Read(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
602 readMessage
.getTimestamp());
606 public record Viewed(RecipientAddress sender
, long timestamp
) {
609 ViewedMessage readMessage
,
610 RecipientResolver recipientResolver
,
611 RecipientAddressResolver addressResolver
613 return new Viewed(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
614 readMessage
.getTimestamp());
618 public record ViewOnceOpen(RecipientAddress sender
, long timestamp
) {
620 static ViewOnceOpen
from(
621 ViewOnceOpenMessage readMessage
,
622 RecipientResolver recipientResolver
,
623 RecipientAddressResolver addressResolver
625 return new ViewOnceOpen(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
626 readMessage
.getSender())), readMessage
.getTimestamp());
630 public record Contacts(boolean isComplete
) {
632 static Contacts
from(ContactsMessage contactsMessage
) {
633 return new Contacts(contactsMessage
.isComplete());
637 public record Groups() {
639 static Groups
from(SignalServiceAttachment groupsMessage
) {
644 public record MessageRequestResponse(Type type
, Optional
<GroupId
> groupId
, Optional
<RecipientAddress
> person
) {
646 static MessageRequestResponse
from(
647 MessageRequestResponseMessage messageRequestResponse
,
648 RecipientResolver recipientResolver
,
649 RecipientAddressResolver addressResolver
651 return new MessageRequestResponse(Type
.from(messageRequestResponse
.getType()),
652 Optional
.ofNullable(messageRequestResponse
.getGroupId()
653 .transform(GroupId
::unknownVersion
)
655 Optional
.ofNullable(messageRequestResponse
.getPerson()
656 .transform(p
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
669 static Type
from(MessageRequestResponseMessage
.Type type
) {
670 return switch (type
) {
671 case UNKNOWN
-> UNKNOWN
;
672 case ACCEPT
-> ACCEPT
;
673 case DELETE
-> DELETE
;
675 case BLOCK_AND_DELETE
-> BLOCK_AND_DELETE
;
676 case UNBLOCK_AND_ACCEPT
-> UNBLOCK_AND_ACCEPT
;
684 Optional
<Integer
> destinationDeviceId
,
685 Optional
<GroupId
> groupId
,
686 Optional
<Long
> timestamp
,
687 Optional
<Offer
> offer
,
688 Optional
<Answer
> answer
,
689 Optional
<Hangup
> hangup
,
691 List
<IceUpdate
> iceUpdate
,
692 Optional
<Opaque
> opaque
695 public static Call
from(final SignalServiceCallMessage callMessage
) {
696 return new Call(Optional
.ofNullable(callMessage
.getDestinationDeviceId().orNull()),
697 Optional
.ofNullable(callMessage
.getGroupId().transform(GroupId
::unknownVersion
).orNull()),
698 Optional
.ofNullable(callMessage
.getTimestamp().orNull()),
699 Optional
.ofNullable(callMessage
.getOfferMessage().transform(Offer
::from
).orNull()),
700 Optional
.ofNullable(callMessage
.getAnswerMessage().transform(Answer
::from
).orNull()),
701 Optional
.ofNullable(callMessage
.getHangupMessage().transform(Hangup
::from
).orNull()),
702 Optional
.ofNullable(callMessage
.getBusyMessage().transform(Busy
::from
).orNull()),
703 callMessage
.getIceUpdateMessages()
704 .transform(m
-> m
.stream().map(IceUpdate
::from
).collect(Collectors
.toList()))
706 Optional
.ofNullable(callMessage
.getOpaqueMessage().transform(Opaque
::from
).orNull()));
709 public record Offer(long id
, String sdp
, Type type
, byte[] opaque
) {
711 static Offer
from(OfferMessage offerMessage
) {
712 return new Offer(offerMessage
.getId(),
713 offerMessage
.getSdp(),
714 Type
.from(offerMessage
.getType()),
715 offerMessage
.getOpaque());
722 static Type
from(OfferMessage
.Type type
) {
723 return switch (type
) {
724 case AUDIO_CALL
-> AUDIO_CALL
;
725 case VIDEO_CALL
-> VIDEO_CALL
;
731 public record Answer(long id
, String sdp
, byte[] opaque
) {
733 static Answer
from(AnswerMessage answerMessage
) {
734 return new Answer(answerMessage
.getId(), answerMessage
.getSdp(), answerMessage
.getOpaque());
738 public record Busy(long id
) {
740 static Busy
from(BusyMessage busyMessage
) {
741 return new Busy(busyMessage
.getId());
745 public record Hangup(long id
, Type type
, int deviceId
, boolean isLegacy
) {
747 static Hangup
from(HangupMessage hangupMessage
) {
748 return new Hangup(hangupMessage
.getId(),
749 Type
.from(hangupMessage
.getType()),
750 hangupMessage
.getDeviceId(),
751 hangupMessage
.isLegacy());
761 static Type
from(HangupMessage
.Type type
) {
762 return switch (type
) {
763 case NORMAL
-> NORMAL
;
764 case ACCEPTED
-> ACCEPTED
;
765 case DECLINED
-> DECLINED
;
767 case NEED_PERMISSION
-> NEED_PERMISSION
;
773 public record IceUpdate(long id
, String sdp
, byte[] opaque
) {
775 static IceUpdate
from(IceUpdateMessage iceUpdateMessage
) {
776 return new IceUpdate(iceUpdateMessage
.getId(), iceUpdateMessage
.getSdp(), iceUpdateMessage
.getOpaque());
780 public record Opaque(byte[] opaque
, Urgency urgency
) {
782 static Opaque
from(OpaqueMessage opaqueMessage
) {
783 return new Opaque(opaqueMessage
.getOpaque(), Urgency
.from(opaqueMessage
.getUrgency()));
786 public enum Urgency
{
790 static Urgency
from(OpaqueMessage
.Urgency urgency
) {
791 return switch (urgency
) {
792 case DROPPABLE
-> DROPPABLE
;
793 case HANDLE_IMMEDIATELY
-> HANDLE_IMMEDIATELY
;
800 public static MessageEnvelope
from(
801 SignalServiceEnvelope envelope
,
802 SignalServiceContent content
,
803 RecipientResolver recipientResolver
,
804 RecipientAddressResolver addressResolver
,
805 final AttachmentFileProvider fileProvider
807 final var source
= !envelope
.isUnidentifiedSender() && envelope
.hasSourceUuid()
808 ? recipientResolver
.resolveRecipient(envelope
.getSourceAddress())
809 : envelope
.isUnidentifiedSender() && content
!= null
810 ? recipientResolver
.resolveRecipient(content
.getSender())
812 final var sourceDevice
= envelope
.hasSourceDevice()
813 ? envelope
.getSourceDevice()
814 : content
!= null ? content
.getSenderDevice() : 0;
816 Optional
<Receipt
> receipt
;
817 Optional
<Typing
> typing
;
821 if (content
!= null) {
822 receipt
= Optional
.ofNullable(content
.getReceiptMessage().transform(Receipt
::from
).orNull());
823 typing
= Optional
.ofNullable(content
.getTypingMessage().transform(Typing
::from
).orNull());
824 data
= Optional
.ofNullable(content
.getDataMessage()
825 .transform(dataMessage
-> Data
.from(dataMessage
, recipientResolver
, addressResolver
, fileProvider
))
827 sync
= Optional
.ofNullable(content
.getSyncMessage()
828 .transform(s
-> Sync
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
830 call
= Optional
.ofNullable(content
.getCallMessage().transform(Call
::from
).orNull());
832 receipt
= Optional
.empty();
833 typing
= Optional
.empty();
834 data
= Optional
.empty();
835 sync
= Optional
.empty();
836 call
= Optional
.empty();
839 return new MessageEnvelope(source
== null
841 : Optional
.of(addressResolver
.resolveRecipientAddress(source
)),
843 envelope
.getTimestamp(),
844 envelope
.getServerReceivedTimestamp(),
845 envelope
.getServerDeliveredTimestamp(),
846 envelope
.isUnidentifiedSender(),
854 public interface AttachmentFileProvider
{
856 File
getFile(SignalServiceAttachmentRemoteId attachmentRemoteId
);