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