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