]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java
Remove libsignal-service from manager lib API
[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 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 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 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() == null
208 ? List.of()
209 : quote.getMentions()
210 .stream()
211 .map(m -> Mention.from(m, recipientResolver, addressResolver))
212 .collect(Collectors.toList()),
213 quote.getAttachments() == null
214 ? List.of()
215 : quote.getAttachments().stream().map(Attachment::from).collect(Collectors.toList()));
216 }
217 }
218
219 public record Mention(RecipientAddress recipient, int start, int length) {
220
221 static Mention from(
222 SignalServiceDataMessage.Mention mention,
223 RecipientResolver recipientResolver,
224 RecipientAddressResolver addressResolver
225 ) {
226 return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getUuid())),
227 mention.getStart(),
228 mention.getLength());
229 }
230 }
231
232 public record Attachment(
233 Optional<String> id,
234 Optional<String> fileName,
235 String contentType,
236 Optional<Long> uploadTimestamp,
237 Optional<Long> size,
238 Optional<byte[]> preview,
239 Optional<Attachment> thumbnail,
240 Optional<String> caption,
241 Optional<Integer> width,
242 Optional<Integer> height,
243 boolean isVoiceNote,
244 boolean isGif,
245 boolean isBorderless
246 ) {
247
248 static Attachment from(SignalServiceAttachment attachment) {
249 if (attachment.isPointer()) {
250 final var a = attachment.asPointer();
251 return new Attachment(Optional.of(a.getRemoteId().toString()),
252 Optional.ofNullable(a.getFileName().orNull()),
253 a.getContentType(),
254 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
255 Optional.ofNullable(a.getSize().transform(Integer::longValue).orNull()),
256 Optional.ofNullable(a.getPreview().orNull()),
257 Optional.empty(),
258 Optional.ofNullable(a.getCaption().orNull()),
259 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
260 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
261 a.getVoiceNote(),
262 a.isGif(),
263 a.isBorderless());
264 } else {
265 final var a = attachment.asStream();
266 return new Attachment(Optional.empty(),
267 Optional.ofNullable(a.getFileName().orNull()),
268 a.getContentType(),
269 a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
270 Optional.of(a.getLength()),
271 Optional.ofNullable(a.getPreview().orNull()),
272 Optional.empty(),
273 Optional.ofNullable(a.getCaption().orNull()),
274 a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
275 a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
276 a.getVoiceNote(),
277 a.isGif(),
278 a.isBorderless());
279 }
280 }
281
282 static Attachment from(SignalServiceDataMessage.Quote.QuotedAttachment a) {
283 return new Attachment(Optional.empty(),
284 Optional.ofNullable(a.getFileName()),
285 a.getContentType(),
286 Optional.empty(),
287 Optional.empty(),
288 Optional.empty(),
289 a.getThumbnail() == null ? Optional.empty() : Optional.of(Attachment.from(a.getThumbnail())),
290 Optional.empty(),
291 Optional.empty(),
292 Optional.empty(),
293 false,
294 false,
295 false);
296 }
297 }
298
299 public record Sticker(byte[] packId, byte[] packKey, int stickerId) {
300
301 static Sticker from(SignalServiceDataMessage.Sticker sticker) {
302 return new Sticker(sticker.getPackId(), sticker.getPackKey(), sticker.getStickerId());
303 }
304 }
305
306 public record SharedContact(
307 Name name,
308 Optional<Avatar> avatar,
309 List<Phone> phone,
310 List<Email> email,
311 List<Address> address,
312 Optional<String> organization
313 ) {
314
315 static SharedContact from(org.whispersystems.signalservice.api.messages.shared.SharedContact sharedContact) {
316 return new SharedContact(Name.from(sharedContact.getName()),
317 Optional.ofNullable(sharedContact.getAvatar().transform(Avatar::from).orNull()),
318 sharedContact.getPhone()
319 .transform(p -> p.stream().map(Phone::from).collect(Collectors.toList()))
320 .or(List.of()),
321 sharedContact.getEmail()
322 .transform(p -> p.stream().map(Email::from).collect(Collectors.toList()))
323 .or(List.of()),
324 sharedContact.getAddress()
325 .transform(p -> p.stream().map(Address::from).collect(Collectors.toList()))
326 .or(List.of()),
327 Optional.ofNullable(sharedContact.getOrganization().orNull()));
328 }
329
330 public record Name(
331 Optional<String> display,
332 Optional<String> given,
333 Optional<String> family,
334 Optional<String> prefix,
335 Optional<String> suffix,
336 Optional<String> middle
337 ) {
338
339 static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) {
340 return new Name(Optional.ofNullable(name.getDisplay().orNull()),
341 Optional.ofNullable(name.getGiven().orNull()),
342 Optional.ofNullable(name.getFamily().orNull()),
343 Optional.ofNullable(name.getPrefix().orNull()),
344 Optional.ofNullable(name.getSuffix().orNull()),
345 Optional.ofNullable(name.getMiddle().orNull()));
346 }
347 }
348
349 public record Avatar(Attachment attachment, boolean isProfile) {
350
351 static Avatar from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Avatar avatar) {
352 return new Avatar(Attachment.from(avatar.getAttachment()), avatar.isProfile());
353 }
354 }
355
356 public record Phone(
357 String value, Type type, Optional<String> label
358 ) {
359
360 static Phone from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone phone) {
361 return new Phone(phone.getValue(),
362 Type.from(phone.getType()),
363 Optional.ofNullable(phone.getLabel().orNull()));
364 }
365
366 public enum Type {
367 HOME,
368 WORK,
369 MOBILE,
370 CUSTOM;
371
372 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Phone.Type type) {
373 return switch (type) {
374 case HOME -> HOME;
375 case WORK -> WORK;
376 case MOBILE -> MOBILE;
377 case CUSTOM -> CUSTOM;
378 };
379 }
380 }
381 }
382
383 public record Email(
384 String value, Type type, Optional<String> label
385 ) {
386
387 static Email from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email email) {
388 return new Email(email.getValue(),
389 Type.from(email.getType()),
390 Optional.ofNullable(email.getLabel().orNull()));
391 }
392
393 public enum Type {
394 HOME,
395 WORK,
396 MOBILE,
397 CUSTOM;
398
399 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Email.Type type) {
400 return switch (type) {
401 case HOME -> HOME;
402 case WORK -> WORK;
403 case MOBILE -> MOBILE;
404 case CUSTOM -> CUSTOM;
405 };
406 }
407 }
408 }
409
410 public record Address(
411 Type type,
412 Optional<String> label,
413 Optional<String> street,
414 Optional<String> pobox,
415 Optional<String> neighborhood,
416 Optional<String> city,
417 Optional<String> region,
418 Optional<String> postcode,
419 Optional<String> country
420 ) {
421
422 static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) {
423 return new Address(Address.Type.from(address.getType()),
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 Optional.ofNullable(address.getLabel().orNull()),
429 Optional.ofNullable(address.getLabel().orNull()),
430 Optional.ofNullable(address.getLabel().orNull()),
431 Optional.ofNullable(address.getLabel().orNull()));
432 }
433
434 public enum Type {
435 HOME,
436 WORK,
437 CUSTOM;
438
439 static Type from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress.Type type) {
440 return switch (type) {
441 case HOME -> HOME;
442 case WORK -> WORK;
443 case CUSTOM -> CUSTOM;
444 };
445 }
446 }
447 }
448 }
449
450 public record Preview(String title, String description, long date, String url, Optional<Attachment> image) {
451
452 static Preview from(SignalServiceDataMessage.Preview preview) {
453 return new Preview(preview.getTitle(),
454 preview.getDescription(),
455 preview.getDate(),
456 preview.getUrl(),
457 Optional.ofNullable(preview.getImage().transform(Attachment::from).orNull()));
458 }
459 }
460 }
461
462 public record Sync(
463 Optional<Sent> sent,
464 Optional<Blocked> blocked,
465 List<Read> read,
466 List<Viewed> viewed,
467 Optional<ViewOnceOpen> viewOnceOpen,
468 Optional<Contacts> contacts,
469 Optional<Groups> groups,
470 Optional<MessageRequestResponse> messageRequestResponse
471 ) {
472
473 public static Sync from(
474 final SignalServiceSyncMessage syncMessage,
475 RecipientResolver recipientResolver,
476 RecipientAddressResolver addressResolver
477 ) {
478 return new Sync(Optional.ofNullable(syncMessage.getSent()
479 .transform(s -> Sent.from(s, recipientResolver, addressResolver))
480 .orNull()),
481 Optional.ofNullable(syncMessage.getBlockedList()
482 .transform(b -> Blocked.from(b, recipientResolver, addressResolver))
483 .orNull()),
484 syncMessage.getRead()
485 .transform(r -> r.stream()
486 .map(rm -> Read.from(rm, recipientResolver, addressResolver))
487 .collect(Collectors.toList()))
488 .or(List.of()),
489 syncMessage.getViewed()
490 .transform(r -> r.stream()
491 .map(rm -> Viewed.from(rm, recipientResolver, addressResolver))
492 .collect(Collectors.toList()))
493 .or(List.of()),
494 Optional.ofNullable(syncMessage.getViewOnceOpen()
495 .transform(rm -> ViewOnceOpen.from(rm, recipientResolver, addressResolver))
496 .orNull()),
497 Optional.ofNullable(syncMessage.getContacts().transform(Contacts::from).orNull()),
498 Optional.ofNullable(syncMessage.getGroups().transform(Groups::from).orNull()),
499 Optional.ofNullable(syncMessage.getMessageRequestResponse()
500 .transform(m -> MessageRequestResponse.from(m, recipientResolver, addressResolver))
501 .orNull()));
502 }
503
504 public record Sent(
505 long timestamp,
506 long expirationStartTimestamp,
507 Optional<RecipientAddress> destination,
508 Set<RecipientAddress> recipients,
509 Data message
510 ) {
511
512 static Sent from(
513 SentTranscriptMessage sentMessage,
514 RecipientResolver recipientResolver,
515 RecipientAddressResolver addressResolver
516 ) {
517 return new Sent(sentMessage.getTimestamp(),
518 sentMessage.getExpirationStartTimestamp(),
519 Optional.ofNullable(sentMessage.getDestination()
520 .transform(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
521 d)))
522 .orNull()),
523 sentMessage.getRecipients()
524 .stream()
525 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
526 .collect(Collectors.toSet()),
527 Data.from(sentMessage.getMessage(), recipientResolver, addressResolver));
528 }
529 }
530
531 public record Blocked(List<RecipientAddress> recipients, List<GroupId> groupIds) {
532
533 static Blocked from(
534 BlockedListMessage blockedListMessage,
535 RecipientResolver recipientResolver,
536 RecipientAddressResolver addressResolver
537 ) {
538 return new Blocked(blockedListMessage.getAddresses()
539 .stream()
540 .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
541 .collect(Collectors.toList()),
542 blockedListMessage.getGroupIds()
543 .stream()
544 .map(GroupId::unknownVersion)
545 .collect(Collectors.toList()));
546 }
547 }
548
549 public record Read(RecipientAddress sender, long timestamp) {
550
551 static Read from(
552 ReadMessage readMessage,
553 RecipientResolver recipientResolver,
554 RecipientAddressResolver addressResolver
555 ) {
556 return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
557 readMessage.getTimestamp());
558 }
559 }
560
561 public record Viewed(RecipientAddress sender, long timestamp) {
562
563 static Viewed from(
564 ViewedMessage readMessage,
565 RecipientResolver recipientResolver,
566 RecipientAddressResolver addressResolver
567 ) {
568 return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
569 readMessage.getTimestamp());
570 }
571 }
572
573 public record ViewOnceOpen(RecipientAddress sender, long timestamp) {
574
575 static ViewOnceOpen from(
576 ViewOnceOpenMessage readMessage,
577 RecipientResolver recipientResolver,
578 RecipientAddressResolver addressResolver
579 ) {
580 return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
581 readMessage.getSender())), readMessage.getTimestamp());
582 }
583 }
584
585 public record Contacts(boolean isComplete) {
586
587 static Contacts from(ContactsMessage contactsMessage) {
588 return new Contacts(contactsMessage.isComplete());
589 }
590 }
591
592 public record Groups() {
593
594 static Groups from(SignalServiceAttachment groupsMessage) {
595 return new Groups();
596 }
597 }
598
599 public record MessageRequestResponse(Type type, Optional<GroupId> groupId, Optional<RecipientAddress> person) {
600
601 static MessageRequestResponse from(
602 MessageRequestResponseMessage messageRequestResponse,
603 RecipientResolver recipientResolver,
604 RecipientAddressResolver addressResolver
605 ) {
606 return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
607 Optional.ofNullable(messageRequestResponse.getGroupId()
608 .transform(GroupId::unknownVersion)
609 .orNull()),
610 Optional.ofNullable(messageRequestResponse.getPerson()
611 .transform(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
612 p)))
613 .orNull()));
614 }
615
616 public enum Type {
617 UNKNOWN,
618 ACCEPT,
619 DELETE,
620 BLOCK,
621 BLOCK_AND_DELETE,
622 UNBLOCK_AND_ACCEPT;
623
624 static Type from(MessageRequestResponseMessage.Type type) {
625 return switch (type) {
626 case UNKNOWN -> UNKNOWN;
627 case ACCEPT -> ACCEPT;
628 case DELETE -> DELETE;
629 case BLOCK -> BLOCK;
630 case BLOCK_AND_DELETE -> BLOCK_AND_DELETE;
631 case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT;
632 };
633 }
634 }
635 }
636 }
637
638 public record Call(
639 Optional<Integer> destinationDeviceId,
640 Optional<GroupId> groupId,
641 Optional<Long> timestamp,
642 Optional<Offer> offer,
643 Optional<Answer> answer,
644 Optional<Hangup> hangup,
645 Optional<Busy> busy,
646 List<IceUpdate> iceUpdate,
647 Optional<Opaque> opaque
648 ) {
649
650 public static Call from(final SignalServiceCallMessage callMessage) {
651 return new Call(Optional.ofNullable(callMessage.getDestinationDeviceId().orNull()),
652 Optional.ofNullable(callMessage.getGroupId().transform(GroupId::unknownVersion).orNull()),
653 Optional.ofNullable(callMessage.getTimestamp().orNull()),
654 Optional.ofNullable(callMessage.getOfferMessage().transform(Offer::from).orNull()),
655 Optional.ofNullable(callMessage.getAnswerMessage().transform(Answer::from).orNull()),
656 Optional.ofNullable(callMessage.getHangupMessage().transform(Hangup::from).orNull()),
657 Optional.ofNullable(callMessage.getBusyMessage().transform(Busy::from).orNull()),
658 callMessage.getIceUpdateMessages()
659 .transform(m -> m.stream().map(IceUpdate::from).collect(Collectors.toList()))
660 .or(List.of()),
661 Optional.ofNullable(callMessage.getOpaqueMessage().transform(Opaque::from).orNull()));
662 }
663
664 public record Offer(long id, String sdp, Type type, byte[] opaque) {
665
666 static Offer from(OfferMessage offerMessage) {
667 return new Offer(offerMessage.getId(),
668 offerMessage.getSdp(),
669 Type.from(offerMessage.getType()),
670 offerMessage.getOpaque());
671 }
672
673 public enum Type {
674 AUDIO_CALL,
675 VIDEO_CALL;
676
677 static Type from(OfferMessage.Type type) {
678 return switch (type) {
679 case AUDIO_CALL -> AUDIO_CALL;
680 case VIDEO_CALL -> VIDEO_CALL;
681 };
682 }
683 }
684 }
685
686 public record Answer(long id, String sdp, byte[] opaque) {
687
688 static Answer from(AnswerMessage answerMessage) {
689 return new Answer(answerMessage.getId(), answerMessage.getSdp(), answerMessage.getOpaque());
690 }
691 }
692
693 public record Busy(long id) {
694
695 static Offer from(OfferMessage offerMessage) {
696 return new Offer(offerMessage.getId(),
697 offerMessage.getSdp(),
698 Offer.Type.from(offerMessage.getType()),
699 offerMessage.getOpaque());
700 }
701
702 static Busy from(BusyMessage busyMessage) {
703 return new Busy(busyMessage.getId());
704 }
705 }
706
707 public record Hangup(long id, Type type, int deviceId, boolean isLegacy) {
708
709 static Hangup from(HangupMessage hangupMessage) {
710 return new Hangup(hangupMessage.getId(),
711 Type.from(hangupMessage.getType()),
712 hangupMessage.getDeviceId(),
713 hangupMessage.isLegacy());
714 }
715
716 public enum Type {
717 NORMAL,
718 ACCEPTED,
719 DECLINED,
720 BUSY,
721 NEED_PERMISSION;
722
723 static Type from(HangupMessage.Type type) {
724 return switch (type) {
725 case NORMAL -> NORMAL;
726 case ACCEPTED -> ACCEPTED;
727 case DECLINED -> DECLINED;
728 case BUSY -> BUSY;
729 case NEED_PERMISSION -> NEED_PERMISSION;
730 };
731 }
732 }
733 }
734
735 public record IceUpdate(long id, String sdp, byte[] opaque) {
736
737 static IceUpdate from(IceUpdateMessage iceUpdateMessage) {
738 return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getSdp(), iceUpdateMessage.getOpaque());
739 }
740 }
741
742 public record Opaque(byte[] opaque, Urgency urgency) {
743
744 static Opaque from(OpaqueMessage opaqueMessage) {
745 return new Opaque(opaqueMessage.getOpaque(), Urgency.from(opaqueMessage.getUrgency()));
746 }
747
748 public enum Urgency {
749 DROPPABLE,
750 HANDLE_IMMEDIATELY;
751
752 static Urgency from(OpaqueMessage.Urgency urgency) {
753 return switch (urgency) {
754 case DROPPABLE -> DROPPABLE;
755 case HANDLE_IMMEDIATELY -> HANDLE_IMMEDIATELY;
756 };
757 }
758 }
759 }
760 }
761
762 public static MessageEnvelope from(
763 SignalServiceEnvelope envelope,
764 SignalServiceContent content,
765 RecipientResolver recipientResolver,
766 RecipientAddressResolver addressResolver
767 ) {
768 final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid()
769 ? recipientResolver.resolveRecipient(envelope.getSourceAddress())
770 : envelope.isUnidentifiedSender() && content != null
771 ? recipientResolver.resolveRecipient(content.getSender())
772 : null;
773 final var sourceDevice = envelope.hasSourceDevice()
774 ? envelope.getSourceDevice()
775 : content != null ? content.getSenderDevice() : 0;
776
777 Optional<Receipt> receipt;
778 Optional<Typing> typing;
779 Optional<Data> data;
780 Optional<Sync> sync;
781 Optional<Call> call;
782 if (content != null) {
783 receipt = Optional.ofNullable(content.getReceiptMessage().transform(Receipt::from).orNull());
784 typing = Optional.ofNullable(content.getTypingMessage().transform(Typing::from).orNull());
785 data = Optional.ofNullable(content.getDataMessage()
786 .transform(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver))
787 .orNull());
788 sync = Optional.ofNullable(content.getSyncMessage()
789 .transform(s -> Sync.from(s, recipientResolver, addressResolver))
790 .orNull());
791 call = Optional.ofNullable(content.getCallMessage().transform(Call::from).orNull());
792 } else {
793 receipt = Optional.empty();
794 typing = Optional.empty();
795 data = Optional.empty();
796 sync = Optional.empty();
797 call = Optional.empty();
798 }
799
800 return new MessageEnvelope(source == null
801 ? Optional.empty()
802 : Optional.of(addressResolver.resolveRecipientAddress(source)),
803 sourceDevice,
804 envelope.getTimestamp(),
805 envelope.getServerReceivedTimestamp(),
806 envelope.getServerDeliveredTimestamp(),
807 envelope.isUnidentifiedSender(),
808 receipt,
809 typing,
810 data,
811 sync,
812 call);
813 }
814 }