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