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().map(as -> Attachment
.from(as, fileProvider
)).toList())
140 Optional
.ofNullable(dataMessage
.getRemoteDelete()
141 .transform(SignalServiceDataMessage
.RemoteDelete
::getTargetSentTimestamp
)
143 Optional
.ofNullable(dataMessage
.getSticker().transform(Sticker
::from
).orNull()),
144 dataMessage
.getSharedContacts()
145 .transform(a
-> a
.stream()
146 .map(sharedContact
-> SharedContact
.from(sharedContact
, fileProvider
))
149 dataMessage
.getMentions()
150 .transform(a
-> a
.stream()
151 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
154 dataMessage
.getPreviews()
155 .transform(a
-> a
.stream().map(preview
-> Preview
.from(preview
, fileProvider
)).toList())
159 public record GroupContext(GroupId groupId
, boolean isGroupUpdate
, int revision
) {
161 static GroupContext
from(SignalServiceGroupContext groupContext
) {
162 if (groupContext
.getGroupV1().isPresent()) {
163 return new GroupContext(GroupId
.v1(groupContext
.getGroupV1().get().getGroupId()),
164 groupContext
.getGroupV1Type() == SignalServiceGroup
.Type
.UPDATE
,
166 } else if (groupContext
.getGroupV2().isPresent()) {
167 final var groupV2
= groupContext
.getGroupV2().get();
168 return new GroupContext(GroupUtils
.getGroupIdV2(groupV2
.getMasterKey()),
169 groupV2
.hasSignedGroupChange(),
170 groupV2
.getRevision());
172 throw new RuntimeException("Invalid group context state");
177 public record GroupCallUpdate(String eraId
) {
179 static GroupCallUpdate
from(SignalServiceDataMessage
.GroupCallUpdate groupCallUpdate
) {
180 return new GroupCallUpdate(groupCallUpdate
.getEraId());
184 public record Reaction(
185 long targetSentTimestamp
, RecipientAddress targetAuthor
, String emoji
, boolean isRemove
188 static Reaction
from(
189 SignalServiceDataMessage
.Reaction reaction
,
190 RecipientResolver recipientResolver
,
191 RecipientAddressResolver addressResolver
193 return new Reaction(reaction
.getTargetSentTimestamp(),
194 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(reaction
.getTargetAuthor())),
196 reaction
.isRemove());
202 RecipientAddress author
,
203 Optional
<String
> text
,
204 List
<Mention
> mentions
,
205 List
<Attachment
> attachments
209 SignalServiceDataMessage
.Quote quote
,
210 RecipientResolver recipientResolver
,
211 RecipientAddressResolver addressResolver
,
212 final AttachmentFileProvider fileProvider
214 return new Quote(quote
.getId(),
215 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(quote
.getAuthor())),
216 Optional
.ofNullable(quote
.getText()),
217 quote
.getMentions() == null
219 : quote
.getMentions()
221 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
223 quote
.getAttachments() == null
225 : quote
.getAttachments().stream().map(a
-> Attachment
.from(a
, fileProvider
)).toList());
229 public record Payment(String note
, byte[] receipt
) {
231 static Payment
from(SignalServiceDataMessage
.Payment payment
) {
232 return new Payment(payment
.getPaymentNotification().get().getNote(),
233 payment
.getPaymentNotification().get().getReceipt());
237 public record Mention(RecipientAddress recipient
, int start
, int length
) {
240 SignalServiceDataMessage
.Mention mention
,
241 RecipientResolver recipientResolver
,
242 RecipientAddressResolver addressResolver
244 return new Mention(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(mention
.getAci())),
246 mention
.getLength());
250 public record Attachment(
253 Optional
<String
> fileName
,
255 Optional
<Long
> uploadTimestamp
,
257 Optional
<byte[]> preview
,
258 Optional
<Attachment
> thumbnail
,
259 Optional
<String
> caption
,
260 Optional
<Integer
> width
,
261 Optional
<Integer
> height
,
267 static Attachment
from(SignalServiceAttachment attachment
, AttachmentFileProvider fileProvider
) {
268 if (attachment
.isPointer()) {
269 final var a
= attachment
.asPointer();
270 return new Attachment(Optional
.of(a
.getRemoteId().toString()),
271 Optional
.of(fileProvider
.getFile(a
.getRemoteId())),
272 Optional
.ofNullable(a
.getFileName().orNull()),
274 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
275 Optional
.ofNullable(a
.getSize().transform(Integer
::longValue
).orNull()),
276 Optional
.ofNullable(a
.getPreview().orNull()),
278 Optional
.ofNullable(a
.getCaption().orNull()),
279 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
280 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
285 final var a
= attachment
.asStream();
286 return new Attachment(Optional
.empty(),
288 Optional
.ofNullable(a
.getFileName().orNull()),
290 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
291 Optional
.of(a
.getLength()),
292 Optional
.ofNullable(a
.getPreview().orNull()),
294 Optional
.ofNullable(a
.getCaption().orNull()),
295 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
296 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
303 static Attachment
from(
304 SignalServiceDataMessage
.Quote
.QuotedAttachment a
, final AttachmentFileProvider fileProvider
306 return new Attachment(Optional
.empty(),
308 Optional
.ofNullable(a
.getFileName()),
313 a
.getThumbnail() == null
315 : Optional
.of(Attachment
.from(a
.getThumbnail(), fileProvider
)),
325 public record Sticker(byte[] packId
, byte[] packKey
, int stickerId
) {
327 static Sticker
from(SignalServiceDataMessage
.Sticker sticker
) {
328 return new Sticker(sticker
.getPackId(), sticker
.getPackKey(), sticker
.getStickerId());
332 public record SharedContact(
334 Optional
<Avatar
> avatar
,
337 List
<Address
> address
,
338 Optional
<String
> organization
341 static SharedContact
from(
342 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact sharedContact
,
343 final AttachmentFileProvider fileProvider
345 return new SharedContact(Name
.from(sharedContact
.getName()),
346 Optional
.ofNullable(sharedContact
.getAvatar()
347 .transform(avatar1
-> Avatar
.from(avatar1
, fileProvider
))
349 sharedContact
.getPhone().transform(p
-> p
.stream().map(Phone
::from
).toList()).or(List
.of()),
350 sharedContact
.getEmail().transform(p
-> p
.stream().map(Email
::from
).toList()).or(List
.of()),
351 sharedContact
.getAddress().transform(p
-> p
.stream().map(Address
::from
).toList()).or(List
.of()),
352 Optional
.ofNullable(sharedContact
.getOrganization().orNull()));
356 Optional
<String
> display
,
357 Optional
<String
> given
,
358 Optional
<String
> family
,
359 Optional
<String
> prefix
,
360 Optional
<String
> suffix
,
361 Optional
<String
> middle
364 static Name
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Name name
) {
365 return new Name(Optional
.ofNullable(name
.getDisplay().orNull()),
366 Optional
.ofNullable(name
.getGiven().orNull()),
367 Optional
.ofNullable(name
.getFamily().orNull()),
368 Optional
.ofNullable(name
.getPrefix().orNull()),
369 Optional
.ofNullable(name
.getSuffix().orNull()),
370 Optional
.ofNullable(name
.getMiddle().orNull()));
374 public record Avatar(Attachment attachment
, boolean isProfile
) {
377 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Avatar avatar
,
378 final AttachmentFileProvider fileProvider
380 return new Avatar(Attachment
.from(avatar
.getAttachment(), fileProvider
), avatar
.isProfile());
385 String value
, Type type
, Optional
<String
> label
388 static Phone
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone phone
) {
389 return new Phone(phone
.getValue(),
390 Type
.from(phone
.getType()),
391 Optional
.ofNullable(phone
.getLabel().orNull()));
400 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone
.Type type
) {
401 return switch (type
) {
404 case MOBILE
-> MOBILE
;
405 case CUSTOM
-> CUSTOM
;
412 String value
, Type type
, Optional
<String
> label
415 static Email
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email email
) {
416 return new Email(email
.getValue(),
417 Type
.from(email
.getType()),
418 Optional
.ofNullable(email
.getLabel().orNull()));
427 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email
.Type type
) {
428 return switch (type
) {
431 case MOBILE
-> MOBILE
;
432 case CUSTOM
-> CUSTOM
;
438 public record Address(
440 Optional
<String
> label
,
441 Optional
<String
> street
,
442 Optional
<String
> pobox
,
443 Optional
<String
> neighborhood
,
444 Optional
<String
> city
,
445 Optional
<String
> region
,
446 Optional
<String
> postcode
,
447 Optional
<String
> country
450 static Address
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress address
) {
451 return new Address(Address
.Type
.from(address
.getType()),
452 Optional
.ofNullable(address
.getLabel().orNull()),
453 Optional
.ofNullable(address
.getLabel().orNull()),
454 Optional
.ofNullable(address
.getLabel().orNull()),
455 Optional
.ofNullable(address
.getLabel().orNull()),
456 Optional
.ofNullable(address
.getLabel().orNull()),
457 Optional
.ofNullable(address
.getLabel().orNull()),
458 Optional
.ofNullable(address
.getLabel().orNull()),
459 Optional
.ofNullable(address
.getLabel().orNull()));
467 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress
.Type type
) {
468 return switch (type
) {
471 case CUSTOM
-> CUSTOM
;
478 public record Preview(String title
, String description
, long date
, String url
, Optional
<Attachment
> image
) {
481 SignalServiceDataMessage
.Preview preview
, final AttachmentFileProvider fileProvider
483 return new Preview(preview
.getTitle(),
484 preview
.getDescription(),
487 Optional
.ofNullable(preview
.getImage()
488 .transform(as -> Attachment
.from(as, fileProvider
))
496 Optional
<Blocked
> blocked
,
499 Optional
<ViewOnceOpen
> viewOnceOpen
,
500 Optional
<Contacts
> contacts
,
501 Optional
<Groups
> groups
,
502 Optional
<MessageRequestResponse
> messageRequestResponse
505 public static Sync
from(
506 final SignalServiceSyncMessage syncMessage
,
507 RecipientResolver recipientResolver
,
508 RecipientAddressResolver addressResolver
,
509 final AttachmentFileProvider fileProvider
511 return new Sync(Optional
.ofNullable(syncMessage
.getSent()
512 .transform(s
-> Sent
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
514 Optional
.ofNullable(syncMessage
.getBlockedList()
515 .transform(b
-> Blocked
.from(b
, recipientResolver
, addressResolver
))
517 syncMessage
.getRead()
518 .transform(r
-> r
.stream()
519 .map(rm
-> Read
.from(rm
, recipientResolver
, addressResolver
))
522 syncMessage
.getViewed()
523 .transform(r
-> r
.stream()
524 .map(rm
-> Viewed
.from(rm
, recipientResolver
, addressResolver
))
527 Optional
.ofNullable(syncMessage
.getViewOnceOpen()
528 .transform(rm
-> ViewOnceOpen
.from(rm
, recipientResolver
, addressResolver
))
530 Optional
.ofNullable(syncMessage
.getContacts().transform(Contacts
::from
).orNull()),
531 Optional
.ofNullable(syncMessage
.getGroups().transform(Groups
::from
).orNull()),
532 Optional
.ofNullable(syncMessage
.getMessageRequestResponse()
533 .transform(m
-> MessageRequestResponse
.from(m
, recipientResolver
, addressResolver
))
539 long expirationStartTimestamp
,
540 Optional
<RecipientAddress
> destination
,
541 Set
<RecipientAddress
> recipients
,
546 SentTranscriptMessage sentMessage
,
547 RecipientResolver recipientResolver
,
548 RecipientAddressResolver addressResolver
,
549 final AttachmentFileProvider fileProvider
551 return new Sent(sentMessage
.getTimestamp(),
552 sentMessage
.getExpirationStartTimestamp(),
553 Optional
.ofNullable(sentMessage
.getDestination()
554 .transform(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
557 sentMessage
.getRecipients()
559 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
560 .collect(Collectors
.toSet()),
561 Data
.from(sentMessage
.getMessage(), recipientResolver
, addressResolver
, fileProvider
));
565 public record Blocked(List
<RecipientAddress
> recipients
, List
<GroupId
> groupIds
) {
568 BlockedListMessage blockedListMessage
,
569 RecipientResolver recipientResolver
,
570 RecipientAddressResolver addressResolver
572 return new Blocked(blockedListMessage
.getAddresses()
574 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
575 .toList(), blockedListMessage
.getGroupIds().stream().map(GroupId
::unknownVersion
).toList());
579 public record Read(RecipientAddress sender
, long timestamp
) {
582 ReadMessage readMessage
,
583 RecipientResolver recipientResolver
,
584 RecipientAddressResolver addressResolver
586 return new Read(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
587 readMessage
.getTimestamp());
591 public record Viewed(RecipientAddress sender
, long timestamp
) {
594 ViewedMessage readMessage
,
595 RecipientResolver recipientResolver
,
596 RecipientAddressResolver addressResolver
598 return new Viewed(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
599 readMessage
.getTimestamp());
603 public record ViewOnceOpen(RecipientAddress sender
, long timestamp
) {
605 static ViewOnceOpen
from(
606 ViewOnceOpenMessage readMessage
,
607 RecipientResolver recipientResolver
,
608 RecipientAddressResolver addressResolver
610 return new ViewOnceOpen(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
611 readMessage
.getSender())), readMessage
.getTimestamp());
615 public record Contacts(boolean isComplete
) {
617 static Contacts
from(ContactsMessage contactsMessage
) {
618 return new Contacts(contactsMessage
.isComplete());
622 public record Groups() {
624 static Groups
from(SignalServiceAttachment groupsMessage
) {
629 public record MessageRequestResponse(Type type
, Optional
<GroupId
> groupId
, Optional
<RecipientAddress
> person
) {
631 static MessageRequestResponse
from(
632 MessageRequestResponseMessage messageRequestResponse
,
633 RecipientResolver recipientResolver
,
634 RecipientAddressResolver addressResolver
636 return new MessageRequestResponse(Type
.from(messageRequestResponse
.getType()),
637 Optional
.ofNullable(messageRequestResponse
.getGroupId()
638 .transform(GroupId
::unknownVersion
)
640 Optional
.ofNullable(messageRequestResponse
.getPerson()
641 .transform(p
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
654 static Type
from(MessageRequestResponseMessage
.Type type
) {
655 return switch (type
) {
656 case UNKNOWN
-> UNKNOWN
;
657 case ACCEPT
-> ACCEPT
;
658 case DELETE
-> DELETE
;
660 case BLOCK_AND_DELETE
-> BLOCK_AND_DELETE
;
661 case UNBLOCK_AND_ACCEPT
-> UNBLOCK_AND_ACCEPT
;
669 Optional
<Integer
> destinationDeviceId
,
670 Optional
<GroupId
> groupId
,
671 Optional
<Long
> timestamp
,
672 Optional
<Offer
> offer
,
673 Optional
<Answer
> answer
,
674 Optional
<Hangup
> hangup
,
676 List
<IceUpdate
> iceUpdate
,
677 Optional
<Opaque
> opaque
680 public static Call
from(final SignalServiceCallMessage callMessage
) {
681 return new Call(Optional
.ofNullable(callMessage
.getDestinationDeviceId().orNull()),
682 Optional
.ofNullable(callMessage
.getGroupId().transform(GroupId
::unknownVersion
).orNull()),
683 Optional
.ofNullable(callMessage
.getTimestamp().orNull()),
684 Optional
.ofNullable(callMessage
.getOfferMessage().transform(Offer
::from
).orNull()),
685 Optional
.ofNullable(callMessage
.getAnswerMessage().transform(Answer
::from
).orNull()),
686 Optional
.ofNullable(callMessage
.getHangupMessage().transform(Hangup
::from
).orNull()),
687 Optional
.ofNullable(callMessage
.getBusyMessage().transform(Busy
::from
).orNull()),
688 callMessage
.getIceUpdateMessages()
689 .transform(m
-> m
.stream().map(IceUpdate
::from
).toList())
691 Optional
.ofNullable(callMessage
.getOpaqueMessage().transform(Opaque
::from
).orNull()));
694 public record Offer(long id
, String sdp
, Type type
, byte[] opaque
) {
696 static Offer
from(OfferMessage offerMessage
) {
697 return new Offer(offerMessage
.getId(),
698 offerMessage
.getSdp(),
699 Type
.from(offerMessage
.getType()),
700 offerMessage
.getOpaque());
707 static Type
from(OfferMessage
.Type type
) {
708 return switch (type
) {
709 case AUDIO_CALL
-> AUDIO_CALL
;
710 case VIDEO_CALL
-> VIDEO_CALL
;
716 public record Answer(long id
, String sdp
, byte[] opaque
) {
718 static Answer
from(AnswerMessage answerMessage
) {
719 return new Answer(answerMessage
.getId(), answerMessage
.getSdp(), answerMessage
.getOpaque());
723 public record Busy(long id
) {
725 static Busy
from(BusyMessage busyMessage
) {
726 return new Busy(busyMessage
.getId());
730 public record Hangup(long id
, Type type
, int deviceId
, boolean isLegacy
) {
732 static Hangup
from(HangupMessage hangupMessage
) {
733 return new Hangup(hangupMessage
.getId(),
734 Type
.from(hangupMessage
.getType()),
735 hangupMessage
.getDeviceId(),
736 hangupMessage
.isLegacy());
746 static Type
from(HangupMessage
.Type type
) {
747 return switch (type
) {
748 case NORMAL
-> NORMAL
;
749 case ACCEPTED
-> ACCEPTED
;
750 case DECLINED
-> DECLINED
;
752 case NEED_PERMISSION
-> NEED_PERMISSION
;
758 public record IceUpdate(long id
, String sdp
, byte[] opaque
) {
760 static IceUpdate
from(IceUpdateMessage iceUpdateMessage
) {
761 return new IceUpdate(iceUpdateMessage
.getId(), iceUpdateMessage
.getSdp(), iceUpdateMessage
.getOpaque());
765 public record Opaque(byte[] opaque
, Urgency urgency
) {
767 static Opaque
from(OpaqueMessage opaqueMessage
) {
768 return new Opaque(opaqueMessage
.getOpaque(), Urgency
.from(opaqueMessage
.getUrgency()));
771 public enum Urgency
{
775 static Urgency
from(OpaqueMessage
.Urgency urgency
) {
776 return switch (urgency
) {
777 case DROPPABLE
-> DROPPABLE
;
778 case HANDLE_IMMEDIATELY
-> HANDLE_IMMEDIATELY
;
785 public static MessageEnvelope
from(
786 SignalServiceEnvelope envelope
,
787 SignalServiceContent content
,
788 RecipientResolver recipientResolver
,
789 RecipientAddressResolver addressResolver
,
790 final AttachmentFileProvider fileProvider
792 final var source
= !envelope
.isUnidentifiedSender() && envelope
.hasSourceUuid()
793 ? recipientResolver
.resolveRecipient(envelope
.getSourceAddress())
794 : envelope
.isUnidentifiedSender() && content
!= null
795 ? recipientResolver
.resolveRecipient(content
.getSender())
797 final var sourceDevice
= envelope
.hasSourceDevice()
798 ? envelope
.getSourceDevice()
799 : content
!= null ? content
.getSenderDevice() : 0;
801 Optional
<Receipt
> receipt
;
802 Optional
<Typing
> typing
;
806 if (content
!= null) {
807 receipt
= Optional
.ofNullable(content
.getReceiptMessage().transform(Receipt
::from
).orNull());
808 typing
= Optional
.ofNullable(content
.getTypingMessage().transform(Typing
::from
).orNull());
809 data
= Optional
.ofNullable(content
.getDataMessage()
810 .transform(dataMessage
-> Data
.from(dataMessage
, recipientResolver
, addressResolver
, fileProvider
))
812 sync
= Optional
.ofNullable(content
.getSyncMessage()
813 .transform(s
-> Sync
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
815 call
= Optional
.ofNullable(content
.getCallMessage().transform(Call
::from
).orNull());
817 receipt
= Optional
.empty();
818 typing
= Optional
.empty();
819 data
= Optional
.empty();
820 sync
= Optional
.empty();
821 call
= Optional
.empty();
824 return new MessageEnvelope(source
== null
826 : Optional
.of(addressResolver
.resolveRecipientAddress(source
)),
828 envelope
.getTimestamp(),
829 envelope
.getServerReceivedTimestamp(),
830 envelope
.getServerDeliveredTimestamp(),
831 envelope
.isUnidentifiedSender(),
839 public interface AttachmentFileProvider
{
841 File
getFile(SignalServiceAttachmentRemoteId attachmentRemoteId
);