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