]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
Refactor receive api
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / IncomingMessageHandler.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.JobExecutor;
4 import org.asamk.signal.manager.Manager;
5 import org.asamk.signal.manager.SignalDependencies;
6 import org.asamk.signal.manager.TrustLevel;
7 import org.asamk.signal.manager.UntrustedIdentityException;
8 import org.asamk.signal.manager.actions.HandleAction;
9 import org.asamk.signal.manager.actions.RefreshPreKeysAction;
10 import org.asamk.signal.manager.actions.RenewSessionAction;
11 import org.asamk.signal.manager.actions.RetrieveProfileAction;
12 import org.asamk.signal.manager.actions.RetrieveStorageDataAction;
13 import org.asamk.signal.manager.actions.SendGroupInfoAction;
14 import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
15 import org.asamk.signal.manager.actions.SendReceiptAction;
16 import org.asamk.signal.manager.actions.SendRetryMessageRequestAction;
17 import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
18 import org.asamk.signal.manager.actions.SendSyncConfigurationAction;
19 import org.asamk.signal.manager.actions.SendSyncContactsAction;
20 import org.asamk.signal.manager.actions.SendSyncGroupsAction;
21 import org.asamk.signal.manager.actions.SendSyncKeysAction;
22 import org.asamk.signal.manager.api.MessageEnvelope;
23 import org.asamk.signal.manager.api.Pair;
24 import org.asamk.signal.manager.groups.GroupId;
25 import org.asamk.signal.manager.groups.GroupNotFoundException;
26 import org.asamk.signal.manager.groups.GroupUtils;
27 import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
28 import org.asamk.signal.manager.storage.SignalAccount;
29 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
30 import org.asamk.signal.manager.storage.recipients.Profile;
31 import org.asamk.signal.manager.storage.recipients.RecipientId;
32 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
33 import org.asamk.signal.manager.storage.stickers.Sticker;
34 import org.asamk.signal.manager.storage.stickers.StickerPackId;
35 import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
36 import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
37 import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
38 import org.signal.libsignal.metadata.ProtocolNoSessionException;
39 import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
40 import org.signal.libsignal.metadata.SelfSendException;
41 import org.signal.zkgroup.InvalidInputException;
42 import org.signal.zkgroup.profiles.ProfileKey;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45 import org.whispersystems.libsignal.SignalProtocolAddress;
46 import org.whispersystems.signalservice.api.messages.SignalServiceContent;
47 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
48 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
49 import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
50 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
51 import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
52 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
53
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.stream.Collectors;
57
58 public final class IncomingMessageHandler {
59
60 private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
61
62 private final SignalAccount account;
63 private final SignalDependencies dependencies;
64 private final RecipientResolver recipientResolver;
65 private final SignalServiceAddressResolver addressResolver;
66 private final GroupHelper groupHelper;
67 private final ContactHelper contactHelper;
68 private final AttachmentHelper attachmentHelper;
69 private final SyncHelper syncHelper;
70 private final ProfileProvider profileProvider;
71 private final JobExecutor jobExecutor;
72
73 public IncomingMessageHandler(
74 final SignalAccount account,
75 final SignalDependencies dependencies,
76 final RecipientResolver recipientResolver,
77 final SignalServiceAddressResolver addressResolver,
78 final GroupHelper groupHelper,
79 final ContactHelper contactHelper,
80 final AttachmentHelper attachmentHelper,
81 final SyncHelper syncHelper,
82 final ProfileProvider profileProvider,
83 final JobExecutor jobExecutor
84 ) {
85 this.account = account;
86 this.dependencies = dependencies;
87 this.recipientResolver = recipientResolver;
88 this.addressResolver = addressResolver;
89 this.groupHelper = groupHelper;
90 this.contactHelper = contactHelper;
91 this.attachmentHelper = attachmentHelper;
92 this.syncHelper = syncHelper;
93 this.profileProvider = profileProvider;
94 this.jobExecutor = jobExecutor;
95 }
96
97 public Pair<List<HandleAction>, Exception> handleRetryEnvelope(
98 final SignalServiceEnvelope envelope,
99 final boolean ignoreAttachments,
100 final Manager.ReceiveMessageHandler handler
101 ) {
102 final List<HandleAction> actions = new ArrayList<>();
103 if (envelope.isPreKeySignalMessage()) {
104 actions.add(RefreshPreKeysAction.create());
105 }
106
107 SignalServiceContent content = null;
108 if (!envelope.isReceipt()) {
109 try {
110 content = dependencies.getCipher().decrypt(envelope);
111 } catch (ProtocolUntrustedIdentityException e) {
112 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
113 final var exception = new UntrustedIdentityException(account.getRecipientStore()
114 .resolveRecipientAddress(recipientId), e.getSenderDevice());
115 return new Pair<>(List.of(), exception);
116 } catch (Exception e) {
117 return new Pair<>(List.of(), e);
118 }
119 }
120 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, null));
121 return new Pair<>(actions, null);
122 }
123
124 public Pair<List<HandleAction>, Exception> handleEnvelope(
125 final SignalServiceEnvelope envelope,
126 final boolean ignoreAttachments,
127 final Manager.ReceiveMessageHandler handler
128 ) {
129 final var actions = new ArrayList<HandleAction>();
130 if (envelope.hasSourceUuid()) {
131 // Store uuid if we don't have it already
132 // address/uuid in envelope is sent by server
133 account.getRecipientStore().resolveRecipientTrusted(envelope.getSourceAddress());
134 }
135 SignalServiceContent content = null;
136 Exception exception = null;
137 if (!envelope.isReceipt()) {
138 try {
139 content = dependencies.getCipher().decrypt(envelope);
140 } catch (ProtocolUntrustedIdentityException e) {
141 final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
142 actions.add(new RetrieveProfileAction(recipientId));
143 exception = new UntrustedIdentityException(account.getRecipientStore()
144 .resolveRecipientAddress(recipientId), e.getSenderDevice());
145 } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) {
146 final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
147 final var senderProfile = profileProvider.getProfile(sender);
148 final var selfProfile = profileProvider.getProfile(account.getSelfRecipientId());
149 if (e.getSenderDevice() != account.getDeviceId()
150 && senderProfile != null
151 && senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
152 && selfProfile != null
153 && selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
154 logger.debug("Received invalid message, requesting message resend.");
155 actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
156 } else {
157 logger.debug("Received invalid message, queuing renew session action.");
158 actions.add(new RenewSessionAction(sender));
159 }
160 exception = e;
161 } catch (SelfSendException e) {
162 logger.debug("Dropping unidentified message from self.");
163 return new Pair<>(List.of(), null);
164 } catch (Exception e) {
165 exception = e;
166 }
167 }
168
169 actions.addAll(checkAndHandleMessage(envelope, content, ignoreAttachments, handler, exception));
170 return new Pair<>(actions, exception);
171 }
172
173 private List<HandleAction> checkAndHandleMessage(
174 final SignalServiceEnvelope envelope,
175 final SignalServiceContent content,
176 final boolean ignoreAttachments,
177 final Manager.ReceiveMessageHandler handler,
178 final Exception exception
179 ) {
180 if (!envelope.hasSourceUuid() && content != null) {
181 // Store uuid if we don't have it already
182 // address/uuid is validated by unidentified sender certificate
183 account.getRecipientStore().resolveRecipientTrusted(content.getSender());
184 }
185 if (isMessageBlocked(envelope, content)) {
186 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
187 return List.of();
188 } else if (isNotAllowedToSendToGroup(envelope, content)) {
189 logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
190 (envelope.hasSourceUuid() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
191 envelope.getTimestamp());
192 return List.of();
193 } else {
194 List<HandleAction> actions;
195 if (content != null) {
196 actions = handleMessage(envelope, content, ignoreAttachments);
197 } else {
198 actions = List.of();
199 }
200 handler.handleMessage(MessageEnvelope.from(envelope,
201 content,
202 recipientResolver,
203 account.getRecipientStore()::resolveRecipientAddress), exception);
204 return actions;
205 }
206 }
207
208 public List<HandleAction> handleMessage(
209 SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
210 ) {
211 var actions = new ArrayList<HandleAction>();
212 final RecipientId sender;
213 final int senderDeviceId;
214 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
215 sender = recipientResolver.resolveRecipient(envelope.getSourceAddress());
216 senderDeviceId = envelope.getSourceDevice();
217 } else {
218 sender = recipientResolver.resolveRecipient(content.getSender());
219 senderDeviceId = content.getSenderDevice();
220 }
221
222 if (content.getSenderKeyDistributionMessage().isPresent()) {
223 final var message = content.getSenderKeyDistributionMessage().get();
224 final var protocolAddress = new SignalProtocolAddress(addressResolver.resolveSignalServiceAddress(sender)
225 .getIdentifier(), senderDeviceId);
226 logger.debug("Received a sender key distribution message for distributionId {} from {}",
227 message.getDistributionId(),
228 protocolAddress);
229 dependencies.getMessageSender().processSenderKeyDistributionMessage(protocolAddress, message);
230 }
231
232 if (content.getDecryptionErrorMessage().isPresent()) {
233 var message = content.getDecryptionErrorMessage().get();
234 logger.debug("Received a decryption error message (resend request for {})", message.getTimestamp());
235 }
236
237 if (content.getDataMessage().isPresent()) {
238 var message = content.getDataMessage().get();
239
240 if (content.isNeedsReceipt()) {
241 actions.add(new SendReceiptAction(sender, message.getTimestamp()));
242 }
243
244 actions.addAll(handleSignalServiceDataMessage(message,
245 false,
246 sender,
247 account.getSelfRecipientId(),
248 ignoreAttachments));
249 }
250
251 if (content.getSyncMessage().isPresent()) {
252 var syncMessage = content.getSyncMessage().get();
253 actions.addAll(handleSyncMessage(syncMessage, sender, ignoreAttachments));
254 }
255
256 return actions;
257 }
258
259 private List<HandleAction> handleSyncMessage(
260 final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
261 ) {
262 var actions = new ArrayList<HandleAction>();
263 account.setMultiDevice(true);
264 if (syncMessage.getSent().isPresent()) {
265 var message = syncMessage.getSent().get();
266 final var destination = message.getDestination().orNull();
267 actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
268 true,
269 sender,
270 destination == null ? null : recipientResolver.resolveRecipient(destination),
271 ignoreAttachments));
272 }
273 if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
274 var rm = syncMessage.getRequest().get();
275 if (rm.isContactsRequest()) {
276 actions.add(SendSyncContactsAction.create());
277 }
278 if (rm.isGroupsRequest()) {
279 actions.add(SendSyncGroupsAction.create());
280 }
281 if (rm.isBlockedListRequest()) {
282 actions.add(SendSyncBlockedListAction.create());
283 }
284 if (rm.isKeysRequest()) {
285 actions.add(SendSyncKeysAction.create());
286 }
287 if (rm.isConfigurationRequest()) {
288 actions.add(SendSyncConfigurationAction.create());
289 }
290 }
291 if (syncMessage.getGroups().isPresent()) {
292 logger.warn("Received a group v1 sync message, that can't be handled anymore, ignoring.");
293 }
294 if (syncMessage.getBlockedList().isPresent()) {
295 final var blockedListMessage = syncMessage.getBlockedList().get();
296 for (var address : blockedListMessage.getAddresses()) {
297 contactHelper.setContactBlocked(recipientResolver.resolveRecipient(address), true);
298 }
299 for (var groupId : blockedListMessage.getGroupIds()
300 .stream()
301 .map(GroupId::unknownVersion)
302 .collect(Collectors.toSet())) {
303 try {
304 groupHelper.setGroupBlocked(groupId, true);
305 } catch (GroupNotFoundException e) {
306 logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
307 groupId.toBase64());
308 }
309 }
310 }
311 if (syncMessage.getContacts().isPresent()) {
312 try {
313 final var contactsMessage = syncMessage.getContacts().get();
314 attachmentHelper.retrieveAttachment(contactsMessage.getContactsStream(),
315 syncHelper::handleSyncDeviceContacts);
316 } catch (Exception e) {
317 logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
318 }
319 }
320 if (syncMessage.getVerified().isPresent()) {
321 final var verifiedMessage = syncMessage.getVerified().get();
322 account.getIdentityKeyStore()
323 .setIdentityTrustLevel(account.getRecipientStore()
324 .resolveRecipientTrusted(verifiedMessage.getDestination()),
325 verifiedMessage.getIdentityKey(),
326 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
327 }
328 if (syncMessage.getStickerPackOperations().isPresent()) {
329 final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
330 for (var m : stickerPackOperationMessages) {
331 if (!m.getPackId().isPresent()) {
332 continue;
333 }
334 final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
335 final var installed = !m.getType().isPresent()
336 || m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
337
338 var sticker = account.getStickerStore().getSticker(stickerPackId);
339 if (m.getPackKey().isPresent()) {
340 if (sticker == null) {
341 sticker = new Sticker(stickerPackId, m.getPackKey().get());
342 }
343 if (installed) {
344 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
345 }
346 }
347
348 if (sticker != null) {
349 sticker.setInstalled(installed);
350 account.getStickerStore().updateSticker(sticker);
351 }
352 }
353 }
354 if (syncMessage.getFetchType().isPresent()) {
355 switch (syncMessage.getFetchType().get()) {
356 case LOCAL_PROFILE:
357 actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
358 case STORAGE_MANIFEST:
359 actions.add(RetrieveStorageDataAction.create());
360 }
361 }
362 if (syncMessage.getKeys().isPresent()) {
363 final var keysMessage = syncMessage.getKeys().get();
364 if (keysMessage.getStorageService().isPresent()) {
365 final var storageKey = keysMessage.getStorageService().get();
366 account.setStorageKey(storageKey);
367 actions.add(RetrieveStorageDataAction.create());
368 }
369 }
370 if (syncMessage.getConfiguration().isPresent()) {
371 final var configurationMessage = syncMessage.getConfiguration().get();
372 final var configurationStore = account.getConfigurationStore();
373 if (configurationMessage.getReadReceipts().isPresent()) {
374 configurationStore.setReadReceipts(configurationMessage.getReadReceipts().get());
375 }
376 if (configurationMessage.getLinkPreviews().isPresent()) {
377 configurationStore.setLinkPreviews(configurationMessage.getLinkPreviews().get());
378 }
379 if (configurationMessage.getTypingIndicators().isPresent()) {
380 configurationStore.setTypingIndicators(configurationMessage.getTypingIndicators().get());
381 }
382 if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) {
383 configurationStore.setUnidentifiedDeliveryIndicators(configurationMessage.getUnidentifiedDeliveryIndicators()
384 .get());
385 }
386 }
387 return actions;
388 }
389
390 private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
391 SignalServiceAddress source;
392 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
393 source = envelope.getSourceAddress();
394 } else if (content != null) {
395 source = content.getSender();
396 } else {
397 return false;
398 }
399 final var recipientId = recipientResolver.resolveRecipient(source);
400 if (contactHelper.isContactBlocked(recipientId)) {
401 return true;
402 }
403
404 if (content != null && content.getDataMessage().isPresent()) {
405 var message = content.getDataMessage().get();
406 if (message.getGroupContext().isPresent()) {
407 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
408 return groupHelper.isGroupBlocked(groupId);
409 }
410 }
411
412 return false;
413 }
414
415 private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
416 SignalServiceAddress source;
417 if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
418 source = envelope.getSourceAddress();
419 } else if (content != null) {
420 source = content.getSender();
421 } else {
422 return false;
423 }
424
425 if (content == null || !content.getDataMessage().isPresent()) {
426 return false;
427 }
428
429 var message = content.getDataMessage().get();
430 if (!message.getGroupContext().isPresent()) {
431 return false;
432 }
433
434 if (message.getGroupContext().get().getGroupV1().isPresent()) {
435 var groupInfo = message.getGroupContext().get().getGroupV1().get();
436 if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
437 return false;
438 }
439 }
440
441 var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
442 var group = groupHelper.getGroup(groupId);
443 if (group == null) {
444 return false;
445 }
446
447 final var recipientId = recipientResolver.resolveRecipient(source);
448 if (!group.isMember(recipientId) && !(group.isPendingMember(recipientId) && message.isGroupV2Update())) {
449 return true;
450 }
451
452 if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
453 return message.getBody().isPresent()
454 || message.getAttachments().isPresent()
455 || message.getQuote()
456 .isPresent()
457 || message.getPreviews().isPresent()
458 || message.getMentions().isPresent()
459 || message.getSticker().isPresent();
460 }
461 return false;
462 }
463
464 private List<HandleAction> handleSignalServiceDataMessage(
465 SignalServiceDataMessage message,
466 boolean isSync,
467 RecipientId source,
468 RecipientId destination,
469 boolean ignoreAttachments
470 ) {
471 var actions = new ArrayList<HandleAction>();
472 if (message.getGroupContext().isPresent()) {
473 if (message.getGroupContext().get().getGroupV1().isPresent()) {
474 var groupInfo = message.getGroupContext().get().getGroupV1().get();
475 var groupId = GroupId.v1(groupInfo.getGroupId());
476 var group = groupHelper.getGroup(groupId);
477 if (group == null || group instanceof GroupInfoV1) {
478 var groupV1 = (GroupInfoV1) group;
479 switch (groupInfo.getType()) {
480 case UPDATE: {
481 if (groupV1 == null) {
482 groupV1 = new GroupInfoV1(groupId);
483 }
484
485 if (groupInfo.getAvatar().isPresent()) {
486 var avatar = groupInfo.getAvatar().get();
487 groupHelper.downloadGroupAvatar(groupV1.getGroupId(), avatar);
488 }
489
490 if (groupInfo.getName().isPresent()) {
491 groupV1.name = groupInfo.getName().get();
492 }
493
494 if (groupInfo.getMembers().isPresent()) {
495 groupV1.addMembers(groupInfo.getMembers()
496 .get()
497 .stream()
498 .map(recipientResolver::resolveRecipient)
499 .collect(Collectors.toSet()));
500 }
501
502 account.getGroupStore().updateGroup(groupV1);
503 break;
504 }
505 case DELIVER:
506 if (groupV1 == null && !isSync) {
507 actions.add(new SendGroupInfoRequestAction(source, groupId));
508 }
509 break;
510 case QUIT: {
511 if (groupV1 != null) {
512 groupV1.removeMember(source);
513 account.getGroupStore().updateGroup(groupV1);
514 }
515 break;
516 }
517 case REQUEST_INFO:
518 if (groupV1 != null && !isSync) {
519 actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
520 }
521 break;
522 }
523 } else {
524 // Received a group v1 message for a v2 group
525 }
526 }
527 if (message.getGroupContext().get().getGroupV2().isPresent()) {
528 final var groupContext = message.getGroupContext().get().getGroupV2().get();
529 final var groupMasterKey = groupContext.getMasterKey();
530
531 groupHelper.getOrMigrateGroup(groupMasterKey,
532 groupContext.getRevision(),
533 groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
534 }
535 }
536
537 final var conversationPartnerAddress = isSync ? destination : source;
538 if (conversationPartnerAddress != null && message.isEndSession()) {
539 account.getSessionStore().deleteAllSessions(conversationPartnerAddress);
540 }
541 if (message.isExpirationUpdate() || message.getBody().isPresent()) {
542 if (message.getGroupContext().isPresent()) {
543 if (message.getGroupContext().get().getGroupV1().isPresent()) {
544 var groupInfo = message.getGroupContext().get().getGroupV1().get();
545 var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
546 if (group != null) {
547 if (group.messageExpirationTime != message.getExpiresInSeconds()) {
548 group.messageExpirationTime = message.getExpiresInSeconds();
549 account.getGroupStore().updateGroup(group);
550 }
551 }
552 } else if (message.getGroupContext().get().getGroupV2().isPresent()) {
553 // disappearing message timer already stored in the DecryptedGroup
554 }
555 } else if (conversationPartnerAddress != null) {
556 contactHelper.setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
557 }
558 }
559 if (!ignoreAttachments) {
560 if (message.getAttachments().isPresent()) {
561 for (var attachment : message.getAttachments().get()) {
562 attachmentHelper.downloadAttachment(attachment);
563 }
564 }
565 if (message.getSharedContacts().isPresent()) {
566 for (var contact : message.getSharedContacts().get()) {
567 if (contact.getAvatar().isPresent()) {
568 attachmentHelper.downloadAttachment(contact.getAvatar().get().getAttachment());
569 }
570 }
571 }
572 if (message.getPreviews().isPresent()) {
573 final var previews = message.getPreviews().get();
574 for (var preview : previews) {
575 if (preview.getImage().isPresent()) {
576 attachmentHelper.downloadAttachment(preview.getImage().get());
577 }
578 }
579 }
580 if (message.getQuote().isPresent()) {
581 final var quote = message.getQuote().get();
582
583 for (var quotedAttachment : quote.getAttachments()) {
584 final var thumbnail = quotedAttachment.getThumbnail();
585 if (thumbnail != null) {
586 attachmentHelper.downloadAttachment(thumbnail);
587 }
588 }
589 }
590 }
591 if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
592 final ProfileKey profileKey;
593 try {
594 profileKey = new ProfileKey(message.getProfileKey().get());
595 } catch (InvalidInputException e) {
596 throw new AssertionError(e);
597 }
598 if (account.getSelfRecipientId().equals(source)) {
599 this.account.setProfileKey(profileKey);
600 }
601 this.account.getProfileStore().storeProfileKey(source, profileKey);
602 }
603 if (message.getSticker().isPresent()) {
604 final var messageSticker = message.getSticker().get();
605 final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
606 var sticker = account.getStickerStore().getSticker(stickerPackId);
607 if (sticker == null) {
608 sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
609 account.getStickerStore().updateSticker(sticker);
610 }
611 jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
612 }
613 return actions;
614 }
615 }