]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/ReceiveMessageHandler.java
c52700304250fdaaed5d3e5f00b71da5699daeb4
[signal-cli] / src / main / java / org / asamk / signal / ReceiveMessageHandler.java
1 package org.asamk.signal;
2
3 import org.asamk.signal.manager.Manager;
4 import org.asamk.signal.manager.api.MessageEnvelope;
5 import org.asamk.signal.manager.api.RecipientAddress;
6 import org.asamk.signal.manager.api.RecipientIdentifier;
7 import org.asamk.signal.manager.api.UntrustedIdentityException;
8 import org.asamk.signal.manager.groups.GroupId;
9 import org.asamk.signal.output.PlainTextWriter;
10 import org.asamk.signal.util.DateUtils;
11 import org.asamk.signal.util.Hex;
12 import org.slf4j.helpers.MessageFormatter;
13
14 import java.util.ArrayList;
15 import java.util.stream.Collectors;
16
17 public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
18
19 final Manager m;
20 final PlainTextWriter writer;
21
22 public ReceiveMessageHandler(Manager m, final PlainTextWriter writer) {
23 this.m = m;
24 this.writer = writer;
25 }
26
27 @Override
28 public void handleMessage(MessageEnvelope envelope, Throwable exception) {
29 synchronized (writer) {
30 handleMessageInternal(envelope, exception);
31 }
32 }
33
34 private void handleMessageInternal(MessageEnvelope envelope, Throwable exception) {
35 var source = envelope.sourceAddress();
36 writer.println("Envelope from: {} (device: {}) to {}",
37 source.map(this::formatContact).orElse("unknown source"),
38 envelope.sourceDevice(),
39 m.getSelfNumber());
40 writer.println("Timestamp: {}", DateUtils.formatTimestamp(envelope.timestamp()));
41 writer.println("Server timestamps: received: {} delivered: {}",
42 DateUtils.formatTimestamp(envelope.serverReceivedTimestamp()),
43 DateUtils.formatTimestamp(envelope.serverDeliveredTimestamp()));
44 if (envelope.isUnidentifiedSender()) {
45 writer.println("Sent by unidentified/sealed sender");
46 }
47
48 if (exception != null) {
49 if (exception instanceof UntrustedIdentityException e) {
50 writer.println(
51 "The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
52 final var recipientName = e.getSender().getLegacyIdentifier();
53 writer.println(
54 "Use 'signal-cli -a {} listIdentities -n {}', verify the key and run 'signal-cli -a {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
55 m.getSelfNumber(),
56 recipientName,
57 m.getSelfNumber(),
58 recipientName);
59 writer.println(
60 "If you don't care about security, use 'signal-cli -a {} trust -a {}' to trust it without verification",
61 m.getSelfNumber(),
62 recipientName);
63 } else {
64 writer.println("Exception: {} ({})", exception.getMessage(), exception.getClass().getSimpleName());
65 }
66 }
67
68 if (envelope.data().isPresent()) {
69 var message = envelope.data().get();
70 printDataMessage(writer, message);
71 }
72 if (envelope.story().isPresent()) {
73 var message = envelope.story().get();
74 printStoryMessage(writer.indentedWriter(), message);
75 }
76 if (envelope.sync().isPresent()) {
77 writer.println("Received a sync message");
78 var syncMessage = envelope.sync().get();
79 printSyncMessage(writer, syncMessage);
80 }
81 if (envelope.call().isPresent()) {
82 writer.println("Received a call message");
83 var callMessage = envelope.call().get();
84 printCallMessage(writer.indentedWriter(), callMessage);
85 }
86 if (envelope.receipt().isPresent()) {
87 writer.println("Received a receipt message");
88 var receiptMessage = envelope.receipt().get();
89 printReceiptMessage(writer.indentedWriter(), receiptMessage);
90 }
91 if (envelope.typing().isPresent()) {
92 writer.println("Received a typing message");
93 var typingMessage = envelope.typing().get();
94 printTypingMessage(writer.indentedWriter(), typingMessage);
95 }
96 writer.println();
97 }
98
99 private void printDataMessage(
100 PlainTextWriter writer, MessageEnvelope.Data message
101 ) {
102 writer.println("Message timestamp: {}", DateUtils.formatTimestamp(message.timestamp()));
103 if (message.isViewOnce()) {
104 writer.println("=VIEW ONCE=");
105 }
106
107 if (message.body().isPresent()) {
108 writer.println("Body: {}", message.body().get());
109 }
110 if (message.groupContext().isPresent()) {
111 writer.println("Group info:");
112 final var groupContext = message.groupContext().get();
113 printGroupContext(writer.indentedWriter(), groupContext);
114 }
115 if (message.storyContext().isPresent()) {
116 writer.println("Story reply:");
117 final var storyContext = message.storyContext().get();
118 printStoryContext(writer.indentedWriter(), storyContext);
119 }
120 if (message.groupCallUpdate().isPresent()) {
121 writer.println("Group call update:");
122 final var groupCallUpdate = message.groupCallUpdate().get();
123 writer.indentedWriter().println("Era id: {}", groupCallUpdate.eraId());
124 }
125 if (message.previews().size() > 0) {
126 writer.println("Previews:");
127 final var previews = message.previews();
128 for (var preview : previews) {
129 writer.println("- Preview");
130 printPreview(writer.indentedWriter(), preview);
131 }
132 }
133 if (message.sharedContacts().size() > 0) {
134 writer.println("Contacts:");
135 for (var contact : message.sharedContacts()) {
136 writer.println("- Contact:");
137 printSharedContact(writer.indentedWriter(), contact);
138 }
139 }
140 if (message.sticker().isPresent()) {
141 final var sticker = message.sticker().get();
142 writer.println("Sticker:");
143 printSticker(writer.indentedWriter(), sticker);
144 }
145 if (message.isEndSession()) {
146 writer.println("Is end session");
147 }
148 if (message.isExpirationUpdate()) {
149 writer.println("Is Expiration update: true");
150 }
151 if (message.expiresInSeconds() > 0) {
152 writer.println("Expires in: {} seconds", message.expiresInSeconds());
153 }
154 if (message.isProfileKeyUpdate()) {
155 writer.println("Profile key update");
156 }
157 if (message.hasProfileKey()) {
158 writer.println("With profile key");
159 }
160 if (message.reaction().isPresent()) {
161 writer.println("Reaction:");
162 final var reaction = message.reaction().get();
163 printReaction(writer.indentedWriter(), reaction);
164 }
165 if (message.quote().isPresent()) {
166 writer.println("Quote:");
167 var quote = message.quote().get();
168 printQuote(writer.indentedWriter(), quote);
169 }
170 if (message.remoteDeleteId().isPresent()) {
171 final var remoteDelete = message.remoteDeleteId().get();
172 writer.println("Remote delete message: timestamp = {}", remoteDelete);
173 }
174 if (message.mentions().size() > 0) {
175 writer.println("Mentions:");
176 for (var mention : message.mentions()) {
177 printMention(writer, mention);
178 }
179 }
180 if (message.textStyles().size() > 0) {
181 writer.println("Text styles:");
182 for (var textStyle : message.textStyles()) {
183 printTextStyle(writer, textStyle);
184 }
185 }
186 if (message.attachments().size() > 0) {
187 writer.println("Attachments:");
188 for (var attachment : message.attachments()) {
189 writer.println("- Attachment:");
190 printAttachment(writer.indentedWriter(), attachment);
191 }
192 }
193 }
194
195 private void printStoryMessage(
196 PlainTextWriter writer, MessageEnvelope.Story message
197 ) {
198 writer.println("Story: with replies: {}", message.allowsReplies());
199 if (message.groupId().isPresent()) {
200 writer.println("Group info:");
201 printGroupInfo(writer.indentedWriter(), message.groupId().get());
202 }
203 if (message.textAttachment().isPresent()) {
204 writer.println("Body: {}", message.textAttachment().get().text().orElse(""));
205
206 if (message.textAttachment().get().preview().isPresent()) {
207 writer.println("Preview:");
208 printPreview(writer.indentedWriter(), message.textAttachment().get().preview().get());
209 }
210 }
211 if (message.fileAttachment().isPresent()) {
212 writer.println("Attachments:");
213 printAttachment(writer.indentedWriter(), message.fileAttachment().get());
214 }
215 }
216
217 private void printTypingMessage(
218 final PlainTextWriter writer, final MessageEnvelope.Typing typingMessage
219 ) {
220 writer.println("Action: {}", typingMessage.type());
221 writer.println("Timestamp: {}", DateUtils.formatTimestamp(typingMessage.timestamp()));
222 if (typingMessage.groupId().isPresent()) {
223 writer.println("Group Info:");
224 final var groupId = typingMessage.groupId().get();
225 printGroupInfo(writer.indentedWriter(), groupId);
226 }
227 }
228
229 private void printReceiptMessage(
230 final PlainTextWriter writer, final MessageEnvelope.Receipt receiptMessage
231 ) {
232 writer.println("When: {}", DateUtils.formatTimestamp(receiptMessage.when()));
233 if (receiptMessage.type() == MessageEnvelope.Receipt.Type.DELIVERY) {
234 writer.println("Is delivery receipt");
235 }
236 if (receiptMessage.type() == MessageEnvelope.Receipt.Type.READ) {
237 writer.println("Is read receipt");
238 }
239 if (receiptMessage.type() == MessageEnvelope.Receipt.Type.VIEWED) {
240 writer.println("Is viewed receipt");
241 }
242 writer.println("Timestamps:");
243 for (long timestamp : receiptMessage.timestamps()) {
244 writer.println("- {}", DateUtils.formatTimestamp(timestamp));
245 }
246 }
247
248 private void printCallMessage(
249 final PlainTextWriter writer, final MessageEnvelope.Call callMessage
250 ) {
251 if (callMessage.destinationDeviceId().isPresent()) {
252 final var deviceId = callMessage.destinationDeviceId().get();
253 writer.println("Destination device id: {}", deviceId);
254 }
255 if (callMessage.groupId().isPresent()) {
256 final var groupId = callMessage.groupId().get();
257 writer.println("Destination group id: {}", groupId);
258 }
259 if (callMessage.timestamp().isPresent()) {
260 writer.println("Timestamp: {}", DateUtils.formatTimestamp(callMessage.timestamp().get()));
261 }
262 if (callMessage.answer().isPresent()) {
263 var answerMessage = callMessage.answer().get();
264 writer.println("Answer message: {}, sdp: {})", answerMessage.id(), answerMessage.sdp());
265 }
266 if (callMessage.busy().isPresent()) {
267 var busyMessage = callMessage.busy().get();
268 writer.println("Busy message: {}", busyMessage.id());
269 }
270 if (callMessage.hangup().isPresent()) {
271 var hangupMessage = callMessage.hangup().get();
272 writer.println("Hangup message: {}", hangupMessage.id());
273 }
274 if (callMessage.iceUpdate().size() > 0) {
275 writer.println("Ice update messages:");
276 var iceUpdateMessages = callMessage.iceUpdate();
277 for (var iceUpdateMessage : iceUpdateMessages) {
278 writer.println("- {}, sdp: {}", iceUpdateMessage.id(), iceUpdateMessage.sdp());
279 }
280 }
281 if (callMessage.offer().isPresent()) {
282 var offerMessage = callMessage.offer().get();
283 writer.println("Offer message: {}, sdp: {}", offerMessage.id(), offerMessage.sdp());
284 }
285 if (callMessage.opaque().isPresent()) {
286 final var opaqueMessage = callMessage.opaque().get();
287 writer.println("Opaque message: size {}, urgency: {}",
288 opaqueMessage.opaque().length,
289 opaqueMessage.urgency().name());
290 }
291 }
292
293 private void printSyncMessage(
294 final PlainTextWriter writer, final MessageEnvelope.Sync syncMessage
295 ) {
296 if (syncMessage.contacts().isPresent()) {
297 final var contactsMessage = syncMessage.contacts().get();
298 var type = contactsMessage.isComplete() ? "complete" : "partial";
299 writer.println("Received {} sync contacts:", type);
300 }
301 if (syncMessage.groups().isPresent()) {
302 writer.println("Received sync groups.");
303 }
304 if (syncMessage.read().size() > 0) {
305 writer.println("Received sync read messages list");
306 for (var rm : syncMessage.read()) {
307 writer.println("- From: {} Message timestamp: {}",
308 formatContact(rm.sender()),
309 DateUtils.formatTimestamp(rm.timestamp()));
310 }
311 }
312 if (syncMessage.viewed().size() > 0) {
313 writer.println("Received sync viewed messages list");
314 for (var vm : syncMessage.viewed()) {
315 writer.println("- From: {} Message timestamp: {}",
316 formatContact(vm.sender()),
317 DateUtils.formatTimestamp(vm.timestamp()));
318 }
319 }
320 if (syncMessage.sent().isPresent()) {
321 writer.println("Received sync sent message");
322 final var sentTranscriptMessage = syncMessage.sent().get();
323 String to;
324 if (sentTranscriptMessage.destination().isPresent()) {
325 to = formatContact(sentTranscriptMessage.destination().get());
326 } else if (sentTranscriptMessage.recipients().size() > 0) {
327 to = sentTranscriptMessage.recipients()
328 .stream()
329 .map(this::formatContact)
330 .collect(Collectors.joining(", "));
331 } else {
332 to = "<unknown>";
333 }
334 writer.indentedWriter().println("To: {}", to);
335 writer.indentedWriter()
336 .println("Timestamp: {}", DateUtils.formatTimestamp(sentTranscriptMessage.timestamp()));
337 if (sentTranscriptMessage.expirationStartTimestamp() > 0) {
338 writer.indentedWriter()
339 .println("Expiration started at: {}",
340 DateUtils.formatTimestamp(sentTranscriptMessage.expirationStartTimestamp()));
341 }
342 if (sentTranscriptMessage.message().isPresent()) {
343 var message = sentTranscriptMessage.message().get();
344 printDataMessage(writer.indentedWriter(), message);
345 }
346 if (sentTranscriptMessage.story().isPresent()) {
347 var message = sentTranscriptMessage.story().get();
348 printStoryMessage(writer.indentedWriter(), message);
349 }
350 }
351 if (syncMessage.blocked().isPresent()) {
352 writer.println("Received sync message with block list");
353 writer.println("Blocked:");
354 final var blockedList = syncMessage.blocked().get();
355 for (var address : blockedList.recipients()) {
356 writer.println("- {}", address.getLegacyIdentifier());
357 }
358 for (var groupId : blockedList.groupIds()) {
359 writer.println("- {}", groupId.toBase64());
360 }
361 }
362 if (syncMessage.viewOnceOpen().isPresent()) {
363 final var viewOnceOpenMessage = syncMessage.viewOnceOpen().get();
364 writer.println("Received sync message with view once open message:");
365 writer.indentedWriter().println("Sender: {}", formatContact(viewOnceOpenMessage.sender()));
366 writer.indentedWriter()
367 .println("Timestamp: {}", DateUtils.formatTimestamp(viewOnceOpenMessage.timestamp()));
368 }
369 if (syncMessage.messageRequestResponse().isPresent()) {
370 final var requestResponseMessage = syncMessage.messageRequestResponse().get();
371 writer.println("Received message request response:");
372 writer.indentedWriter().println("Type: {}", requestResponseMessage.type());
373 if (requestResponseMessage.groupId().isPresent()) {
374 writer.println("For group:");
375 printGroupInfo(writer.indentedWriter(), requestResponseMessage.groupId().get());
376 }
377 if (requestResponseMessage.person().isPresent()) {
378 writer.indentedWriter().println("For Person: {}", formatContact(requestResponseMessage.person().get()));
379 }
380 }
381 }
382
383 private void printPreview(
384 final PlainTextWriter writer, final MessageEnvelope.Data.Preview preview
385 ) {
386 writer.println("Title: {}", preview.title());
387 writer.println("Description: {}", preview.description());
388 writer.println("Date: {}", DateUtils.formatTimestamp(preview.date()));
389 writer.println("Url: {}", preview.url());
390 if (preview.image().isPresent()) {
391 writer.println("Image:");
392 printAttachment(writer.indentedWriter(), preview.image().get());
393 }
394 }
395
396 private void printSticker(
397 final PlainTextWriter writer, final MessageEnvelope.Data.Sticker sticker
398 ) {
399 writer.println("Pack id: {}", Hex.toStringCondensed(sticker.packId().serialize()));
400 writer.println("Sticker id: {}", sticker.stickerId());
401 }
402
403 private void printReaction(
404 final PlainTextWriter writer, final MessageEnvelope.Data.Reaction reaction
405 ) {
406 writer.println("Emoji: {}", reaction.emoji());
407 writer.println("Target author: {}", formatContact(reaction.targetAuthor()));
408 writer.println("Target timestamp: {}", DateUtils.formatTimestamp(reaction.targetSentTimestamp()));
409 writer.println("Is remove: {}", reaction.isRemove());
410 }
411
412 private void printQuote(
413 final PlainTextWriter writer, final MessageEnvelope.Data.Quote quote
414 ) {
415 writer.println("Id: {}", quote.id());
416 writer.println("Author: {}", formatContact(quote.author()));
417 if (quote.text().isPresent()) {
418 writer.println("Text: {}", quote.text().get());
419 }
420 if (quote.mentions() != null && quote.mentions().size() > 0) {
421 writer.println("Mentions:");
422 for (var mention : quote.mentions()) {
423 printMention(writer, mention);
424 }
425 }
426 if (quote.attachments().size() > 0) {
427 writer.println("Attachments:");
428 for (var attachment : quote.attachments()) {
429 writer.println("- Attachment:");
430 printAttachment(writer.indentedWriter(), attachment);
431 }
432 }
433 }
434
435 private void printSharedContact(final PlainTextWriter writer, final MessageEnvelope.Data.SharedContact contact) {
436 writer.println("Name:");
437 var name = contact.name();
438 writer.indent(w -> {
439 if (name.display().isPresent() && !name.display().get().isBlank()) {
440 w.println("Display name: {}", name.display().get());
441 }
442 if (name.given().isPresent() && !name.given().get().isBlank()) {
443 w.println("First name: {}", name.given().get());
444 }
445 if (name.middle().isPresent() && !name.middle().get().isBlank()) {
446 w.println("Middle name: {}", name.middle().get());
447 }
448 if (name.family().isPresent() && !name.family().get().isBlank()) {
449 w.println("Family name: {}", name.family().get());
450 }
451 if (name.prefix().isPresent() && !name.prefix().get().isBlank()) {
452 w.println("Prefix name: {}", name.prefix().get());
453 }
454 if (name.suffix().isPresent() && !name.suffix().get().isBlank()) {
455 w.println("Suffix name: {}", name.suffix().get());
456 }
457 });
458
459 if (contact.avatar().isPresent()) {
460 var avatar = contact.avatar().get();
461 writer.println("Avatar: (profile: {})", avatar.isProfile());
462 printAttachment(writer.indentedWriter(), avatar.attachment());
463 }
464
465 if (contact.organization().isPresent()) {
466 writer.println("Organisation: {}", contact.organization().get());
467 }
468
469 if (contact.phone().size() > 0) {
470 writer.println("Phone details:");
471 for (var phone : contact.phone()) {
472 writer.println("- Phone:");
473 writer.indent(w -> {
474 w.println("Number: {}", phone.value());
475 w.println("Type: {}", phone.type());
476 if (phone.label().isPresent() && !phone.label().get().isBlank()) {
477 w.println("Label: {}", phone.label().get());
478 }
479 });
480 }
481 }
482
483 if (contact.email().size() > 0) {
484 writer.println("Email details:");
485 for (var email : contact.email()) {
486 writer.println("- Email:");
487 writer.indent(w -> {
488 w.println("Address: {}", email.value());
489 w.println("Type: {}", email.type());
490 if (email.label().isPresent() && !email.label().get().isBlank()) {
491 w.println("Label: {}", email.label().get());
492 }
493 });
494 }
495 }
496
497 if (contact.address().size() > 0) {
498 writer.println("Address details:");
499 for (var address : contact.address()) {
500 writer.println("- Address:");
501 writer.indent(w -> {
502 w.println("Type: {}", address.type());
503 if (address.label().isPresent() && !address.label().get().isBlank()) {
504 w.println("Label: {}", address.label().get());
505 }
506 if (address.street().isPresent() && !address.street().get().isBlank()) {
507 w.println("Street: {}", address.street().get());
508 }
509 if (address.pobox().isPresent() && !address.pobox().get().isBlank()) {
510 w.println("Pobox: {}", address.pobox().get());
511 }
512 if (address.neighborhood().isPresent() && !address.neighborhood().get().isBlank()) {
513 w.println("Neighbourhood: {}", address.neighborhood().get());
514 }
515 if (address.city().isPresent() && !address.city().get().isBlank()) {
516 w.println("City: {}", address.city().get());
517 }
518 if (address.region().isPresent() && !address.region().get().isBlank()) {
519 w.println("Region: {}", address.region().get());
520 }
521 if (address.postcode().isPresent() && !address.postcode().get().isBlank()) {
522 w.println("Postcode: {}", address.postcode().get());
523 }
524 if (address.country().isPresent() && !address.country().get().isBlank()) {
525 w.println("Country: {}", address.country().get());
526 }
527 });
528 }
529 }
530 }
531
532 private void printGroupContext(
533 final PlainTextWriter writer, final MessageEnvelope.Data.GroupContext groupContext
534 ) {
535 printGroupInfo(writer, groupContext.groupId());
536 writer.println("Revision: {}", groupContext.revision());
537 writer.println("Type: {}", groupContext.isGroupUpdate() ? "UPDATE" : "DELIVER");
538 }
539
540 private void printStoryContext(
541 final PlainTextWriter writer, final MessageEnvelope.Data.StoryContext storyContext
542 ) {
543 writer.println("Sender: {}", formatContact(storyContext.author()));
544 writer.println("Sent timestamp: {}", storyContext.sentTimestamp());
545 }
546
547 private void printGroupInfo(final PlainTextWriter writer, final GroupId groupId) {
548 writer.println("Id: {}", groupId.toBase64());
549
550 var group = m.getGroup(groupId);
551 if (group != null) {
552 writer.println("Name: {}", group.title());
553 } else {
554 writer.println("Name: <Unknown group>");
555 }
556 }
557
558 private void printMention(
559 PlainTextWriter writer, MessageEnvelope.Data.Mention mention
560 ) {
561 writer.println("- {}: {} (length: {})", formatContact(mention.recipient()), mention.start(), mention.length());
562 }
563
564 private void printTextStyle(
565 PlainTextWriter writer, MessageEnvelope.Data.TextStyle textStyle
566 ) {
567 writer.println("- {}: {} (length: {})", textStyle.style().name(), textStyle.start(), textStyle.length());
568 }
569
570 private void printAttachment(PlainTextWriter writer, MessageEnvelope.Data.Attachment attachment) {
571 writer.println("Content-Type: {}", attachment.contentType());
572 writer.println("Type: {}", attachment.id().isPresent() ? "Pointer" : "Stream");
573 if (attachment.id().isPresent()) {
574 writer.println("Id: {}", attachment.id().get());
575 }
576 if (attachment.uploadTimestamp().isPresent()) {
577 writer.println("Upload timestamp: {}", DateUtils.formatTimestamp(attachment.uploadTimestamp().get()));
578 }
579 if (attachment.caption().isPresent()) {
580 writer.println("Caption: {}", attachment.caption().get());
581 }
582 if (attachment.fileName().isPresent()) {
583 writer.println("Filename: {}", attachment.fileName().get());
584 }
585 if (attachment.size().isPresent() || attachment.preview().isPresent()) {
586 writer.println("Size: {}{}",
587 attachment.size().isPresent() ? attachment.size().get() + " bytes" : "<unavailable>",
588 attachment.preview().isPresent() ? " (Preview is available: "
589 + attachment.preview().get().length
590 + " bytes)" : "");
591 }
592 if (attachment.thumbnail().isPresent()) {
593 writer.println("Thumbnail:");
594 printAttachment(writer.indentedWriter(), attachment.thumbnail().get());
595 }
596 final var flags = new ArrayList<String>();
597 if (attachment.isVoiceNote()) {
598 flags.add("voice note");
599 }
600 if (attachment.isBorderless()) {
601 flags.add("borderless");
602 }
603 if (attachment.isGif()) {
604 flags.add("video gif");
605 }
606 if (flags.size() > 0) {
607 writer.println("Flags: {}", String.join(", ", flags));
608 }
609 if (attachment.width().isPresent() || attachment.height().isPresent()) {
610 writer.println("Dimensions: {}x{}", attachment.width().orElse(0), attachment.height().orElse(0));
611 }
612 if (attachment.file().isPresent()) {
613 var file = attachment.file().get();
614 if (file.exists()) {
615 writer.println("Stored plaintext in: {}", file);
616 }
617 }
618 }
619
620 private String formatContact(RecipientAddress address) {
621 final var number = address.getLegacyIdentifier();
622 final var name = m.getContactOrProfileName(RecipientIdentifier.Single.fromAddress(address));
623 if (name == null || name.isEmpty()) {
624 return number;
625 } else {
626 return MessageFormatter.arrayFormat("“{}” {}", new Object[]{name, number}).getMessage();
627 }
628 }
629 }