]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
98b754e2ce13a1c6b4a95d1322abb12213868034
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / api / MessageEnvelope.java
1 package org.asamk.signal.manager.api;
2
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.SignalServiceContent;
10 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
11 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
12 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
13 import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
14 import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
15 import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
16 import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
17 import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
18 import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
19 import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
20 import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
21 import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
22 import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
23 import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
24 import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
25 import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage;
26 import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
27 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
28 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
29 import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
30 import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
31
32 import java.util.List;
33 import java.util.Optional;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36
37 public record MessageEnvelope(
38 Optional<RecipientAddress> sourceAddress,
39 int sourceDevice,
40 long timestamp,
41 long serverReceivedTimestamp,
42 long serverDeliveredTimestamp,
43 boolean isUnidentifiedSender,
44 Optional<Receipt> receipt,
45 Optional<Typing> typing,
46 Optional<Data> data,
47 Optional<Sync> sync,
48 Optional<Call> call
49 ) {
50
51 public static final record Receipt(long when, Type type, List<Long> timestamps) {
52
53 static Receipt from(final SignalServiceReceiptMessage receiptMessage) {
54 return new Receipt(receiptMessage.getWhen(),
55 Type.from(receiptMessage.getType()),
56 receiptMessage.getTimestamps());
57 }
58
59 public enum Type {
60 DELIVERY,
61 READ,
62 VIEWED,
63 UNKNOWN;
64
65 static Type from(SignalServiceReceiptMessage.Type type) {
66 return switch (type) {
67 case DELIVERY -> DELIVERY;
68 case READ -> READ;
69 case VIEWED -> VIEWED;
70 case UNKNOWN -> UNKNOWN;
71 };
72 }
73 }
74 }
75
76 public static final record Typing(long timestamp, Type type, Optional<GroupId> groupId) {
77
78 public static Typing from(final SignalServiceTypingMessage typingMessage) {
79 return new Typing(typingMessage.getTimestamp(),
80 typingMessage.isTypingStarted() ? Type.STARTED : Type.STOPPED,
81 Optional.ofNullable(typingMessage.getGroupId().transform(GroupId::unknownVersion).orNull()));
82 }
83
84 public enum Type {
85 STARTED,
86 STOPPED,
87 }
88 }
89
90 public static final record Data(
91 long timestamp,
92 Optional<GroupContext> groupContext,
93 Optional<GroupCallUpdate> groupCallUpdate,
94 Optional<String> body,
95 int expiresInSeconds,
96 boolean isExpirationUpdate,
97 boolean isViewOnce,
98 boolean isEndSession,
99 boolean hasProfileKey,
100 Optional<Reaction> reaction,
101 Optional<Quote> quote,
102 List<Attachment> attachments,
103 Optional<Long> remoteDeleteId,
104 Optional<Sticker> sticker,
105 List<SharedContact> sharedContacts,
106 List<Mention> mentions,
107 List<Preview> previews
108 ) {
109
110 static Data from(
111 final SignalServiceDataMessage dataMessage,
112 RecipientResolver recipientResolver,
113 RecipientAddressResolver addressResolver
114 ) {
115 return new Data(dataMessage.getTimestamp(),
116 Optional.ofNullable(dataMessage.getGroupContext().transform(GroupContext::from).orNull()),
117 Optional.ofNullable(dataMessage.getGroupCallUpdate().transform(GroupCallUpdate::from).orNull()),
118 Optional.ofNullable(dataMessage.getBody().orNull()),
119 dataMessage.getExpiresInSeconds(),
120 dataMessage.isExpirationUpdate(),
121 dataMessage.isViewOnce(),
122 dataMessage.isEndSession(),
123 dataMessage.getProfileKey().isPresent(),
124 Optional.ofNullable(dataMessage.getReaction()
125 .transform(r -> Reaction.from(r, recipientResolver, addressResolver))
126 .orNull()),
127 Optional.ofNullable(dataMessage.getQuote()
128 .transform(q -> Quote.from(q, recipientResolver, addressResolver))
129 .orNull()),
130 dataMessage.getAttachments()
131 .transform(a -> a.stream().map(Attachment::from).collect(Collectors.toList()))
132 .or(List.of()),
133 Optional.ofNullable(dataMessage.getRemoteDelete()
134 .transform(SignalServiceDataMessage.RemoteDelete::getTargetSentTimestamp)
135 .orNull()),
136 Optional.ofNullable(dataMessage.getSticker().transform(Sticker::from).orNull()),
137 dataMessage.getSharedContacts()
138 .transform(a -> a.stream().map(SharedContact::from).collect(Collectors.toList()))
139 .or(List.of()),
140 dataMessage.getMentions()
141 .transform(a -> a.stream()
142 .map(m -> Mention.from(m, recipientResolver, addressResolver))
143 .collect(Collectors.toList()))
144 .or(List.of()),
145 dataMessage.getPreviews()
146 .transform(a -> a.stream().map(Preview::from).collect(Collectors.toList()))
147 .or(List.of()));
148 }
149
150 public record GroupContext(GroupId groupId, boolean isGroupUpdate, int revision) {
151
152 static GroupContext from(SignalServiceGroupContext groupContext) {
153 if (groupContext.getGroupV1().isPresent()) {
154 return new GroupContext(GroupId.v1(groupContext.getGroupV1().get().getGroupId()),
155 groupContext.getGroupV1Type() == SignalServiceGroup.Type.UPDATE,
156 0);
157 } else if (groupContext.getGroupV2().isPresent()) {
158 final var groupV2 = groupContext.getGroupV2().get();
159 return new GroupContext(GroupUtils.getGroupIdV2(groupV2.getMasterKey()),
160 groupV2.hasSignedGroupChange(),
161 groupV2.getRevision());
162 } else {
163 throw new RuntimeException("Invalid group context state");
164 }
165 }
166 }
167
168 public record GroupCallUpdate(String eraId) {
169
170 static GroupCallUpdate from(SignalServiceDataMessage.GroupCallUpdate groupCallUpdate) {
171 return new GroupCallUpdate(groupCallUpdate.getEraId());
172 }
173 }
174
175 public record Reaction(
176 long targetSentTimestamp, RecipientAddress targetAuthor, String emoji, boolean isRemove
177 ) {
178
179 static Reaction from(
180 SignalServiceDataMessage.Reaction reaction,
181 RecipientResolver recipientResolver,
182 RecipientAddressResolver addressResolver
183 ) {
184 return new Reaction(reaction.getTargetSentTimestamp(),
185 addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())),
186 reaction.getEmoji(),
187 reaction.isRemove());
188 }
189 }
190
191 public record Quote(
192 long id,
193 RecipientAddress author,
194 Optional<String> text,
195 List<Mention> mentions,
196 List<Attachment> attachments
197 ) {
198
199 static Quote from(
200 SignalServiceDataMessage.Quote quote,
201 RecipientResolver recipientResolver,
202 RecipientAddressResolver addressResolver
203 ) {
204 return new Quote(quote.getId(),
205 addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
206 Optional.ofNullable(quote.getText()),
207 quote.getMentions()
208 .stream()
209 .map(m -> Mention.from(m, recipientResolver, addressResolver))
210 .collect(Collectors.toList()),
211 quote.getAttachments().stream().map(Attachment::from).collect(Collectors.toList()));
212 }
213 }
214
215 public record Mention(RecipientAddress recipient, int start, int length) {
216
217 static Mention from(
218 SignalServiceDataMessage.Mention mention,
219 RecipientResolver recipientResolver,
220 RecipientAddressResolver addressResolver
221 ) {
222 return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getUuid())),
223 mention.getStart(),
224 mention.getLength());
225 }
226 }
227
228 public record Attachment(
229 Optional<String> id,
230 Optional<String> fileName,
231 String contentType,
232 Optional<Long> uploadTimestamp,
233 Optional<Long> size,
234 Optional<byte[]> preview,
235 Optional<Attachment> thumbnail,
236 Optional<String> caption,
237 Optional<Integer> width,
238 Optional<Integer> height,
239 boolean isVoiceNote,
240 boolean isGif,
241 boolean isBorderless
242 ) {
243
244 static Attachment from(SignalServiceAttachment attachment) {
245 if (attachment.isPointer()) {
246 final var a = attachment.asPointer();
247 return new Attachment(Optional.of(a.getRemoteId().toString()),
248 Optional.ofNullable(a.getFileName().orNull()),
249 a.getContentType(),
250 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
251 Optional.ofNullable(a.getSize().transform(Integer::longValue).orNull()),
252 Optional.ofNullable(a.getPreview().orNull()),
253 Optional.empty(),
254 Optional.ofNullable(a.getCaption().orNull()),
255 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
256 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
257 a.getVoiceNote(),
258 a.isGif(),
259 a.isBorderless());
260 } else {
261 final var a = attachment.asStream();
262 return new Attachment(Optional.empty(),
263 Optional.ofNullable(a.getFileName().orNull()),
264 a.getContentType(),
265 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
266 Optional.of(a.getLength()),
267 Optional.ofNullable(a.getPreview().orNull()),
268 Optional.empty(),
269 Optional.ofNullable(a.getCaption().orNull()),
270 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
271 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
272 a.getVoiceNote(),
273 a.isGif(),
274 a.isBorderless());
275 }
276 }
277
278 static Attachment from(SignalServiceDataMessage.Quote.QuotedAttachment a) {
279 return new Attachment(Optional.empty(),
280 Optional.ofNullable(a.getFileName()),
281 a.getContentType(),
282 Optional.empty(),
283 Optional.empty(),
284 Optional.empty(),
285 a.getThumbnail() == null ? Optional.empty() : Optional.of(Attachment.from(a.getThumbnail())),
286 Optional.empty(),
287 Optional.empty(),
288 Optional.empty(),
289 false,
290 false,
291 false);
292 }
293 }
294
295 public record Sticker(byte[] packId, byte[] packKey, int stickerId) {
296
297 static Sticker from(SignalServiceDataMessage.Sticker sticker) {
298 return new Sticker(sticker.getPackId(), sticker.getPackKey(), sticker.getStickerId());
299 }
300 }
301
302 public record SharedContact(
303 Name name,
304 Optional<Avatar> avatar,
305 List<Phone> phone,
306 List<Email> email,
307 List<Address> address,
308 Optional<String> organization
309 ) {
310
311 static SharedContact from(org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact) {
312 return new SharedContact(Name.from(sharedContact.getName()),
313 Optional.ofNullable(sharedContact.getAvatar().transform(Avatar::from).orNull()),
314 sharedContact.getPhone()
315 .transform(p -> p.stream().map(Phone::from).collect(Collectors.toList()))
316 .or(List.of()),
317 sharedContact.getEmail()
318 .transform(p -> p.stream().map(Email::from).collect(Collectors.toList()))
319 .or(List.of()),
320 sharedContact.getAddress()
321 .transform(p -> p.stream().map(Address::from).collect(Collectors.toList()))
322 .or(List.of()),
323 Optional.ofNullable(sharedContact.getOrganization().orNull()));
324 }
325
326 public record Name(
327 Optional<String> display,
328 Optional<String> given,
329 Optional<String> family,
330 Optional<String> prefix,
331 Optional<String> suffix,
332 Optional<String> middle
333 ) {
334
335 static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) {
336 return new Name(Optional.ofNullable(name.getDisplay().orNull()),
337 Optional.ofNullable(name.getGiven().orNull()),
338 Optional.ofNullable(name.getFamily().orNull()),
339 Optional.ofNullable(name.getPrefix().orNull()),
340 Optional.ofNullable(name.getSuffix().orNull()),
341 Optional.ofNullable(name.getMiddle().orNull()));
342 }
343 }
344
345 public record Avatar(Attachment attachment, boolean isProfile) {
346
347 static Avatar from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar) {
348 return new Avatar(Attachment.from(avatar.getAttachment()), avatar.isProfile());
349 }
350 }
351
352 public record Phone(
353 String value, Type type, Optional<String> label
354 ) {
355
356 static Phone from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone phone) {
357 return new Phone(phone.getValue(),
358 Type.from(phone.getType()),
359 Optional.ofNullable(phone.getLabel().orNull()));
360 }
361
362 public enum Type {
363 HOME,
364 WORK,
365 MOBILE,
366 CUSTOM;
367
368 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone.Type type) {
369 return switch (type) {
370 case HOME -> HOME;
371 case WORK -> WORK;
372 case MOBILE -> MOBILE;
373 case CUSTOM -> CUSTOM;
374 };
375 }
376 }
377 }
378
379 public record Email(
380 String value, Type type, Optional<String> label
381 ) {
382
383 static Email from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email email) {
384 return new Email(email.getValue(),
385 Type.from(email.getType()),
386 Optional.ofNullable(email.getLabel().orNull()));
387 }
388
389 public enum Type {
390 HOME,
391 WORK,
392 MOBILE,
393 CUSTOM;
394
395 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email.Type type) {
396 return switch (type) {
397 case HOME -> HOME;
398 case WORK -> WORK;
399 case MOBILE -> MOBILE;
400 case CUSTOM -> CUSTOM;
401 };
402 }
403 }
404 }
405
406 public record Address(
407 Type type,
408 Optional<String> label,
409 Optional<String> street,
410 Optional<String> pobox,
411 Optional<String> neighborhood,
412 Optional<String> city,
413 Optional<String> region,
414 Optional<String> postcode,
415 Optional<String> country
416 ) {
417
418 static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) {
419 return new Address(Address.Type.from(address.getType()),
420 Optional.ofNullable(address.getLabel().orNull()),
421 Optional.ofNullable(address.getLabel().orNull()),
422 Optional.ofNullable(address.getLabel().orNull()),
423 Optional.ofNullable(address.getLabel().orNull()),
424 Optional.ofNullable(address.getLabel().orNull()),
425 Optional.ofNullable(address.getLabel().orNull()),
426 Optional.ofNullable(address.getLabel().orNull()),
427 Optional.ofNullable(address.getLabel().orNull()));
428 }
429
430 public enum Type {
431 HOME,
432 WORK,
433 CUSTOM;
434
435 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress.Type type) {
436 return switch (type) {
437 case HOME -> HOME;
438 case WORK -> WORK;
439 case CUSTOM -> CUSTOM;
440 };
441 }
442 }
443 }
444 }
445
446 public record Preview(String title, String description, long date, String url, Optional<Attachment> image) {
447
448 static Preview from(SignalServiceDataMessage.Preview preview) {
449 return new Preview(preview.getTitle(),
450 preview.getDescription(),
451 preview.getDate(),
452 preview.getUrl(),
453 Optional.ofNullable(preview.getImage().transform(Attachment::from).orNull()));
454 }
455 }
456 }
457
458 public static final record Sync(
459 Optional<Sent> sent,
460 Optional<Blocked> blocked,
461 List<Read> read,
462 List<Viewed> viewed,
463 Optional<ViewOnceOpen> viewOnceOpen,
464 Optional<Contacts> contacts,
465 Optional<Groups> groups,
466 Optional<MessageRequestResponse> messageRequestResponse
467 ) {
468
469 public static Sync from(
470 final SignalServiceSyncMessage syncMessage,
471 RecipientResolver recipientResolver,
472 RecipientAddressResolver addressResolver
473 ) {
474 return new Sync(Optional.ofNullable(syncMessage.getSent()
475 .transform(s -> Sent.from(s, recipientResolver, addressResolver))
476 .orNull()),
477 Optional.ofNullable(syncMessage.getBlockedList()
478 .transform(b -> Blocked.from(b, recipientResolver, addressResolver))
479 .orNull()),
480 syncMessage.getRead()
481 .transform(r -> r.stream()
482 .map(rm -> Read.from(rm, recipientResolver, addressResolver))
483 .collect(Collectors.toList()))
484 .or(List.of()),
485 syncMessage.getViewed()
486 .transform(r -> r.stream()
487 .map(rm -> Viewed.from(rm, recipientResolver, addressResolver))
488 .collect(Collectors.toList()))
489 .or(List.of()),
490 Optional.ofNullable(syncMessage.getViewOnceOpen()
491 .transform(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver))
492 .orNull()),
493 Optional.ofNullable(syncMessage.getContacts().transform(Contacts::from).orNull()),
494 Optional.ofNullable(syncMessage.getGroups().transform(Groups::from).orNull()),
495 Optional.ofNullable(syncMessage.getMessageRequestResponse()
496 .transform(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver))
497 .orNull()));
498 }
499
500 public record Sent(
501 long timestamp,
502 long expirationStartTimestamp,
503 Optional<RecipientAddress> destination,
504 Set<RecipientAddress> recipients,
505 Data message
506 ) {
507
508 static Sent from(
509 SentTranscriptMessage sentMessage,
510 RecipientResolver recipientResolver,
511 RecipientAddressResolver addressResolver
512 ) {
513 return new Sent(sentMessage.getTimestamp(),
514 sentMessage.getExpirationStartTimestamp(),
515 Optional.ofNullable(sentMessage.getDestination()
516 .transform(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
517 d)))
518 .orNull()),
519 sentMessage.getRecipients()
520 .stream()
521 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
522 .collect(Collectors.toSet()),
523 Data.from(sentMessage.getMessage(), recipientResolver, addressResolver));
524 }
525 }
526
527 public record Blocked(List<RecipientAddress> recipients, List<GroupId> groupIds) {
528
529 static Blocked from(
530 BlockedListMessage blockedListMessage,
531 RecipientResolver recipientResolver,
532 RecipientAddressResolver addressResolver
533 ) {
534 return new Blocked(blockedListMessage.getAddresses()
535 .stream()
536 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
537 .collect(Collectors.toList()),
538 blockedListMessage.getGroupIds()
539 .stream()
540 .map(GroupId::unknownVersion)
541 .collect(Collectors.toList()));
542 }
543 }
544
545 public record Read(RecipientAddress sender, long timestamp) {
546
547 static Read from(
548 ReadMessage readMessage,
549 RecipientResolver recipientResolver,
550 RecipientAddressResolver addressResolver
551 ) {
552 return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
553 readMessage.getTimestamp());
554 }
555 }
556
557 public record Viewed(RecipientAddress sender, long timestamp) {
558
559 static Viewed from(
560 ViewedMessage readMessage,
561 RecipientResolver recipientResolver,
562 RecipientAddressResolver addressResolver
563 ) {
564 return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
565 readMessage.getTimestamp());
566 }
567 }
568
569 public record ViewOnceOpen(RecipientAddress sender, long timestamp) {
570
571 static ViewOnceOpen from(
572 ViewOnceOpenMessage readMessage,
573 RecipientResolver recipientResolver,
574 RecipientAddressResolver addressResolver
575 ) {
576 return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
577 readMessage.getSender())), readMessage.getTimestamp());
578 }
579 }
580
581 public record Contacts(boolean isComplete) {
582
583 static Contacts from(ContactsMessage contactsMessage) {
584 return new Contacts(contactsMessage.isComplete());
585 }
586 }
587
588 public record Groups() {
589
590 static Groups from(SignalServiceAttachment groupsMessage) {
591 return new Groups();
592 }
593 }
594
595 public record MessageRequestResponse(Type type, Optional<GroupId> groupId, Optional<RecipientAddress> person) {
596
597 static MessageRequestResponse from(
598 MessageRequestResponseMessage messageRequestResponse,
599 RecipientResolver recipientResolver,
600 RecipientAddressResolver addressResolver
601 ) {
602 return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
603 Optional.ofNullable(messageRequestResponse.getGroupId()
604 .transform(GroupId::unknownVersion)
605 .orNull()),
606 Optional.ofNullable(messageRequestResponse.getPerson()
607 .transform(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
608 p)))
609 .orNull()));
610 }
611
612 public enum Type {
613 UNKNOWN,
614 ACCEPT,
615 DELETE,
616 BLOCK,
617 BLOCK_AND_DELETE,
618 UNBLOCK_AND_ACCEPT;
619
620 static Type from(MessageRequestResponseMessage.Type type) {
621 return switch (type) {
622 case UNKNOWN -> UNKNOWN;
623 case ACCEPT -> ACCEPT;
624 case DELETE -> DELETE;
625 case BLOCK -> BLOCK;
626 case BLOCK_AND_DELETE -> BLOCK_AND_DELETE;
627 case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT;
628 };
629 }
630 }
631 }
632 }
633
634 public static final record Call(
635 Optional<Integer> destinationDeviceId,
636 Optional<GroupId> groupId,
637 Optional<Long> timestamp,
638 Optional<Offer> offer,
639 Optional<Answer> answer,
640 Optional<Hangup> hangup,
641 Optional<Busy> busy,
642 List<IceUpdate> iceUpdate,
643 Optional<Opaque> opaque
644 ) {
645
646 public static Call from(final SignalServiceCallMessage callMessage) {
647 return new Call(Optional.ofNullable(callMessage.getDestinationDeviceId().orNull()),
648 Optional.ofNullable(callMessage.getGroupId().transform(GroupId::unknownVersion).orNull()),
649 Optional.ofNullable(callMessage.getTimestamp().orNull()),
650 Optional.ofNullable(callMessage.getOfferMessage().transform(Offer::from).orNull()),
651 Optional.ofNullable(callMessage.getAnswerMessage().transform(Answer::from).orNull()),
652 Optional.ofNullable(callMessage.getHangupMessage().transform(Hangup::from).orNull()),
653 Optional.ofNullable(callMessage.getBusyMessage().transform(Busy::from).orNull()),
654 callMessage.getIceUpdateMessages()
655 .transform(m -> m.stream().map(IceUpdate::from).collect(Collectors.toList()))
656 .or(List.of()),
657 Optional.ofNullable(callMessage.getOpaqueMessage().transform(Opaque::from).orNull()));
658 }
659
660 public record Offer(long id, String sdp, Type type, byte[] opaque) {
661
662 static Offer from(OfferMessage offerMessage) {
663 return new Offer(offerMessage.getId(),
664 offerMessage.getSdp(),
665 Type.from(offerMessage.getType()),
666 offerMessage.getOpaque());
667 }
668
669 public enum Type {
670 AUDIO_CALL,
671 VIDEO_CALL;
672
673 static Type from(OfferMessage.Type type) {
674 return switch (type) {
675 case AUDIO_CALL -> AUDIO_CALL;
676 case VIDEO_CALL -> VIDEO_CALL;
677 };
678 }
679 }
680 }
681
682 public record Answer(long id, String sdp, byte[] opaque) {
683
684 static Answer from(AnswerMessage answerMessage) {
685 return new Answer(answerMessage.getId(), answerMessage.getSdp(), answerMessage.getOpaque());
686 }
687 }
688
689 public record Busy(long id) {
690
691 static Offer from(OfferMessage offerMessage) {
692 return new Offer(offerMessage.getId(),
693 offerMessage.getSdp(),
694 Offer.Type.from(offerMessage.getType()),
695 offerMessage.getOpaque());
696 }
697
698 static Busy from(BusyMessage busyMessage) {
699 return new Busy(busyMessage.getId());
700 }
701 }
702
703 public record Hangup(long id, Type type, int deviceId, boolean isLegacy) {
704
705 static Hangup from(HangupMessage hangupMessage) {
706 return new Hangup(hangupMessage.getId(),
707 Type.from(hangupMessage.getType()),
708 hangupMessage.getDeviceId(),
709 hangupMessage.isLegacy());
710 }
711
712 public enum Type {
713 NORMAL,
714 ACCEPTED,
715 DECLINED,
716 BUSY,
717 NEED_PERMISSION;
718
719 static Type from(HangupMessage.Type type) {
720 return switch (type) {
721 case NORMAL -> NORMAL;
722 case ACCEPTED -> ACCEPTED;
723 case DECLINED -> DECLINED;
724 case BUSY -> BUSY;
725 case NEED_PERMISSION -> NEED_PERMISSION;
726 };
727 }
728 }
729 }
730
731 public record IceUpdate(long id, String sdp, byte[] opaque) {
732
733 static IceUpdate from(IceUpdateMessage iceUpdateMessage) {
734 return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getSdp(), iceUpdateMessage.getOpaque());
735 }
736 }
737
738 public record Opaque(byte[] opaque, Urgency urgency) {
739
740 static Opaque from(OpaqueMessage opaqueMessage) {
741 return new Opaque(opaqueMessage.getOpaque(), Urgency.from(opaqueMessage.getUrgency()));
742 }
743
744 public enum Urgency {
745 DROPPABLE,
746 HANDLE_IMMEDIATELY;
747
748 static Urgency from(OpaqueMessage.Urgency urgency) {
749 return switch (urgency) {
750 case DROPPABLE -> DROPPABLE;
751 case HANDLE_IMMEDIATELY -> HANDLE_IMMEDIATELY;
752 };
753 }
754 }
755 }
756 }
757
758 public static MessageEnvelope from(
759 SignalServiceEnvelope envelope,
760 SignalServiceContent content,
761 RecipientResolver recipientResolver,
762 RecipientAddressResolver addressResolver
763 ) {
764 final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid()
765 ? recipientResolver.resolveRecipient(envelope.getSourceAddress())
766 : envelope.isUnidentifiedSender() && content != null
767 ? recipientResolver.resolveRecipient(content.getSender())
768 : null;
769 final var sourceDevice = envelope.hasSourceDevice()
770 ? envelope.getSourceDevice()
771 : content != null ? content.getSenderDevice() : 0;
772
773 Optional<Receipt> receipt;
774 Optional<Typing> typing;
775 Optional<Data> data;
776 Optional<Sync> sync;
777 Optional<Call> call;
778 if (content != null) {
779 receipt = Optional.ofNullable(content.getReceiptMessage().transform(Receipt::from).orNull());
780 typing = Optional.ofNullable(content.getTypingMessage().transform(Typing::from).orNull());
781 data = Optional.ofNullable(content.getDataMessage()
782 .transform(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver))
783 .orNull());
784 sync = Optional.ofNullable(content.getSyncMessage()
785 .transform(s -> Sync.from(s, recipientResolver, addressResolver))
786 .orNull());
787 call = Optional.ofNullable(content.getCallMessage().transform(Call::from).orNull());
788 } else {
789 receipt = Optional.empty();
790 typing = Optional.empty();
791 data = Optional.empty();
792 sync = Optional.empty();
793 call = Optional.empty();
794 }
795
796 return new MessageEnvelope(source == null
797 ? Optional.empty()
798 : Optional.of(addressResolver.resolveRecipientAddress(source)),
799 sourceDevice,
800 envelope.getTimestamp(),
801 envelope.getServerReceivedTimestamp(),
802 envelope.getServerDeliveredTimestamp(),
803 envelope.isUnidentifiedSender(),
804 receipt,
805 typing,
806 data,
807 sync,
808 call);
809 }
810 }