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