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
.SignalServiceReceiptMessage
;
17 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceTypingMessage
;
18 import org
.whispersystems
.signalservice
.api
.messages
.calls
.AnswerMessage
;
19 import org
.whispersystems
.signalservice
.api
.messages
.calls
.BusyMessage
;
20 import org
.whispersystems
.signalservice
.api
.messages
.calls
.HangupMessage
;
21 import org
.whispersystems
.signalservice
.api
.messages
.calls
.IceUpdateMessage
;
22 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OfferMessage
;
23 import org
.whispersystems
.signalservice
.api
.messages
.calls
.OpaqueMessage
;
24 import org
.whispersystems
.signalservice
.api
.messages
.calls
.SignalServiceCallMessage
;
25 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.BlockedListMessage
;
26 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ContactsMessage
;
27 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.MessageRequestResponseMessage
;
28 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ReadMessage
;
29 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SentTranscriptMessage
;
30 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.SignalServiceSyncMessage
;
31 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewOnceOpenMessage
;
32 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.ViewedMessage
;
35 import java
.util
.List
;
36 import java
.util
.Optional
;
38 import java
.util
.stream
.Collectors
;
40 public record MessageEnvelope(
41 Optional
<RecipientAddress
> sourceAddress
,
44 long serverReceivedTimestamp
,
45 long serverDeliveredTimestamp
,
46 boolean isUnidentifiedSender
,
47 Optional
<Receipt
> receipt
,
48 Optional
<Typing
> typing
,
54 public record Receipt(long when, Type type
, List
<Long
> timestamps
) {
56 static Receipt
from(final SignalServiceReceiptMessage receiptMessage
) {
57 return new Receipt(receiptMessage
.getWhen(),
58 Type
.from(receiptMessage
.getType()),
59 receiptMessage
.getTimestamps());
68 static Type
from(SignalServiceReceiptMessage
.Type type
) {
69 return switch (type
) {
70 case DELIVERY
-> DELIVERY
;
72 case VIEWED
-> VIEWED
;
73 case UNKNOWN
-> UNKNOWN
;
79 public record Typing(long timestamp
, Type type
, Optional
<GroupId
> groupId
) {
81 public static Typing
from(final SignalServiceTypingMessage typingMessage
) {
82 return new Typing(typingMessage
.getTimestamp(),
83 typingMessage
.isTypingStarted() ? Type
.STARTED
: Type
.STOPPED
,
84 Optional
.ofNullable(typingMessage
.getGroupId().transform(GroupId
::unknownVersion
).orNull()));
95 Optional
<GroupContext
> groupContext
,
96 Optional
<GroupCallUpdate
> groupCallUpdate
,
97 Optional
<String
> body
,
99 boolean isExpirationUpdate
,
101 boolean isEndSession
,
102 boolean hasProfileKey
,
103 Optional
<Reaction
> reaction
,
104 Optional
<Quote
> quote
,
105 Optional
<Payment
> payment
,
106 List
<Attachment
> attachments
,
107 Optional
<Long
> remoteDeleteId
,
108 Optional
<Sticker
> sticker
,
109 List
<SharedContact
> sharedContacts
,
110 List
<Mention
> mentions
,
111 List
<Preview
> previews
115 final SignalServiceDataMessage dataMessage
,
116 RecipientResolver recipientResolver
,
117 RecipientAddressResolver addressResolver
,
118 final AttachmentFileProvider fileProvider
120 return new Data(dataMessage
.getTimestamp(),
121 Optional
.ofNullable(dataMessage
.getGroupContext().transform(GroupContext
::from
).orNull()),
122 Optional
.ofNullable(dataMessage
.getGroupCallUpdate().transform(GroupCallUpdate
::from
).orNull()),
123 Optional
.ofNullable(dataMessage
.getBody().orNull()),
124 dataMessage
.getExpiresInSeconds(),
125 dataMessage
.isExpirationUpdate(),
126 dataMessage
.isViewOnce(),
127 dataMessage
.isEndSession(),
128 dataMessage
.getProfileKey().isPresent(),
129 Optional
.ofNullable(dataMessage
.getReaction()
130 .transform(r
-> Reaction
.from(r
, recipientResolver
, addressResolver
))
132 Optional
.ofNullable(dataMessage
.getQuote()
133 .transform(q
-> Quote
.from(q
, recipientResolver
, addressResolver
, fileProvider
))
135 Optional
.ofNullable(dataMessage
.getPayment()
136 .transform(p
-> p
.getPaymentNotification().isPresent() ? Payment
.from(p
) : null)
138 dataMessage
.getAttachments()
139 .transform(a
-> a
.stream().map(as -> Attachment
.from(as, fileProvider
)).toList())
141 Optional
.ofNullable(dataMessage
.getRemoteDelete()
142 .transform(SignalServiceDataMessage
.RemoteDelete
::getTargetSentTimestamp
)
144 Optional
.ofNullable(dataMessage
.getSticker().transform(Sticker
::from
).orNull()),
145 dataMessage
.getSharedContacts()
146 .transform(a
-> a
.stream()
147 .map(sharedContact
-> SharedContact
.from(sharedContact
, fileProvider
))
150 dataMessage
.getMentions()
151 .transform(a
-> a
.stream()
152 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
155 dataMessage
.getPreviews()
156 .transform(a
-> a
.stream().map(preview
-> Preview
.from(preview
, fileProvider
)).toList())
160 public record GroupContext(GroupId groupId
, boolean isGroupUpdate
, int revision
) {
162 static GroupContext
from(SignalServiceGroupContext groupContext
) {
163 if (groupContext
.getGroupV1().isPresent()) {
164 return new GroupContext(GroupId
.v1(groupContext
.getGroupV1().get().getGroupId()),
165 groupContext
.getGroupV1Type() == SignalServiceGroup
.Type
.UPDATE
,
167 } else if (groupContext
.getGroupV2().isPresent()) {
168 final var groupV2
= groupContext
.getGroupV2().get();
169 return new GroupContext(GroupUtils
.getGroupIdV2(groupV2
.getMasterKey()),
170 groupV2
.hasSignedGroupChange(),
171 groupV2
.getRevision());
173 throw new RuntimeException("Invalid group context state");
178 public record GroupCallUpdate(String eraId
) {
180 static GroupCallUpdate
from(SignalServiceDataMessage
.GroupCallUpdate groupCallUpdate
) {
181 return new GroupCallUpdate(groupCallUpdate
.getEraId());
185 public record Reaction(
186 long targetSentTimestamp
, RecipientAddress targetAuthor
, String emoji
, boolean isRemove
189 static Reaction
from(
190 SignalServiceDataMessage
.Reaction reaction
,
191 RecipientResolver recipientResolver
,
192 RecipientAddressResolver addressResolver
194 return new Reaction(reaction
.getTargetSentTimestamp(),
195 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(reaction
.getTargetAuthor())),
197 reaction
.isRemove());
203 RecipientAddress author
,
204 Optional
<String
> text
,
205 List
<Mention
> mentions
,
206 List
<Attachment
> attachments
210 SignalServiceDataMessage
.Quote quote
,
211 RecipientResolver recipientResolver
,
212 RecipientAddressResolver addressResolver
,
213 final AttachmentFileProvider fileProvider
215 return new Quote(quote
.getId(),
216 addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(quote
.getAuthor())),
217 Optional
.ofNullable(quote
.getText()),
218 quote
.getMentions() == null
220 : quote
.getMentions()
222 .map(m
-> Mention
.from(m
, recipientResolver
, addressResolver
))
224 quote
.getAttachments() == null
226 : quote
.getAttachments().stream().map(a
-> Attachment
.from(a
, fileProvider
)).toList());
230 public record Payment(String note
, byte[] receipt
) {
232 static Payment
from(SignalServiceDataMessage
.Payment payment
) {
233 return new Payment(payment
.getPaymentNotification().get().getNote(),
234 payment
.getPaymentNotification().get().getReceipt());
238 public record Mention(RecipientAddress recipient
, int start
, int length
) {
241 SignalServiceDataMessage
.Mention mention
,
242 RecipientResolver recipientResolver
,
243 RecipientAddressResolver addressResolver
245 return new Mention(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(mention
.getAci())),
247 mention
.getLength());
251 public record Attachment(
254 Optional
<String
> fileName
,
256 Optional
<Long
> uploadTimestamp
,
258 Optional
<byte[]> preview
,
259 Optional
<Attachment
> thumbnail
,
260 Optional
<String
> caption
,
261 Optional
<Integer
> width
,
262 Optional
<Integer
> height
,
268 static Attachment
from(SignalServiceAttachment attachment
, AttachmentFileProvider fileProvider
) {
269 if (attachment
.isPointer()) {
270 final var a
= attachment
.asPointer();
271 return new Attachment(Optional
.of(a
.getRemoteId().toString()),
272 Optional
.of(fileProvider
.getFile(a
.getRemoteId())),
273 Optional
.ofNullable(a
.getFileName().orNull()),
275 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
276 Optional
.ofNullable(a
.getSize().transform(Integer
::longValue
).orNull()),
277 Optional
.ofNullable(a
.getPreview().orNull()),
279 Optional
.ofNullable(a
.getCaption().orNull()),
280 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
281 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
286 final var a
= attachment
.asStream();
287 return new Attachment(Optional
.empty(),
289 Optional
.ofNullable(a
.getFileName().orNull()),
291 a
.getUploadTimestamp() == 0 ? Optional
.empty() : Optional
.of(a
.getUploadTimestamp()),
292 Optional
.of(a
.getLength()),
293 Optional
.ofNullable(a
.getPreview().orNull()),
295 Optional
.ofNullable(a
.getCaption().orNull()),
296 a
.getWidth() == 0 ? Optional
.empty() : Optional
.of(a
.getWidth()),
297 a
.getHeight() == 0 ? Optional
.empty() : Optional
.of(a
.getHeight()),
304 static Attachment
from(
305 SignalServiceDataMessage
.Quote
.QuotedAttachment a
, final AttachmentFileProvider fileProvider
307 return new Attachment(Optional
.empty(),
309 Optional
.ofNullable(a
.getFileName()),
314 a
.getThumbnail() == null
316 : Optional
.of(Attachment
.from(a
.getThumbnail(), fileProvider
)),
326 public record Sticker(StickerPackId packId
, byte[] packKey
, int stickerId
) {
328 static Sticker
from(SignalServiceDataMessage
.Sticker sticker
) {
329 return new Sticker(StickerPackId
.deserialize(sticker
.getPackId()),
330 sticker
.getPackKey(),
331 sticker
.getStickerId());
335 public record SharedContact(
337 Optional
<Avatar
> avatar
,
340 List
<Address
> address
,
341 Optional
<String
> organization
344 static SharedContact
from(
345 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact sharedContact
,
346 final AttachmentFileProvider fileProvider
348 return new SharedContact(Name
.from(sharedContact
.getName()),
349 Optional
.ofNullable(sharedContact
.getAvatar()
350 .transform(avatar1
-> Avatar
.from(avatar1
, fileProvider
))
352 sharedContact
.getPhone().transform(p
-> p
.stream().map(Phone
::from
).toList()).or(List
.of()),
353 sharedContact
.getEmail().transform(p
-> p
.stream().map(Email
::from
).toList()).or(List
.of()),
354 sharedContact
.getAddress().transform(p
-> p
.stream().map(Address
::from
).toList()).or(List
.of()),
355 Optional
.ofNullable(sharedContact
.getOrganization().orNull()));
359 Optional
<String
> display
,
360 Optional
<String
> given
,
361 Optional
<String
> family
,
362 Optional
<String
> prefix
,
363 Optional
<String
> suffix
,
364 Optional
<String
> middle
367 static Name
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Name name
) {
368 return new Name(Optional
.ofNullable(name
.getDisplay().orNull()),
369 Optional
.ofNullable(name
.getGiven().orNull()),
370 Optional
.ofNullable(name
.getFamily().orNull()),
371 Optional
.ofNullable(name
.getPrefix().orNull()),
372 Optional
.ofNullable(name
.getSuffix().orNull()),
373 Optional
.ofNullable(name
.getMiddle().orNull()));
377 public record Avatar(Attachment attachment
, boolean isProfile
) {
380 org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Avatar avatar
,
381 final AttachmentFileProvider fileProvider
383 return new Avatar(Attachment
.from(avatar
.getAttachment(), fileProvider
), avatar
.isProfile());
388 String value
, Type type
, Optional
<String
> label
391 static Phone
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone phone
) {
392 return new Phone(phone
.getValue(),
393 Type
.from(phone
.getType()),
394 Optional
.ofNullable(phone
.getLabel().orNull()));
403 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Phone
.Type type
) {
404 return switch (type
) {
407 case MOBILE
-> MOBILE
;
408 case CUSTOM
-> CUSTOM
;
415 String value
, Type type
, Optional
<String
> label
418 static Email
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email email
) {
419 return new Email(email
.getValue(),
420 Type
.from(email
.getType()),
421 Optional
.ofNullable(email
.getLabel().orNull()));
430 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.Email
.Type type
) {
431 return switch (type
) {
434 case MOBILE
-> MOBILE
;
435 case CUSTOM
-> CUSTOM
;
441 public record Address(
443 Optional
<String
> label
,
444 Optional
<String
> street
,
445 Optional
<String
> pobox
,
446 Optional
<String
> neighborhood
,
447 Optional
<String
> city
,
448 Optional
<String
> region
,
449 Optional
<String
> postcode
,
450 Optional
<String
> country
453 static Address
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress address
) {
454 return new Address(Address
.Type
.from(address
.getType()),
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()),
460 Optional
.ofNullable(address
.getLabel().orNull()),
461 Optional
.ofNullable(address
.getLabel().orNull()),
462 Optional
.ofNullable(address
.getLabel().orNull()));
470 static Type
from(org
.whispersystems
.signalservice
.api
.messages
.shared
.SharedContact
.PostalAddress
.Type type
) {
471 return switch (type
) {
474 case CUSTOM
-> CUSTOM
;
481 public record Preview(String title
, String description
, long date
, String url
, Optional
<Attachment
> image
) {
484 SignalServiceDataMessage
.Preview preview
, final AttachmentFileProvider fileProvider
486 return new Preview(preview
.getTitle(),
487 preview
.getDescription(),
490 Optional
.ofNullable(preview
.getImage()
491 .transform(as -> Attachment
.from(as, fileProvider
))
499 Optional
<Blocked
> blocked
,
502 Optional
<ViewOnceOpen
> viewOnceOpen
,
503 Optional
<Contacts
> contacts
,
504 Optional
<Groups
> groups
,
505 Optional
<MessageRequestResponse
> messageRequestResponse
508 public static Sync
from(
509 final SignalServiceSyncMessage syncMessage
,
510 RecipientResolver recipientResolver
,
511 RecipientAddressResolver addressResolver
,
512 final AttachmentFileProvider fileProvider
514 return new Sync(Optional
.ofNullable(syncMessage
.getSent()
515 .transform(s
-> Sent
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
517 Optional
.ofNullable(syncMessage
.getBlockedList()
518 .transform(b
-> Blocked
.from(b
, recipientResolver
, addressResolver
))
520 syncMessage
.getRead()
521 .transform(r
-> r
.stream()
522 .map(rm
-> Read
.from(rm
, recipientResolver
, addressResolver
))
525 syncMessage
.getViewed()
526 .transform(r
-> r
.stream()
527 .map(rm
-> Viewed
.from(rm
, recipientResolver
, addressResolver
))
530 Optional
.ofNullable(syncMessage
.getViewOnceOpen()
531 .transform(rm
-> ViewOnceOpen
.from(rm
, recipientResolver
, addressResolver
))
533 Optional
.ofNullable(syncMessage
.getContacts().transform(Contacts
::from
).orNull()),
534 Optional
.ofNullable(syncMessage
.getGroups().transform(Groups
::from
).orNull()),
535 Optional
.ofNullable(syncMessage
.getMessageRequestResponse()
536 .transform(m
-> MessageRequestResponse
.from(m
, recipientResolver
, addressResolver
))
542 long expirationStartTimestamp
,
543 Optional
<RecipientAddress
> destination
,
544 Set
<RecipientAddress
> recipients
,
549 SentTranscriptMessage sentMessage
,
550 RecipientResolver recipientResolver
,
551 RecipientAddressResolver addressResolver
,
552 final AttachmentFileProvider fileProvider
554 return new Sent(sentMessage
.getTimestamp(),
555 sentMessage
.getExpirationStartTimestamp(),
556 Optional
.ofNullable(sentMessage
.getDestination()
557 .transform(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
560 sentMessage
.getRecipients()
562 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
563 .collect(Collectors
.toSet()),
564 Data
.from(sentMessage
.getMessage(), recipientResolver
, addressResolver
, fileProvider
));
568 public record Blocked(List
<RecipientAddress
> recipients
, List
<GroupId
> groupIds
) {
571 BlockedListMessage blockedListMessage
,
572 RecipientResolver recipientResolver
,
573 RecipientAddressResolver addressResolver
575 return new Blocked(blockedListMessage
.getAddresses()
577 .map(d
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(d
)))
578 .toList(), blockedListMessage
.getGroupIds().stream().map(GroupId
::unknownVersion
).toList());
582 public record Read(RecipientAddress sender
, long timestamp
) {
585 ReadMessage readMessage
,
586 RecipientResolver recipientResolver
,
587 RecipientAddressResolver addressResolver
589 return new Read(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
590 readMessage
.getTimestamp());
594 public record Viewed(RecipientAddress sender
, long timestamp
) {
597 ViewedMessage readMessage
,
598 RecipientResolver recipientResolver
,
599 RecipientAddressResolver addressResolver
601 return new Viewed(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(readMessage
.getSender())),
602 readMessage
.getTimestamp());
606 public record ViewOnceOpen(RecipientAddress sender
, long timestamp
) {
608 static ViewOnceOpen
from(
609 ViewOnceOpenMessage readMessage
,
610 RecipientResolver recipientResolver
,
611 RecipientAddressResolver addressResolver
613 return new ViewOnceOpen(addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
614 readMessage
.getSender())), readMessage
.getTimestamp());
618 public record Contacts(boolean isComplete
) {
620 static Contacts
from(ContactsMessage contactsMessage
) {
621 return new Contacts(contactsMessage
.isComplete());
625 public record Groups() {
627 static Groups
from(SignalServiceAttachment groupsMessage
) {
632 public record MessageRequestResponse(Type type
, Optional
<GroupId
> groupId
, Optional
<RecipientAddress
> person
) {
634 static MessageRequestResponse
from(
635 MessageRequestResponseMessage messageRequestResponse
,
636 RecipientResolver recipientResolver
,
637 RecipientAddressResolver addressResolver
639 return new MessageRequestResponse(Type
.from(messageRequestResponse
.getType()),
640 Optional
.ofNullable(messageRequestResponse
.getGroupId()
641 .transform(GroupId
::unknownVersion
)
643 Optional
.ofNullable(messageRequestResponse
.getPerson()
644 .transform(p
-> addressResolver
.resolveRecipientAddress(recipientResolver
.resolveRecipient(
657 static Type
from(MessageRequestResponseMessage
.Type type
) {
658 return switch (type
) {
659 case UNKNOWN
-> UNKNOWN
;
660 case ACCEPT
-> ACCEPT
;
661 case DELETE
-> DELETE
;
663 case BLOCK_AND_DELETE
-> BLOCK_AND_DELETE
;
664 case UNBLOCK_AND_ACCEPT
-> UNBLOCK_AND_ACCEPT
;
672 Optional
<Integer
> destinationDeviceId
,
673 Optional
<GroupId
> groupId
,
674 Optional
<Long
> timestamp
,
675 Optional
<Offer
> offer
,
676 Optional
<Answer
> answer
,
677 Optional
<Hangup
> hangup
,
679 List
<IceUpdate
> iceUpdate
,
680 Optional
<Opaque
> opaque
683 public static Call
from(final SignalServiceCallMessage callMessage
) {
684 return new Call(Optional
.ofNullable(callMessage
.getDestinationDeviceId().orNull()),
685 Optional
.ofNullable(callMessage
.getGroupId().transform(GroupId
::unknownVersion
).orNull()),
686 Optional
.ofNullable(callMessage
.getTimestamp().orNull()),
687 Optional
.ofNullable(callMessage
.getOfferMessage().transform(Offer
::from
).orNull()),
688 Optional
.ofNullable(callMessage
.getAnswerMessage().transform(Answer
::from
).orNull()),
689 Optional
.ofNullable(callMessage
.getHangupMessage().transform(Hangup
::from
).orNull()),
690 Optional
.ofNullable(callMessage
.getBusyMessage().transform(Busy
::from
).orNull()),
691 callMessage
.getIceUpdateMessages()
692 .transform(m
-> m
.stream().map(IceUpdate
::from
).toList())
694 Optional
.ofNullable(callMessage
.getOpaqueMessage().transform(Opaque
::from
).orNull()));
697 public record Offer(long id
, String sdp
, Type type
, byte[] opaque
) {
699 static Offer
from(OfferMessage offerMessage
) {
700 return new Offer(offerMessage
.getId(),
701 offerMessage
.getSdp(),
702 Type
.from(offerMessage
.getType()),
703 offerMessage
.getOpaque());
710 static Type
from(OfferMessage
.Type type
) {
711 return switch (type
) {
712 case AUDIO_CALL
-> AUDIO_CALL
;
713 case VIDEO_CALL
-> VIDEO_CALL
;
719 public record Answer(long id
, String sdp
, byte[] opaque
) {
721 static Answer
from(AnswerMessage answerMessage
) {
722 return new Answer(answerMessage
.getId(), answerMessage
.getSdp(), answerMessage
.getOpaque());
726 public record Busy(long id
) {
728 static Busy
from(BusyMessage busyMessage
) {
729 return new Busy(busyMessage
.getId());
733 public record Hangup(long id
, Type type
, int deviceId
, boolean isLegacy
) {
735 static Hangup
from(HangupMessage hangupMessage
) {
736 return new Hangup(hangupMessage
.getId(),
737 Type
.from(hangupMessage
.getType()),
738 hangupMessage
.getDeviceId(),
739 hangupMessage
.isLegacy());
749 static Type
from(HangupMessage
.Type type
) {
750 return switch (type
) {
751 case NORMAL
-> NORMAL
;
752 case ACCEPTED
-> ACCEPTED
;
753 case DECLINED
-> DECLINED
;
755 case NEED_PERMISSION
-> NEED_PERMISSION
;
761 public record IceUpdate(long id
, String sdp
, byte[] opaque
) {
763 static IceUpdate
from(IceUpdateMessage iceUpdateMessage
) {
764 return new IceUpdate(iceUpdateMessage
.getId(), iceUpdateMessage
.getSdp(), iceUpdateMessage
.getOpaque());
768 public record Opaque(byte[] opaque
, Urgency urgency
) {
770 static Opaque
from(OpaqueMessage opaqueMessage
) {
771 return new Opaque(opaqueMessage
.getOpaque(), Urgency
.from(opaqueMessage
.getUrgency()));
774 public enum Urgency
{
778 static Urgency
from(OpaqueMessage
.Urgency urgency
) {
779 return switch (urgency
) {
780 case DROPPABLE
-> DROPPABLE
;
781 case HANDLE_IMMEDIATELY
-> HANDLE_IMMEDIATELY
;
788 public static MessageEnvelope
from(
789 SignalServiceEnvelope envelope
,
790 SignalServiceContent content
,
791 RecipientResolver recipientResolver
,
792 RecipientAddressResolver addressResolver
,
793 final AttachmentFileProvider fileProvider
,
796 final var source
= !envelope
.isUnidentifiedSender() && envelope
.hasSourceUuid()
797 ? recipientResolver
.resolveRecipient(envelope
.getSourceAddress())
798 : envelope
.isUnidentifiedSender() && content
!= null
799 ? recipientResolver
.resolveRecipient(content
.getSender())
800 : exception
instanceof ProtocolException e
801 ? recipientResolver
.resolveRecipient(e
.getSender())
803 final var sourceDevice
= envelope
.hasSourceDevice()
804 ? envelope
.getSourceDevice()
806 ? content
.getSenderDevice()
807 : exception
instanceof ProtocolException e ? e
.getSenderDevice() : 0;
809 Optional
<Receipt
> receipt
;
810 Optional
<Typing
> typing
;
814 if (content
!= null) {
815 receipt
= Optional
.ofNullable(content
.getReceiptMessage().transform(Receipt
::from
).orNull());
816 typing
= Optional
.ofNullable(content
.getTypingMessage().transform(Typing
::from
).orNull());
817 data
= Optional
.ofNullable(content
.getDataMessage()
818 .transform(dataMessage
-> Data
.from(dataMessage
, recipientResolver
, addressResolver
, fileProvider
))
820 sync
= Optional
.ofNullable(content
.getSyncMessage()
821 .transform(s
-> Sync
.from(s
, recipientResolver
, addressResolver
, fileProvider
))
823 call
= Optional
.ofNullable(content
.getCallMessage().transform(Call
::from
).orNull());
825 receipt
= envelope
.isReceipt() ? Optional
.of(new Receipt(envelope
.getServerReceivedTimestamp(),
826 Receipt
.Type
.DELIVERY
,
827 List
.of(envelope
.getTimestamp()))) : Optional
.empty();
828 typing
= Optional
.empty();
829 data
= Optional
.empty();
830 sync
= Optional
.empty();
831 call
= Optional
.empty();
834 return new MessageEnvelope(source
== null
836 : Optional
.of(addressResolver
.resolveRecipientAddress(source
)),
838 envelope
.getTimestamp(),
839 envelope
.getServerReceivedTimestamp(),
840 envelope
.getServerDeliveredTimestamp(),
841 envelope
.isUnidentifiedSender(),
849 public interface AttachmentFileProvider
{
851 File
getFile(SignalServiceAttachmentRemoteId attachmentRemoteId
);