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