]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/Main.java
Implement device linking
[signal-cli] / src / main / java / org / asamk / signal / Main.java
1 /**
2 * Copyright (C) 2015 AsamK
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 package org.asamk.signal;
18
19 import net.sourceforge.argparse4j.ArgumentParsers;
20 import net.sourceforge.argparse4j.impl.Arguments;
21 import net.sourceforge.argparse4j.inf.*;
22 import org.apache.commons.io.IOUtils;
23 import org.apache.http.util.TextUtils;
24 import org.asamk.Signal;
25 import org.freedesktop.dbus.DBusConnection;
26 import org.freedesktop.dbus.DBusSigHandler;
27 import org.freedesktop.dbus.exceptions.DBusException;
28 import org.freedesktop.dbus.exceptions.DBusExecutionException;
29 import org.whispersystems.libsignal.InvalidKeyException;
30 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
31 import org.whispersystems.signalservice.api.messages.*;
32 import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
33 import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
34 import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
35 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
36 import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
37 import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
38 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
39 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
40
41 import java.io.File;
42 import java.io.IOException;
43 import java.security.Security;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.concurrent.TimeoutException;
47
48 public class Main {
49
50 public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
51 public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
52
53 public static void main(String[] args) {
54 // Workaround for BKS truststore
55 Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1);
56
57 Namespace ns = parseArgs(args);
58 if (ns == null) {
59 System.exit(1);
60 }
61
62 final String username = ns.getString("username");
63 Manager m;
64 Signal ts;
65 DBusConnection dBusConn = null;
66 try {
67 if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
68 try {
69 m = null;
70 int busType;
71 if (ns.getBoolean("dbus_system")) {
72 busType = DBusConnection.SYSTEM;
73 } else {
74 busType = DBusConnection.SESSION;
75 }
76 dBusConn = DBusConnection.getConnection(busType);
77 ts = (Signal) dBusConn.getRemoteObject(
78 SIGNAL_BUSNAME, SIGNAL_OBJECTPATH,
79 Signal.class);
80 } catch (DBusException e) {
81 e.printStackTrace();
82 if (dBusConn != null) {
83 dBusConn.disconnect();
84 }
85 System.exit(3);
86 return;
87 }
88 } else {
89 String settingsPath = ns.getString("config");
90 if (TextUtils.isEmpty(settingsPath)) {
91 settingsPath = System.getProperty("user.home") + "/.config/signal";
92 if (!new File(settingsPath).exists()) {
93 String legacySettingsPath = System.getProperty("user.home") + "/.config/textsecure";
94 if (new File(legacySettingsPath).exists()) {
95 settingsPath = legacySettingsPath;
96 }
97 }
98 }
99
100 m = new Manager(username, settingsPath);
101 ts = m;
102 if (m.userExists()) {
103 try {
104 m.load();
105 } catch (Exception e) {
106 System.err.println("Error loading state file \"" + m.getFileName() + "\": " + e.getMessage());
107 System.exit(2);
108 return;
109 }
110 }
111 }
112
113 switch (ns.getString("command")) {
114 case "register":
115 if (dBusConn != null) {
116 System.err.println("register is not yet implemented via dbus");
117 System.exit(1);
118 }
119 if (!m.userHasKeys()) {
120 m.createNewIdentity();
121 }
122 try {
123 m.register(ns.getBoolean("voice"));
124 } catch (IOException e) {
125 System.err.println("Request verify error: " + e.getMessage());
126 System.exit(3);
127 }
128 break;
129 case "verify":
130 if (dBusConn != null) {
131 System.err.println("verify is not yet implemented via dbus");
132 System.exit(1);
133 }
134 if (!m.userHasKeys()) {
135 System.err.println("User has no keys, first call register.");
136 System.exit(1);
137 }
138 if (m.isRegistered()) {
139 System.err.println("User registration is already verified");
140 System.exit(1);
141 }
142 try {
143 m.verifyAccount(ns.getString("verificationCode"));
144 } catch (IOException e) {
145 System.err.println("Verify error: " + e.getMessage());
146 System.exit(3);
147 }
148 break;
149 case "link":
150 if (dBusConn != null) {
151 System.err.println("link is not yet implemented via dbus");
152 System.exit(1);
153 }
154
155 // When linking, username is null and we always have to create keys
156 m.createNewIdentity();
157
158 String deviceName = ns.getString("name");
159 if (deviceName == null) {
160 deviceName = "cli";
161 }
162 try {
163 System.out.println(m.getDeviceLinkUri());
164 m.finishDeviceLink(deviceName);
165 System.out.println("Associated with: " + m.getUsername());
166 } catch (TimeoutException e) {
167 System.err.println("Link request timed out, please try again.");
168 System.exit(3);
169 } catch (IOException e) {
170 System.err.println("Link request error: " + e.getMessage());
171 System.exit(3);
172 } catch (InvalidKeyException e) {
173 e.printStackTrace();
174 System.exit(3);
175 } catch (UserAlreadyExists e) {
176 System.err.println("The user " + e.getUsername() + " already exists\nDelete \"" + e.getFileName() + "\" before trying again.");
177 System.exit(3);
178 }
179 break;
180 case "send":
181 if (dBusConn == null && !m.isRegistered()) {
182 System.err.println("User is not registered.");
183 System.exit(1);
184 }
185
186 if (ns.getBoolean("endsession")) {
187 if (ns.getList("recipient") == null) {
188 System.err.println("No recipients given");
189 System.err.println("Aborting sending.");
190 System.exit(1);
191 }
192 try {
193 ts.sendEndSessionMessage(ns.<String>getList("recipient"));
194 } catch (IOException e) {
195 handleIOException(e);
196 } catch (EncapsulatedExceptions e) {
197 handleEncapsulatedExceptions(e);
198 } catch (AssertionError e) {
199 handleAssertionError(e);
200 } catch (DBusExecutionException e) {
201 handleDBusExecutionException(e);
202 } catch (UntrustedIdentityException e) {
203 e.printStackTrace();
204 }
205 } else {
206 String messageText = ns.getString("message");
207 if (messageText == null) {
208 try {
209 messageText = IOUtils.toString(System.in);
210 } catch (IOException e) {
211 System.err.println("Failed to read message from stdin: " + e.getMessage());
212 System.err.println("Aborting sending.");
213 System.exit(1);
214 }
215 }
216
217 try {
218 List<String> attachments = ns.getList("attachment");
219 if (attachments == null) {
220 attachments = new ArrayList<>();
221 }
222 if (ns.getString("group") != null) {
223 byte[] groupId = decodeGroupId(ns.getString("group"));
224 ts.sendGroupMessage(messageText, attachments, groupId);
225 } else {
226 ts.sendMessage(messageText, attachments, ns.<String>getList("recipient"));
227 }
228 } catch (IOException e) {
229 handleIOException(e);
230 } catch (EncapsulatedExceptions e) {
231 handleEncapsulatedExceptions(e);
232 } catch (AssertionError e) {
233 handleAssertionError(e);
234 } catch (GroupNotFoundException e) {
235 handleGroupNotFoundException(e);
236 } catch (AttachmentInvalidException e) {
237 System.err.println("Failed to add attachment: " + e.getMessage());
238 System.err.println("Aborting sending.");
239 System.exit(1);
240 } catch (DBusExecutionException e) {
241 handleDBusExecutionException(e);
242 } catch (UntrustedIdentityException e) {
243 e.printStackTrace();
244 }
245 }
246
247 break;
248 case "receive":
249 if (dBusConn != null) {
250 try {
251 dBusConn.addSigHandler(Signal.MessageReceived.class, new DBusSigHandler<Signal.MessageReceived>() {
252 @Override
253 public void handle(Signal.MessageReceived s) {
254 System.out.print(String.format("Envelope from: %s\nTimestamp: %d\nBody: %s\n",
255 s.getSender(), s.getTimestamp(), s.getMessage()));
256 if (s.getGroupId().length > 0) {
257 System.out.println("Group info:");
258 System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId()));
259 }
260 if (s.getAttachments().size() > 0) {
261 System.out.println("Attachments: ");
262 for (String attachment : s.getAttachments()) {
263 System.out.println("- Stored plaintext in: " + attachment);
264 }
265 }
266 System.out.println();
267 }
268 });
269 } catch (DBusException e) {
270 e.printStackTrace();
271 }
272 while (true) {
273 try {
274 Thread.sleep(10000);
275 } catch (InterruptedException e) {
276 System.exit(0);
277 }
278 }
279 }
280 if (!m.isRegistered()) {
281 System.err.println("User is not registered.");
282 System.exit(1);
283 }
284 int timeout = 5;
285 if (ns.getInt("timeout") != null) {
286 timeout = ns.getInt("timeout");
287 }
288 boolean returnOnTimeout = true;
289 if (timeout < 0) {
290 returnOnTimeout = false;
291 timeout = 3600;
292 }
293 try {
294 m.receiveMessages(timeout, returnOnTimeout, new ReceiveMessageHandler(m));
295 } catch (IOException e) {
296 System.err.println("Error while receiving messages: " + e.getMessage());
297 System.exit(3);
298 } catch (AssertionError e) {
299 handleAssertionError(e);
300 }
301 break;
302 case "quitGroup":
303 if (dBusConn != null) {
304 System.err.println("quitGroup is not yet implemented via dbus");
305 System.exit(1);
306 }
307 if (!m.isRegistered()) {
308 System.err.println("User is not registered.");
309 System.exit(1);
310 }
311
312 try {
313 m.sendQuitGroupMessage(decodeGroupId(ns.getString("group")));
314 } catch (IOException e) {
315 handleIOException(e);
316 } catch (EncapsulatedExceptions e) {
317 handleEncapsulatedExceptions(e);
318 } catch (AssertionError e) {
319 handleAssertionError(e);
320 } catch (GroupNotFoundException e) {
321 handleGroupNotFoundException(e);
322 } catch (UntrustedIdentityException e) {
323 e.printStackTrace();
324 }
325
326 break;
327 case "updateGroup":
328 if (dBusConn != null) {
329 System.err.println("updateGroup is not yet implemented via dbus");
330 System.exit(1);
331 }
332 if (!m.isRegistered()) {
333 System.err.println("User is not registered.");
334 System.exit(1);
335 }
336
337 try {
338 byte[] groupId = null;
339 if (ns.getString("group") != null) {
340 groupId = decodeGroupId(ns.getString("group"));
341 }
342 byte[] newGroupId = m.sendUpdateGroupMessage(groupId, ns.getString("name"), ns.<String>getList("member"), ns.getString("avatar"));
343 if (groupId == null) {
344 System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …");
345 }
346 } catch (IOException e) {
347 handleIOException(e);
348 } catch (AttachmentInvalidException e) {
349 System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
350 System.err.println("Aborting sending.");
351 System.exit(1);
352 } catch (GroupNotFoundException e) {
353 handleGroupNotFoundException(e);
354 } catch (EncapsulatedExceptions e) {
355 handleEncapsulatedExceptions(e);
356 } catch (UntrustedIdentityException e) {
357 e.printStackTrace();
358 }
359
360 break;
361 case "daemon":
362 if (dBusConn != null) {
363 System.err.println("Stop it.");
364 System.exit(1);
365 }
366 if (!m.isRegistered()) {
367 System.err.println("User is not registered.");
368 System.exit(1);
369 }
370 DBusConnection conn = null;
371 try {
372 try {
373 int busType;
374 if (ns.getBoolean("system")) {
375 busType = DBusConnection.SYSTEM;
376 } else {
377 busType = DBusConnection.SESSION;
378 }
379 conn = DBusConnection.getConnection(busType);
380 conn.exportObject(SIGNAL_OBJECTPATH, m);
381 conn.requestBusName(SIGNAL_BUSNAME);
382 } catch (DBusException e) {
383 e.printStackTrace();
384 System.exit(3);
385 }
386 try {
387 m.receiveMessages(3600, false, new DbusReceiveMessageHandler(m, conn));
388 } catch (IOException e) {
389 System.err.println("Error while receiving messages: " + e.getMessage());
390 System.exit(3);
391 } catch (AssertionError e) {
392 handleAssertionError(e);
393 }
394 } finally {
395 if (conn != null) {
396 conn.disconnect();
397 }
398 }
399
400 break;
401 }
402 System.exit(0);
403 } finally {
404 if (dBusConn != null) {
405 dBusConn.disconnect();
406 }
407 }
408 }
409
410 private static void handleGroupNotFoundException(GroupNotFoundException e) {
411 System.err.println("Failed to send to group: " + e.getMessage());
412 System.err.println("Aborting sending.");
413 System.exit(1);
414 }
415
416 private static void handleDBusExecutionException(DBusExecutionException e) {
417 System.err.println("Cannot connect to dbus: " + e.getMessage());
418 System.err.println("Aborting.");
419 System.exit(1);
420 }
421
422 private static byte[] decodeGroupId(String groupId) {
423 try {
424 return Base64.decode(groupId);
425 } catch (IOException e) {
426 System.err.println("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage());
427 System.err.println("Aborting sending.");
428 System.exit(1);
429 return null;
430 }
431 }
432
433 private static Namespace parseArgs(String[] args) {
434 ArgumentParser parser = ArgumentParsers.newArgumentParser("signal-cli")
435 .defaultHelp(true)
436 .description("Commandline interface for Signal.")
437 .version(Manager.PROJECT_NAME + " " + Manager.PROJECT_VERSION);
438
439 parser.addArgument("-v", "--version")
440 .help("Show package version.")
441 .action(Arguments.version());
442 parser.addArgument("--config")
443 .help("Set the path, where to store the config (Default: $HOME/.config/signal-cli).");
444
445 MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
446 mut.addArgument("-u", "--username")
447 .help("Specify your phone number, that will be used for verification.");
448 mut.addArgument("--dbus")
449 .help("Make request via user dbus.")
450 .action(Arguments.storeTrue());
451 mut.addArgument("--dbus-system")
452 .help("Make request via system dbus.")
453 .action(Arguments.storeTrue());
454
455 Subparsers subparsers = parser.addSubparsers()
456 .title("subcommands")
457 .dest("command")
458 .description("valid subcommands")
459 .help("additional help");
460
461 Subparser parserLink = subparsers.addParser("link");
462 parserLink.addArgument("-n", "--name")
463 .help("Specify a name to describe this new device.");
464
465 Subparser parserRegister = subparsers.addParser("register");
466 parserRegister.addArgument("-v", "--voice")
467 .help("The verification should be done over voice, not sms.")
468 .action(Arguments.storeTrue());
469
470 Subparser parserVerify = subparsers.addParser("verify");
471 parserVerify.addArgument("verificationCode")
472 .help("The verification code you received via sms or voice call.");
473
474 Subparser parserSend = subparsers.addParser("send");
475 parserSend.addArgument("-g", "--group")
476 .help("Specify the recipient group ID.");
477 parserSend.addArgument("recipient")
478 .help("Specify the recipients' phone number.")
479 .nargs("*");
480 parserSend.addArgument("-m", "--message")
481 .help("Specify the message, if missing standard input is used.");
482 parserSend.addArgument("-a", "--attachment")
483 .nargs("*")
484 .help("Add file as attachment");
485 parserSend.addArgument("-e", "--endsession")
486 .help("Clear session state and send end session message.")
487 .action(Arguments.storeTrue());
488
489 Subparser parserLeaveGroup = subparsers.addParser("quitGroup");
490 parserLeaveGroup.addArgument("-g", "--group")
491 .required(true)
492 .help("Specify the recipient group ID.");
493
494 Subparser parserUpdateGroup = subparsers.addParser("updateGroup");
495 parserUpdateGroup.addArgument("-g", "--group")
496 .help("Specify the recipient group ID.");
497 parserUpdateGroup.addArgument("-n", "--name")
498 .help("Specify the new group name.");
499 parserUpdateGroup.addArgument("-a", "--avatar")
500 .help("Specify a new group avatar image file");
501 parserUpdateGroup.addArgument("-m", "--member")
502 .nargs("*")
503 .help("Specify one or more members to add to the group");
504
505 Subparser parserReceive = subparsers.addParser("receive");
506 parserReceive.addArgument("-t", "--timeout")
507 .type(int.class)
508 .help("Number of seconds to wait for new messages (negative values disable timeout)");
509
510 Subparser parserDaemon = subparsers.addParser("daemon");
511 parserDaemon.addArgument("--system")
512 .action(Arguments.storeTrue())
513 .help("Use DBus system bus instead of user bus.");
514
515 try {
516 Namespace ns = parser.parseArgs(args);
517 if ("link".equals(ns.getString("command"))) {
518 if (ns.getString("username") != null) {
519 parser.printUsage();
520 System.err.println("You cannot specify a username (phone number) when linking");
521 System.exit(2);
522 }
523 } else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
524 if (ns.getString("username") == null) {
525 parser.printUsage();
526 System.err.println("You need to specify a username (phone number)");
527 System.exit(2);
528 }
529 if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"))) {
530 System.err.println("Invalid username (phone number), make sure you include the country code.");
531 System.exit(2);
532 }
533 }
534 if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
535 System.err.println("You cannot specify recipients by phone number and groups a the same time");
536 System.exit(2);
537 }
538 return ns;
539 } catch (ArgumentParserException e) {
540 parser.handleError(e);
541 return null;
542 }
543 }
544
545 private static void handleAssertionError(AssertionError e) {
546 System.err.println("Failed to send/receive message (Assertion): " + e.getMessage());
547 e.printStackTrace();
548 System.err.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
549 System.exit(1);
550 }
551
552 private static void handleEncapsulatedExceptions(EncapsulatedExceptions e) {
553 System.err.println("Failed to send (some) messages:");
554 for (NetworkFailureException n : e.getNetworkExceptions()) {
555 System.err.println("Network failure for \"" + n.getE164number() + "\": " + n.getMessage());
556 }
557 for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) {
558 System.err.println("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage());
559 }
560 for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) {
561 System.err.println("Untrusted Identity for \"" + n.getE164Number() + "\": " + n.getMessage());
562 }
563 }
564
565 private static void handleIOException(IOException e) {
566 System.err.println("Failed to send message: " + e.getMessage());
567 }
568
569 private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
570 final Manager m;
571
572 public ReceiveMessageHandler(Manager m) {
573 this.m = m;
574 }
575
576 @Override
577 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) {
578 SignalServiceAddress source = envelope.getSourceAddress();
579 System.out.println(String.format("Envelope from: %s (device: %d)", source.getNumber(), envelope.getSourceDevice()));
580 if (source.getRelay().isPresent()) {
581 System.out.println("Relayed by: " + source.getRelay().get());
582 }
583 System.out.println("Timestamp: " + envelope.getTimestamp());
584
585 if (envelope.isReceipt()) {
586 System.out.println("Got receipt.");
587 } else if (envelope.isSignalMessage() | envelope.isPreKeySignalMessage()) {
588 if (content == null) {
589 System.out.println("Failed to decrypt message.");
590 } else {
591 if (content.getDataMessage().isPresent()) {
592 SignalServiceDataMessage message = content.getDataMessage().get();
593 handleSignalServiceDataMessage(message, group);
594 }
595 if (content.getSyncMessage().isPresent()) {
596 SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
597
598 if (syncMessage.getContacts().isPresent()) {
599 System.out.println("Received sync contacts");
600 printAttachment(syncMessage.getContacts().get());
601 }
602 if (syncMessage.getGroups().isPresent()) {
603 System.out.println("Received sync groups");
604 printAttachment(syncMessage.getGroups().get());
605 }
606 if (syncMessage.getRead().isPresent()) {
607 System.out.println("Received sync read messages list");
608 for (ReadMessage rm : syncMessage.getRead().get()) {
609 System.out.println("From: " + rm.getSender() + " Message timestamp: " + rm.getTimestamp());
610 }
611 }
612 if (syncMessage.getRequest().isPresent()) {
613 System.out.println("Received sync request");
614 if (syncMessage.getRequest().get().isContactsRequest()) {
615 System.out.println(" - contacts request");
616 }
617 if (syncMessage.getRequest().get().isGroupsRequest()) {
618 System.out.println(" - groups request");
619 }
620 }
621 if (syncMessage.getSent().isPresent()) {
622 System.out.println("Received sync sent message");
623 final SentTranscriptMessage sentTranscriptMessage = syncMessage.getSent().get();
624 System.out.println("To: " + (sentTranscriptMessage.getDestination().isPresent() ? sentTranscriptMessage.getDestination().get() : "Unknown") + " , Message timestamp: " + sentTranscriptMessage.getTimestamp());
625 SignalServiceDataMessage message = sentTranscriptMessage.getMessage();
626 handleSignalServiceDataMessage(message, null);
627 }
628 }
629 }
630 } else {
631 System.out.println("Unknown message received.");
632 }
633 System.out.println();
634 }
635
636 // TODO remove group parameter
637 private void handleSignalServiceDataMessage(SignalServiceDataMessage message, GroupInfo group) {
638 System.out.println("Message timestamp: " + message.getTimestamp());
639
640 if (message.getBody().isPresent()) {
641 System.out.println("Body: " + message.getBody().get());
642 }
643 if (message.getGroupInfo().isPresent()) {
644 SignalServiceGroup groupInfo = message.getGroupInfo().get();
645 System.out.println("Group info:");
646 System.out.println(" Id: " + Base64.encodeBytes(groupInfo.getGroupId()));
647 if (groupInfo.getName().isPresent()) {
648 System.out.println(" Name: " + groupInfo.getName().get());
649 } else if (group != null) {
650 System.out.println(" Name: " + group.name);
651 } else {
652 System.out.println(" Name: <Unknown group>");
653 }
654 System.out.println(" Type: " + groupInfo.getType());
655 if (groupInfo.getMembers().isPresent()) {
656 for (String member : groupInfo.getMembers().get()) {
657 System.out.println(" Member: " + member);
658 }
659 }
660 if (groupInfo.getAvatar().isPresent()) {
661 System.out.println(" Avatar:");
662 printAttachment(groupInfo.getAvatar().get());
663 }
664 }
665 if (message.isEndSession()) {
666 System.out.println("Is end session");
667 }
668
669 if (message.getAttachments().isPresent()) {
670 System.out.println("Attachments: ");
671 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
672 printAttachment(attachment);
673 }
674 }
675 }
676
677 private void printAttachment(SignalServiceAttachment attachment) {
678 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
679 if (attachment.isPointer()) {
680 final SignalServiceAttachmentPointer pointer = attachment.asPointer();
681 System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : ""));
682 System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "<unavailable>") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : ""));
683 File file = m.getAttachmentFile(pointer.getId());
684 if (file.exists()) {
685 System.out.println(" Stored plaintext in: " + file);
686 }
687 }
688 }
689 }
690
691 private static class DbusReceiveMessageHandler extends ReceiveMessageHandler {
692 final DBusConnection conn;
693
694 public DbusReceiveMessageHandler(Manager m, DBusConnection conn) {
695 super(m);
696 this.conn = conn;
697 }
698
699 @Override
700 public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, GroupInfo group) {
701 super.handleMessage(envelope, content, group);
702
703 if (!envelope.isReceipt() && content != null && content.getDataMessage().isPresent()) {
704 SignalServiceDataMessage message = content.getDataMessage().get();
705
706 if (!message.isEndSession() &&
707 !(message.getGroupInfo().isPresent() &&
708 message.getGroupInfo().get().getType() != SignalServiceGroup.Type.DELIVER)) {
709 List<String> attachments = new ArrayList<>();
710 if (message.getAttachments().isPresent()) {
711 for (SignalServiceAttachment attachment : message.getAttachments().get()) {
712 if (attachment.isPointer()) {
713 attachments.add(m.getAttachmentFile(attachment.asPointer().getId()).getAbsolutePath());
714 }
715 }
716 }
717
718 try {
719 conn.sendSignal(new Signal.MessageReceived(
720 SIGNAL_OBJECTPATH,
721 message.getTimestamp(),
722 envelope.getSource(),
723 message.getGroupInfo().isPresent() ? message.getGroupInfo().get().getGroupId() : new byte[0],
724 message.getBody().isPresent() ? message.getBody().get() : "",
725 attachments));
726 } catch (DBusException e) {
727 e.printStackTrace();
728 }
729 }
730 }
731 }
732
733 private void printAttachment(SignalServiceAttachment attachment) {
734 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
735 if (attachment.isPointer()) {
736 final SignalServiceAttachmentPointer pointer = attachment.asPointer();
737 System.out.println(" Id: " + pointer.getId() + " Key length: " + pointer.getKey().length + (pointer.getRelay().isPresent() ? " Relay: " + pointer.getRelay().get() : ""));
738 System.out.println(" Size: " + (pointer.getSize().isPresent() ? pointer.getSize().get() + " bytes" : "<unavailable>") + (pointer.getPreview().isPresent() ? " (Preview is available: " + pointer.getPreview().get().length + " bytes)" : ""));
739 File file = m.getAttachmentFile(pointer.getId());
740 if (file.exists()) {
741 System.out.println(" Stored plaintext in: " + file);
742 }
743 }
744 }
745 }
746 }