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