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