1 package org
.asamk
.signal
;
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
;
15 import java
.util
.ArrayList
;
16 import java
.util
.stream
.Collectors
;
18 public class ReceiveMessageHandler
implements Manager
.ReceiveMessageHandler
{
21 final PlainTextWriter writer
;
23 public ReceiveMessageHandler(Manager m
, final PlainTextWriter writer
) {
29 public void handleMessage(MessageEnvelope envelope
, Throwable exception
) {
30 synchronized (writer
) {
31 handleMessageInternal(envelope
, exception
);
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(),
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");
49 if (exception
!= null) {
50 if (exception
instanceof UntrustedIdentityException e
) {
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();
55 "Use 'signal-cli -a {} listIdentities -n {}', verify the key and run 'signal-cli -a {} trust -v \"FINGER_PRINT\" {}' to mark it as trusted",
61 "If you don't care about security, use 'signal-cli -a {} trust -a {}' to trust it without verification",
65 writer
.println("Exception: {} ({})", exception
.getMessage(), exception
.getClass().getSimpleName());
69 if (envelope
.data().isPresent()) {
70 var message
= envelope
.data().get();
71 printDataMessage(writer
, message
);
73 if (envelope
.edit().isPresent()) {
74 var message
= envelope
.edit().get();
75 printEditMessage(writer
, message
);
77 if (envelope
.story().isPresent()) {
78 var message
= envelope
.story().get();
79 printStoryMessage(writer
.indentedWriter(), message
);
81 if (envelope
.sync().isPresent()) {
82 writer
.println("Received a sync message");
83 var syncMessage
= envelope
.sync().get();
84 printSyncMessage(writer
, syncMessage
);
86 if (envelope
.call().isPresent()) {
87 writer
.println("Received a call message");
88 var callMessage
= envelope
.call().get();
89 printCallMessage(writer
.indentedWriter(), callMessage
);
91 if (envelope
.receipt().isPresent()) {
92 writer
.println("Received a receipt message");
93 var receiptMessage
= envelope
.receipt().get();
94 printReceiptMessage(writer
.indentedWriter(), receiptMessage
);
96 if (envelope
.typing().isPresent()) {
97 writer
.println("Received a typing message");
98 var typingMessage
= envelope
.typing().get();
99 printTypingMessage(writer
.indentedWriter(), typingMessage
);
104 private void printDataMessage(PlainTextWriter writer
, MessageEnvelope
.Data message
) {
105 writer
.println("Message timestamp: {}", DateUtils
.formatTimestamp(message
.timestamp()));
106 if (message
.isViewOnce()) {
107 writer
.println("=VIEW ONCE=");
110 if (message
.body().isPresent()) {
111 writer
.println("Body: {}", message
.body().get());
113 if (message
.groupContext().isPresent()) {
114 writer
.println("Group info:");
115 final var groupContext
= message
.groupContext().get();
116 printGroupContext(writer
.indentedWriter(), groupContext
);
118 if (message
.storyContext().isPresent()) {
119 writer
.println("Story reply:");
120 final var storyContext
= message
.storyContext().get();
121 printStoryContext(writer
.indentedWriter(), storyContext
);
123 if (message
.groupCallUpdate().isPresent()) {
124 writer
.println("Group call update:");
125 final var groupCallUpdate
= message
.groupCallUpdate().get();
126 writer
.indentedWriter().println("Era id: {}", groupCallUpdate
.eraId());
128 if (!message
.previews().isEmpty()) {
129 writer
.println("Previews:");
130 final var previews
= message
.previews();
131 for (var preview
: previews
) {
132 writer
.println("- Preview");
133 printPreview(writer
.indentedWriter(), preview
);
136 if (!message
.sharedContacts().isEmpty()) {
137 writer
.println("Contacts:");
138 for (var contact
: message
.sharedContacts()) {
139 writer
.println("- Contact:");
140 printSharedContact(writer
.indentedWriter(), contact
);
143 if (message
.sticker().isPresent()) {
144 final var sticker
= message
.sticker().get();
145 writer
.println("Sticker:");
146 printSticker(writer
.indentedWriter(), sticker
);
148 if (message
.isEndSession()) {
149 writer
.println("Is end session");
151 if (message
.isExpirationUpdate()) {
152 writer
.println("Is Expiration update: true");
154 if (message
.expiresInSeconds() > 0) {
155 writer
.println("Expires in: {} seconds", message
.expiresInSeconds());
157 if (message
.isProfileKeyUpdate()) {
158 writer
.println("Profile key update");
160 if (message
.hasProfileKey()) {
161 writer
.println("With profile key");
163 if (message
.reaction().isPresent()) {
164 writer
.println("Reaction:");
165 final var reaction
= message
.reaction().get();
166 printReaction(writer
.indentedWriter(), reaction
);
168 if (message
.quote().isPresent()) {
169 writer
.println("Quote:");
170 var quote
= message
.quote().get();
171 printQuote(writer
.indentedWriter(), quote
);
173 if (message
.remoteDeleteId().isPresent()) {
174 final var remoteDelete
= message
.remoteDeleteId().get();
175 writer
.println("Remote delete message: timestamp = {}", remoteDelete
);
177 if (!message
.mentions().isEmpty()) {
178 writer
.println("Mentions:");
179 for (var mention
: message
.mentions()) {
180 printMention(writer
, mention
);
183 if (!message
.textStyles().isEmpty()) {
184 writer
.println("Text styles:");
185 for (var textStyle
: message
.textStyles()) {
186 printTextStyle(writer
, textStyle
);
189 if (!message
.attachments().isEmpty()) {
190 writer
.println("Attachments:");
191 for (var attachment
: message
.attachments()) {
192 writer
.println("- Attachment:");
193 printAttachment(writer
.indentedWriter(), attachment
);
198 private void printEditMessage(PlainTextWriter writer
, MessageEnvelope
.Edit message
) {
199 writer
.println("Edit: Target message timestamp: {}", DateUtils
.formatTimestamp(message
.targetSentTimestamp()));
200 printDataMessage(writer
.indentedWriter(), message
.dataMessage());
203 private void printStoryMessage(PlainTextWriter writer
, MessageEnvelope
.Story message
) {
204 writer
.println("Story: with replies: {}", message
.allowsReplies());
205 if (message
.groupId().isPresent()) {
206 writer
.println("Group info:");
207 printGroupInfo(writer
.indentedWriter(), message
.groupId().get());
209 if (message
.textAttachment().isPresent()) {
210 writer
.println("Body: {}", message
.textAttachment().get().text().orElse(""));
212 if (message
.textAttachment().get().preview().isPresent()) {
213 writer
.println("Preview:");
214 printPreview(writer
.indentedWriter(), message
.textAttachment().get().preview().get());
217 if (message
.fileAttachment().isPresent()) {
218 writer
.println("Attachments:");
219 printAttachment(writer
.indentedWriter(), message
.fileAttachment().get());
223 private void printTypingMessage(final PlainTextWriter writer
, final MessageEnvelope
.Typing typingMessage
) {
224 writer
.println("Action: {}", typingMessage
.type());
225 writer
.println("Timestamp: {}", DateUtils
.formatTimestamp(typingMessage
.timestamp()));
226 if (typingMessage
.groupId().isPresent()) {
227 writer
.println("Group Info:");
228 final var groupId
= typingMessage
.groupId().get();
229 printGroupInfo(writer
.indentedWriter(), groupId
);
233 private void printReceiptMessage(final PlainTextWriter writer
, final MessageEnvelope
.Receipt receiptMessage
) {
234 writer
.println("When: {}", DateUtils
.formatTimestamp(receiptMessage
.when()));
235 if (receiptMessage
.type() == MessageEnvelope
.Receipt
.Type
.DELIVERY
) {
236 writer
.println("Is delivery receipt");
238 if (receiptMessage
.type() == MessageEnvelope
.Receipt
.Type
.READ
) {
239 writer
.println("Is read receipt");
241 if (receiptMessage
.type() == MessageEnvelope
.Receipt
.Type
.VIEWED
) {
242 writer
.println("Is viewed receipt");
244 writer
.println("Timestamps:");
245 for (long timestamp
: receiptMessage
.timestamps()) {
246 writer
.println("- {}", DateUtils
.formatTimestamp(timestamp
));
250 private void printCallMessage(final PlainTextWriter writer
, final MessageEnvelope
.Call callMessage
) {
251 if (callMessage
.destinationDeviceId().isPresent()) {
252 final var deviceId
= callMessage
.destinationDeviceId().get();
253 writer
.println("Destination device id: {}", deviceId
);
255 if (callMessage
.groupId().isPresent()) {
256 final var groupId
= callMessage
.groupId().get();
257 writer
.println("Destination group id: {}", groupId
);
259 if (callMessage
.timestamp().isPresent()) {
260 writer
.println("Timestamp: {}", DateUtils
.formatTimestamp(callMessage
.timestamp().get()));
262 if (callMessage
.answer().isPresent()) {
263 var answerMessage
= callMessage
.answer().get();
264 writer
.println("Answer message: {}, opaque length: {})", answerMessage
.id(), answerMessage
.opaque().length
);
266 if (callMessage
.busy().isPresent()) {
267 var busyMessage
= callMessage
.busy().get();
268 writer
.println("Busy message: {}", busyMessage
.id());
270 if (callMessage
.hangup().isPresent()) {
271 var hangupMessage
= callMessage
.hangup().get();
272 writer
.println("Hangup message: {}", hangupMessage
.id());
274 if (!callMessage
.iceUpdate().isEmpty()) {
275 writer
.println("Ice update messages:");
276 var iceUpdateMessages
= callMessage
.iceUpdate();
277 for (var iceUpdateMessage
: iceUpdateMessages
) {
278 writer
.println("- {}, opaque length: {}", iceUpdateMessage
.id(), iceUpdateMessage
.opaque().length
);
281 if (callMessage
.offer().isPresent()) {
282 var offerMessage
= callMessage
.offer().get();
283 writer
.println("Offer message: {}, opaque length: {}", offerMessage
.id(), offerMessage
.opaque().length
);
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());
293 private void printSyncMessage(final PlainTextWriter writer
, final MessageEnvelope
.Sync syncMessage
) {
294 if (syncMessage
.contacts().isPresent()) {
295 final var contactsMessage
= syncMessage
.contacts().get();
296 var type
= contactsMessage
.isComplete() ?
"complete" : "partial";
297 writer
.println("Received {} sync contacts:", type
);
299 if (syncMessage
.groups().isPresent()) {
300 writer
.println("Received sync groups.");
302 if (!syncMessage
.read().isEmpty()) {
303 writer
.println("Received sync read messages list");
304 for (var rm
: syncMessage
.read()) {
305 writer
.println("- From: {} Message timestamp: {}",
306 formatContact(rm
.sender()),
307 DateUtils
.formatTimestamp(rm
.timestamp()));
310 if (!syncMessage
.viewed().isEmpty()) {
311 writer
.println("Received sync viewed messages list");
312 for (var vm
: syncMessage
.viewed()) {
313 writer
.println("- From: {} Message timestamp: {}",
314 formatContact(vm
.sender()),
315 DateUtils
.formatTimestamp(vm
.timestamp()));
318 if (syncMessage
.sent().isPresent()) {
319 writer
.println("Received sync sent message");
320 final var sentTranscriptMessage
= syncMessage
.sent().get();
322 if (sentTranscriptMessage
.destination().isPresent()) {
323 to = formatContact(sentTranscriptMessage
.destination().get());
324 } else if (!sentTranscriptMessage
.recipients().isEmpty()) {
325 to = sentTranscriptMessage
.recipients()
327 .map(this::formatContact
)
328 .collect(Collectors
.joining(", "));
332 writer
.indentedWriter().println("To: {}", to);
333 writer
.indentedWriter()
334 .println("Timestamp: {}", DateUtils
.formatTimestamp(sentTranscriptMessage
.timestamp()));
335 if (sentTranscriptMessage
.expirationStartTimestamp() > 0) {
336 writer
.indentedWriter()
337 .println("Expiration started at: {}",
338 DateUtils
.formatTimestamp(sentTranscriptMessage
.expirationStartTimestamp()));
340 if (sentTranscriptMessage
.message().isPresent()) {
341 var message
= sentTranscriptMessage
.message().get();
342 printDataMessage(writer
.indentedWriter(), message
);
344 if (sentTranscriptMessage
.story().isPresent()) {
345 var message
= sentTranscriptMessage
.story().get();
346 printStoryMessage(writer
.indentedWriter(), message
);
349 if (syncMessage
.blocked().isPresent()) {
350 writer
.println("Received sync message with block list");
351 writer
.println("Blocked:");
352 final var blockedList
= syncMessage
.blocked().get();
353 for (var address
: blockedList
.recipients()) {
354 writer
.println("- {}", address
.getLegacyIdentifier());
356 for (var groupId
: blockedList
.groupIds()) {
357 writer
.println("- {}", groupId
.toBase64());
360 if (syncMessage
.viewOnceOpen().isPresent()) {
361 final var viewOnceOpenMessage
= syncMessage
.viewOnceOpen().get();
362 writer
.println("Received sync message with view once open message:");
363 writer
.indentedWriter().println("Sender: {}", formatContact(viewOnceOpenMessage
.sender()));
364 writer
.indentedWriter()
365 .println("Timestamp: {}", DateUtils
.formatTimestamp(viewOnceOpenMessage
.timestamp()));
367 if (syncMessage
.messageRequestResponse().isPresent()) {
368 final var requestResponseMessage
= syncMessage
.messageRequestResponse().get();
369 writer
.println("Received message request response:");
370 writer
.indentedWriter().println("Type: {}", requestResponseMessage
.type());
371 if (requestResponseMessage
.groupId().isPresent()) {
372 writer
.println("For group:");
373 printGroupInfo(writer
.indentedWriter(), requestResponseMessage
.groupId().get());
375 if (requestResponseMessage
.person().isPresent()) {
376 writer
.indentedWriter().println("For Person: {}", formatContact(requestResponseMessage
.person().get()));
381 private void printPreview(final PlainTextWriter writer
, final MessageEnvelope
.Data
.Preview preview
) {
382 writer
.println("Title: {}", preview
.title());
383 writer
.println("Description: {}", preview
.description());
384 writer
.println("Date: {}", DateUtils
.formatTimestamp(preview
.date()));
385 writer
.println("Url: {}", preview
.url());
386 if (preview
.image().isPresent()) {
387 writer
.println("Image:");
388 printAttachment(writer
.indentedWriter(), preview
.image().get());
392 private void printSticker(final PlainTextWriter writer
, final MessageEnvelope
.Data
.Sticker sticker
) {
393 writer
.println("Pack id: {}", Hex
.toStringCondensed(sticker
.packId().serialize()));
394 writer
.println("Sticker id: {}", sticker
.stickerId());
397 private void printReaction(final PlainTextWriter writer
, final MessageEnvelope
.Data
.Reaction reaction
) {
398 writer
.println("Emoji: {}", reaction
.emoji());
399 writer
.println("Target author: {}", formatContact(reaction
.targetAuthor()));
400 writer
.println("Target timestamp: {}", DateUtils
.formatTimestamp(reaction
.targetSentTimestamp()));
401 writer
.println("Is remove: {}", reaction
.isRemove());
404 private void printQuote(final PlainTextWriter writer
, final MessageEnvelope
.Data
.Quote quote
) {
405 writer
.println("Id: {}", quote
.id());
406 writer
.println("Author: {}", formatContact(quote
.author()));
407 if (quote
.text().isPresent()) {
408 writer
.println("Text: {}", quote
.text().get());
410 if (quote
.mentions() != null && !quote
.mentions().isEmpty()) {
411 writer
.println("Mentions:");
412 for (var mention
: quote
.mentions()) {
413 printMention(writer
, mention
);
416 if (!quote
.attachments().isEmpty()) {
417 writer
.println("Attachments:");
418 for (var attachment
: quote
.attachments()) {
419 writer
.println("- Attachment:");
420 printAttachment(writer
.indentedWriter(), attachment
);
425 private void printSharedContact(final PlainTextWriter writer
, final MessageEnvelope
.Data
.SharedContact contact
) {
426 writer
.println("Name:");
427 var name
= contact
.name();
429 if (name
.given().isPresent() && !name
.given().get().isBlank()) {
430 w
.println("First name: {}", name
.given().get());
432 if (name
.middle().isPresent() && !name
.middle().get().isBlank()) {
433 w
.println("Middle name: {}", name
.middle().get());
435 if (name
.family().isPresent() && !name
.family().get().isBlank()) {
436 w
.println("Family name: {}", name
.family().get());
438 if (name
.prefix().isPresent() && !name
.prefix().get().isBlank()) {
439 w
.println("Prefix name: {}", name
.prefix().get());
441 if (name
.suffix().isPresent() && !name
.suffix().get().isBlank()) {
442 w
.println("Suffix name: {}", name
.suffix().get());
444 if (name
.nickname().isPresent() && !name
.nickname().get().isBlank()) {
445 w
.println("Display name: {}", name
.nickname().get());
449 if (contact
.avatar().isPresent()) {
450 var avatar
= contact
.avatar().get();
451 writer
.println("Avatar: (profile: {})", avatar
.isProfile());
452 printAttachment(writer
.indentedWriter(), avatar
.attachment());
455 if (contact
.organization().isPresent()) {
456 writer
.println("Organisation: {}", contact
.organization().get());
459 if (!contact
.phone().isEmpty()) {
460 writer
.println("Phone details:");
461 for (var phone
: contact
.phone()) {
462 writer
.println("- Phone:");
464 w
.println("Number: {}", phone
.value());
465 w
.println("Type: {}", phone
.type());
466 if (phone
.label().isPresent() && !phone
.label().get().isBlank()) {
467 w
.println("Label: {}", phone
.label().get());
473 if (!contact
.email().isEmpty()) {
474 writer
.println("Email details:");
475 for (var email
: contact
.email()) {
476 writer
.println("- Email:");
478 w
.println("Address: {}", email
.value());
479 w
.println("Type: {}", email
.type());
480 if (email
.label().isPresent() && !email
.label().get().isBlank()) {
481 w
.println("Label: {}", email
.label().get());
487 if (!contact
.address().isEmpty()) {
488 writer
.println("Address details:");
489 for (var address
: contact
.address()) {
490 writer
.println("- Address:");
492 w
.println("Type: {}", address
.type());
493 if (address
.label().isPresent() && !address
.label().get().isBlank()) {
494 w
.println("Label: {}", address
.label().get());
496 if (address
.street().isPresent() && !address
.street().get().isBlank()) {
497 w
.println("Street: {}", address
.street().get());
499 if (address
.pobox().isPresent() && !address
.pobox().get().isBlank()) {
500 w
.println("Pobox: {}", address
.pobox().get());
502 if (address
.neighborhood().isPresent() && !address
.neighborhood().get().isBlank()) {
503 w
.println("Neighbourhood: {}", address
.neighborhood().get());
505 if (address
.city().isPresent() && !address
.city().get().isBlank()) {
506 w
.println("City: {}", address
.city().get());
508 if (address
.region().isPresent() && !address
.region().get().isBlank()) {
509 w
.println("Region: {}", address
.region().get());
511 if (address
.postcode().isPresent() && !address
.postcode().get().isBlank()) {
512 w
.println("Postcode: {}", address
.postcode().get());
514 if (address
.country().isPresent() && !address
.country().get().isBlank()) {
515 w
.println("Country: {}", address
.country().get());
522 private void printGroupContext(final PlainTextWriter writer
, final MessageEnvelope
.Data
.GroupContext groupContext
) {
523 printGroupInfo(writer
, groupContext
.groupId());
524 writer
.println("Revision: {}", groupContext
.revision());
525 writer
.println("Type: {}", groupContext
.isGroupUpdate() ?
"UPDATE" : "DELIVER");
528 private void printStoryContext(final PlainTextWriter writer
, final MessageEnvelope
.Data
.StoryContext storyContext
) {
529 writer
.println("Sender: {}", formatContact(storyContext
.author()));
530 writer
.println("Sent timestamp: {}", storyContext
.sentTimestamp());
533 private void printGroupInfo(final PlainTextWriter writer
, final GroupId groupId
) {
534 writer
.println("Id: {}", groupId
.toBase64());
536 var group
= m
.getGroup(groupId
);
538 writer
.println("Name: {}", group
.title());
540 writer
.println("Name: <Unknown group>");
544 private void printMention(PlainTextWriter writer
, MessageEnvelope
.Data
.Mention mention
) {
545 writer
.println("- {}: {} (length: {})", formatContact(mention
.recipient()), mention
.start(), mention
.length());
548 private void printTextStyle(PlainTextWriter writer
, TextStyle textStyle
) {
549 writer
.println("- {}: {} (length: {})", textStyle
.style().name(), textStyle
.start(), textStyle
.length());
552 private void printAttachment(PlainTextWriter writer
, MessageEnvelope
.Data
.Attachment attachment
) {
553 writer
.println("Content-Type: {}", attachment
.contentType());
554 writer
.println("Type: {}", attachment
.id().isPresent() ?
"Pointer" : "Stream");
555 if (attachment
.id().isPresent()) {
556 writer
.println("Id: {}", attachment
.id().get());
558 if (attachment
.uploadTimestamp().isPresent()) {
559 writer
.println("Upload timestamp: {}", DateUtils
.formatTimestamp(attachment
.uploadTimestamp().get()));
561 if (attachment
.caption().isPresent()) {
562 writer
.println("Caption: {}", attachment
.caption().get());
564 if (attachment
.fileName().isPresent()) {
565 writer
.println("Filename: {}", attachment
.fileName().get());
567 if (attachment
.size().isPresent() || attachment
.preview().isPresent()) {
568 writer
.println("Size: {}{}",
569 attachment
.size().isPresent() ? attachment
.size().get() + " bytes" : "<unavailable>",
570 attachment
.preview().isPresent() ?
" (Preview is available: "
571 + attachment
.preview().get().length
574 if (attachment
.thumbnail().isPresent()) {
575 writer
.println("Thumbnail:");
576 printAttachment(writer
.indentedWriter(), attachment
.thumbnail().get());
578 final var flags
= new ArrayList
<String
>();
579 if (attachment
.isVoiceNote()) {
580 flags
.add("voice note");
582 if (attachment
.isBorderless()) {
583 flags
.add("borderless");
585 if (attachment
.isGif()) {
586 flags
.add("video gif");
588 if (!flags
.isEmpty()) {
589 writer
.println("Flags: {}", String
.join(", ", flags
));
591 if (attachment
.width().isPresent() || attachment
.height().isPresent()) {
592 writer
.println("Dimensions: {}x{}", attachment
.width().orElse(0), attachment
.height().orElse(0));
594 if (attachment
.file().isPresent()) {
595 var file
= attachment
.file().get();
597 writer
.println("Stored plaintext in: {}", file
);
602 private String
formatContact(RecipientAddress address
) {
603 final var number
= address
.getLegacyIdentifier();
604 final var name
= m
.getContactOrProfileName(RecipientIdentifier
.Single
.fromAddress(address
));
605 if (name
== null || name
.isEmpty()) {
608 return MessageFormatter
.arrayFormat("“{}” {}", new Object
[]{name
, number
}).getMessage();