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