2 * Copyright (C) 2015 AsamK
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.
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.
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/>.
17 package org
.asamk
.signal
;
19 import net
.sourceforge
.argparse4j
.ArgumentParsers
;
20 import net
.sourceforge
.argparse4j
.impl
.Arguments
;
21 import net
.sourceforge
.argparse4j
.inf
.*;
22 import org
.apache
.http
.util
.TextUtils
;
23 import org
.asamk
.Signal
;
24 import org
.freedesktop
.dbus
.DBusConnection
;
25 import org
.freedesktop
.dbus
.DBusSigHandler
;
26 import org
.freedesktop
.dbus
.exceptions
.DBusException
;
27 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
28 import org
.whispersystems
.libsignal
.InvalidKeyException
;
29 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
30 import org
.whispersystems
.signalservice
.api
.messages
.*;
31 import org
.whispersystems
.signalservice
.api
.messages
.multidevice
.DeviceInfo
;
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
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
44 import java
.io
.StringWriter
;
46 import java
.net
.URISyntaxException
;
47 import java
.nio
.charset
.Charset
;
48 import java
.security
.Security
;
49 import java
.util
.ArrayList
;
50 import java
.util
.List
;
51 import java
.util
.Locale
;
53 import java
.util
.concurrent
.TimeoutException
;
57 public static final String SIGNAL_BUSNAME
= "org.asamk.Signal";
58 public static final String SIGNAL_OBJECTPATH
= "/org/asamk/Signal";
60 public static void main(String
[] args
) {
61 // Workaround for BKS truststore
62 Security
.insertProviderAt(new org
.bouncycastle
.jce
.provider
.BouncyCastleProvider(), 1);
64 Namespace ns
= parseArgs(args
);
69 int res
= handleCommands(ns
);
73 private static int handleCommands(Namespace ns
) {
74 final String username
= ns
.getString("username");
77 DBusConnection dBusConn
= null;
79 if (ns
.getBoolean("dbus") || ns
.getBoolean("dbus_system")) {
83 if (ns
.getBoolean("dbus_system")) {
84 busType
= DBusConnection
.SYSTEM
;
86 busType
= DBusConnection
.SESSION
;
88 dBusConn
= DBusConnection
.getConnection(busType
);
89 ts
= (Signal
) dBusConn
.getRemoteObject(
90 SIGNAL_BUSNAME
, SIGNAL_OBJECTPATH
,
92 } catch (DBusException e
) {
94 if (dBusConn
!= null) {
95 dBusConn
.disconnect();
100 String settingsPath
= ns
.getString("config");
101 if (TextUtils
.isEmpty(settingsPath
)) {
102 settingsPath
= System
.getProperty("user.home") + "/.config/signal";
103 if (!new File(settingsPath
).exists()) {
104 String legacySettingsPath
= System
.getProperty("user.home") + "/.config/textsecure";
105 if (new File(legacySettingsPath
).exists()) {
106 settingsPath
= legacySettingsPath
;
111 m
= new Manager(username
, settingsPath
);
113 if (m
.userExists()) {
116 } catch (Exception e
) {
117 System
.err
.println("Error loading state file \"" + m
.getFileName() + "\": " + e
.getMessage());
123 switch (ns
.getString("command")) {
125 if (dBusConn
!= null) {
126 System
.err
.println("register is not yet implemented via dbus");
129 if (!m
.userHasKeys()) {
130 m
.createNewIdentity();
133 m
.register(ns
.getBoolean("voice"));
134 } catch (IOException e
) {
135 System
.err
.println("Request verify error: " + e
.getMessage());
140 if (dBusConn
!= null) {
141 System
.err
.println("verify is not yet implemented via dbus");
144 if (!m
.userHasKeys()) {
145 System
.err
.println("User has no keys, first call register.");
148 if (m
.isRegistered()) {
149 System
.err
.println("User registration is already verified");
153 m
.verifyAccount(ns
.getString("verificationCode"));
154 } catch (IOException e
) {
155 System
.err
.println("Verify error: " + e
.getMessage());
160 if (dBusConn
!= null) {
161 System
.err
.println("link is not yet implemented via dbus");
165 // When linking, username is null and we always have to create keys
166 m
.createNewIdentity();
168 String deviceName
= ns
.getString("name");
169 if (deviceName
== null) {
173 System
.out
.println(m
.getDeviceLinkUri());
174 m
.finishDeviceLink(deviceName
);
175 System
.out
.println("Associated with: " + m
.getUsername());
176 } catch (TimeoutException e
) {
177 System
.err
.println("Link request timed out, please try again.");
179 } catch (IOException e
) {
180 System
.err
.println("Link request error: " + e
.getMessage());
182 } catch (InvalidKeyException e
) {
185 } catch (UserAlreadyExists e
) {
186 System
.err
.println("The user " + e
.getUsername() + " already exists\nDelete \"" + e
.getFileName() + "\" before trying again.");
191 if (dBusConn
!= null) {
192 System
.err
.println("link is not yet implemented via dbus");
195 if (!m
.isRegistered()) {
196 System
.err
.println("User is not registered.");
200 m
.addDeviceLink(new URI(ns
.getString("uri")));
201 } catch (IOException e
) {
204 } catch (InvalidKeyException e
) {
207 } catch (URISyntaxException e
) {
213 if (dBusConn
!= null) {
214 System
.err
.println("listDevices is not yet implemented via dbus");
217 if (!m
.isRegistered()) {
218 System
.err
.println("User is not registered.");
222 List
<DeviceInfo
> devices
= m
.getLinkedDevices();
223 for (DeviceInfo d
: devices
) {
224 System
.out
.println("Device " + d
.getId() + (d
.getId() == m
.getDeviceId() ?
" (this device)" : "") + ":");
225 System
.out
.println(" Name: " + d
.getName());
226 System
.out
.println(" Created: " + d
.getCreated());
227 System
.out
.println(" Last seen: " + d
.getLastSeen());
229 } catch (IOException e
) {
235 if (dBusConn
!= null) {
236 System
.err
.println("removeDevice is not yet implemented via dbus");
239 if (!m
.isRegistered()) {
240 System
.err
.println("User is not registered.");
244 int deviceId
= ns
.getInt("deviceId");
245 m
.removeLinkedDevices(deviceId
);
246 } catch (IOException e
) {
252 if (dBusConn
== null && !m
.isRegistered()) {
253 System
.err
.println("User is not registered.");
257 if (ns
.getBoolean("endsession")) {
258 if (ns
.getList("recipient") == null) {
259 System
.err
.println("No recipients given");
260 System
.err
.println("Aborting sending.");
264 ts
.sendEndSessionMessage(ns
.<String
>getList("recipient"));
265 } catch (IOException e
) {
266 handleIOException(e
);
268 } catch (EncapsulatedExceptions e
) {
269 handleEncapsulatedExceptions(e
);
271 } catch (AssertionError e
) {
272 handleAssertionError(e
);
274 } catch (DBusExecutionException e
) {
275 handleDBusExecutionException(e
);
279 String messageText
= ns
.getString("message");
280 if (messageText
== null) {
282 messageText
= readAll(System
.in);
283 } catch (IOException e
) {
284 System
.err
.println("Failed to read message from stdin: " + e
.getMessage());
285 System
.err
.println("Aborting sending.");
291 List
<String
> attachments
= ns
.getList("attachment");
292 if (attachments
== null) {
293 attachments
= new ArrayList
<>();
295 if (ns
.getString("group") != null) {
296 byte[] groupId
= decodeGroupId(ns
.getString("group"));
297 ts
.sendGroupMessage(messageText
, attachments
, groupId
);
299 ts
.sendMessage(messageText
, attachments
, ns
.<String
>getList("recipient"));
301 } catch (IOException e
) {
302 handleIOException(e
);
304 } catch (EncapsulatedExceptions e
) {
305 handleEncapsulatedExceptions(e
);
307 } catch (AssertionError e
) {
308 handleAssertionError(e
);
310 } catch (GroupNotFoundException e
) {
311 handleGroupNotFoundException(e
);
313 } catch (AttachmentInvalidException e
) {
314 System
.err
.println("Failed to add attachment: " + e
.getMessage());
315 System
.err
.println("Aborting sending.");
317 } catch (DBusExecutionException e
) {
318 handleDBusExecutionException(e
);
325 if (dBusConn
!= null) {
327 dBusConn
.addSigHandler(Signal
.MessageReceived
.class, new DBusSigHandler
<Signal
.MessageReceived
>() {
329 public void handle(Signal
.MessageReceived s
) {
330 System
.out
.print(String
.format("Envelope from: %s\nTimestamp: %d\nBody: %s\n",
331 s
.getSender(), s
.getTimestamp(), s
.getMessage()));
332 if (s
.getGroupId().length
> 0) {
333 System
.out
.println("Group info:");
334 System
.out
.println(" Id: " + Base64
.encodeBytes(s
.getGroupId()));
336 if (s
.getAttachments().size() > 0) {
337 System
.out
.println("Attachments: ");
338 for (String attachment
: s
.getAttachments()) {
339 System
.out
.println("- Stored plaintext in: " + attachment
);
342 System
.out
.println();
345 } catch (DBusException e
) {
352 } catch (InterruptedException e
) {
357 if (!m
.isRegistered()) {
358 System
.err
.println("User is not registered.");
362 if (ns
.getInt("timeout") != null) {
363 timeout
= ns
.getInt("timeout");
365 boolean returnOnTimeout
= true;
367 returnOnTimeout
= false;
371 m
.receiveMessages(timeout
, returnOnTimeout
, new ReceiveMessageHandler(m
));
372 } catch (IOException e
) {
373 System
.err
.println("Error while receiving messages: " + e
.getMessage());
375 } catch (AssertionError e
) {
376 handleAssertionError(e
);
381 if (dBusConn
!= null) {
382 System
.err
.println("quitGroup is not yet implemented via dbus");
385 if (!m
.isRegistered()) {
386 System
.err
.println("User is not registered.");
391 m
.sendQuitGroupMessage(decodeGroupId(ns
.getString("group")));
392 } catch (IOException e
) {
393 handleIOException(e
);
395 } catch (EncapsulatedExceptions e
) {
396 handleEncapsulatedExceptions(e
);
398 } catch (AssertionError e
) {
399 handleAssertionError(e
);
401 } catch (GroupNotFoundException e
) {
402 handleGroupNotFoundException(e
);
408 if (dBusConn
!= null) {
409 System
.err
.println("updateGroup is not yet implemented via dbus");
412 if (!m
.isRegistered()) {
413 System
.err
.println("User is not registered.");
418 byte[] groupId
= null;
419 if (ns
.getString("group") != null) {
420 groupId
= decodeGroupId(ns
.getString("group"));
422 byte[] newGroupId
= m
.sendUpdateGroupMessage(groupId
, ns
.getString("name"), ns
.<String
>getList("member"), ns
.getString("avatar"));
423 if (groupId
== null) {
424 System
.out
.println("Creating new group \"" + Base64
.encodeBytes(newGroupId
) + "\" …");
426 } catch (IOException e
) {
427 handleIOException(e
);
429 } catch (AttachmentInvalidException e
) {
430 System
.err
.println("Failed to add avatar attachment for group\": " + e
.getMessage());
431 System
.err
.println("Aborting sending.");
433 } catch (GroupNotFoundException e
) {
434 handleGroupNotFoundException(e
);
436 } catch (EncapsulatedExceptions e
) {
437 handleEncapsulatedExceptions(e
);
442 case "listIdentities":
443 if (dBusConn
!= null) {
444 System
.err
.println("listIdentities is not yet implemented via dbus");
447 if (!m
.isRegistered()) {
448 System
.err
.println("User is not registered.");
451 if (ns
.get("number") == null) {
452 for (Map
.Entry
<String
, List
<JsonIdentityKeyStore
.Identity
>> keys
: m
.getIdentities().entrySet()) {
453 for (JsonIdentityKeyStore
.Identity id
: keys
.getValue()) {
454 System
.out
.println(String
.format("%s: %s Added: %s Fingerprint: %s", keys
.getKey(), id
.trustLevel
, id
.added
, Hex
.toStringCondensed(id
.getFingerprint())));
458 String number
= ns
.getString("number");
459 for (JsonIdentityKeyStore
.Identity id
: m
.getIdentities(number
)) {
460 System
.out
.println(String
.format("%s: %s Added: %s Fingerprint: %s", number
, id
.trustLevel
, id
.added
, Hex
.toStringCondensed(id
.getFingerprint())));
465 if (dBusConn
!= null) {
466 System
.err
.println("trust is not yet implemented via dbus");
469 if (!m
.isRegistered()) {
470 System
.err
.println("User is not registered.");
473 String number
= ns
.getString("number");
474 if (ns
.getBoolean("trust_all_known_keys")) {
475 boolean res
= m
.trustIdentityAllKeys(number
);
477 System
.err
.println("Failed to set the trust for this number, make sure the number is correct.");
481 String fingerprint
= ns
.getString("verified_fingerprint");
482 if (fingerprint
!= null) {
483 byte[] fingerprintBytes
;
485 fingerprintBytes
= Hex
.toByteArray(fingerprint
.replaceAll(" ", "").toLowerCase(Locale
.ROOT
));
486 } catch (Exception e
) {
487 System
.err
.println("Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
490 boolean res
= m
.trustIdentityVerified(number
, fingerprintBytes
);
492 System
.err
.println("Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
496 System
.err
.println("You need to specify the fingerprint you have verified with -v FINGERPRINT");
502 if (dBusConn
!= null) {
503 System
.err
.println("Stop it.");
506 if (!m
.isRegistered()) {
507 System
.err
.println("User is not registered.");
510 DBusConnection conn
= null;
514 if (ns
.getBoolean("system")) {
515 busType
= DBusConnection
.SYSTEM
;
517 busType
= DBusConnection
.SESSION
;
519 conn
= DBusConnection
.getConnection(busType
);
520 conn
.exportObject(SIGNAL_OBJECTPATH
, m
);
521 conn
.requestBusName(SIGNAL_BUSNAME
);
522 } catch (DBusException e
) {
527 m
.receiveMessages(3600, false, new DbusReceiveMessageHandler(m
, conn
));
528 } catch (IOException e
) {
529 System
.err
.println("Error while receiving messages: " + e
.getMessage());
531 } catch (AssertionError e
) {
532 handleAssertionError(e
);
545 if (dBusConn
!= null) {
546 dBusConn
.disconnect();
551 private static void handleGroupNotFoundException(GroupNotFoundException e
) {
552 System
.err
.println("Failed to send to group: " + e
.getMessage());
553 System
.err
.println("Aborting sending.");
556 private static void handleDBusExecutionException(DBusExecutionException e
) {
557 System
.err
.println("Cannot connect to dbus: " + e
.getMessage());
558 System
.err
.println("Aborting.");
561 private static byte[] decodeGroupId(String groupId
) {
563 return Base64
.decode(groupId
);
564 } catch (IOException e
) {
565 System
.err
.println("Failed to decode groupId (must be base64) \"" + groupId
+ "\": " + e
.getMessage());
566 System
.err
.println("Aborting sending.");
572 private static Namespace
parseArgs(String
[] args
) {
573 ArgumentParser parser
= ArgumentParsers
.newArgumentParser("signal-cli")
575 .description("Commandline interface for Signal.")
576 .version(Manager
.PROJECT_NAME
+ " " + Manager
.PROJECT_VERSION
);
578 parser
.addArgument("-v", "--version")
579 .help("Show package version.")
580 .action(Arguments
.version());
581 parser
.addArgument("--config")
582 .help("Set the path, where to store the config (Default: $HOME/.config/signal).");
584 MutuallyExclusiveGroup mut
= parser
.addMutuallyExclusiveGroup();
585 mut
.addArgument("-u", "--username")
586 .help("Specify your phone number, that will be used for verification.");
587 mut
.addArgument("--dbus")
588 .help("Make request via user dbus.")
589 .action(Arguments
.storeTrue());
590 mut
.addArgument("--dbus-system")
591 .help("Make request via system dbus.")
592 .action(Arguments
.storeTrue());
594 Subparsers subparsers
= parser
.addSubparsers()
595 .title("subcommands")
597 .description("valid subcommands")
598 .help("additional help");
600 Subparser parserLink
= subparsers
.addParser("link");
601 parserLink
.addArgument("-n", "--name")
602 .help("Specify a name to describe this new device.");
604 Subparser parserAddDevice
= subparsers
.addParser("addDevice");
605 parserAddDevice
.addArgument("--uri")
607 .help("Specify the uri contained in the QR code shown by the new device.");
609 Subparser parserDevices
= subparsers
.addParser("listDevices");
611 Subparser parserRemoveDevice
= subparsers
.addParser("removeDevice");
612 parserRemoveDevice
.addArgument("-d", "--deviceId")
615 .help("Specify the device you want to remove. Use listDevices to see the deviceIds.");
617 Subparser parserRegister
= subparsers
.addParser("register");
618 parserRegister
.addArgument("-v", "--voice")
619 .help("The verification should be done over voice, not sms.")
620 .action(Arguments
.storeTrue());
622 Subparser parserVerify
= subparsers
.addParser("verify");
623 parserVerify
.addArgument("verificationCode")
624 .help("The verification code you received via sms or voice call.");
626 Subparser parserSend
= subparsers
.addParser("send");
627 parserSend
.addArgument("-g", "--group")
628 .help("Specify the recipient group ID.");
629 parserSend
.addArgument("recipient")
630 .help("Specify the recipients' phone number.")
632 parserSend
.addArgument("-m", "--message")
633 .help("Specify the message, if missing standard input is used.");
634 parserSend
.addArgument("-a", "--attachment")
636 .help("Add file as attachment");
637 parserSend
.addArgument("-e", "--endsession")
638 .help("Clear session state and send end session message.")
639 .action(Arguments
.storeTrue());
641 Subparser parserLeaveGroup
= subparsers
.addParser("quitGroup");
642 parserLeaveGroup
.addArgument("-g", "--group")
644 .help("Specify the recipient group ID.");
646 Subparser parserUpdateGroup
= subparsers
.addParser("updateGroup");
647 parserUpdateGroup
.addArgument("-g", "--group")
648 .help("Specify the recipient group ID.");
649 parserUpdateGroup
.addArgument("-n", "--name")
650 .help("Specify the new group name.");
651 parserUpdateGroup
.addArgument("-a", "--avatar")
652 .help("Specify a new group avatar image file");
653 parserUpdateGroup
.addArgument("-m", "--member")
655 .help("Specify one or more members to add to the group");
657 Subparser parserListIdentities
= subparsers
.addParser("listIdentities");
658 parserListIdentities
.addArgument("-n", "--number")
659 .help("Only show identity keys for the given phone number.");
661 Subparser parserTrust
= subparsers
.addParser("trust");
662 parserTrust
.addArgument("number")
663 .help("Specify the phone number, for which to set the trust.")
665 MutuallyExclusiveGroup mutTrust
= parserTrust
.addMutuallyExclusiveGroup();
666 mutTrust
.addArgument("-a", "--trust-all-known-keys")
667 .help("Trust all known keys of this user, only use this for testing.")
668 .action(Arguments
.storeTrue());
669 mutTrust
.addArgument("-v", "--verified-fingerprint")
670 .help("Specify the fingerprint of the key, only use this option if you have verified the fingerprint.");
672 Subparser parserReceive
= subparsers
.addParser("receive");
673 parserReceive
.addArgument("-t", "--timeout")
675 .help("Number of seconds to wait for new messages (negative values disable timeout)");
677 Subparser parserDaemon
= subparsers
.addParser("daemon");
678 parserDaemon
.addArgument("--system")
679 .action(Arguments
.storeTrue())
680 .help("Use DBus system bus instead of user bus.");
683 Namespace ns
= parser
.parseArgs(args
);
684 if ("link".equals(ns
.getString("command"))) {
685 if (ns
.getString("username") != null) {
687 System
.err
.println("You cannot specify a username (phone number) when linking");
690 } else if (!ns
.getBoolean("dbus") && !ns
.getBoolean("dbus_system")) {
691 if (ns
.getString("username") == null) {
693 System
.err
.println("You need to specify a username (phone number)");
696 if (!PhoneNumberFormatter
.isValidNumber(ns
.getString("username"))) {
697 System
.err
.println("Invalid username (phone number), make sure you include the country code.");
701 if (ns
.getList("recipient") != null && !ns
.getList("recipient").isEmpty() && ns
.getString("group") != null) {
702 System
.err
.println("You cannot specify recipients by phone number and groups a the same time");
706 } catch (ArgumentParserException e
) {
707 parser
.handleError(e
);
712 private static void handleAssertionError(AssertionError e
) {
713 System
.err
.println("Failed to send/receive message (Assertion): " + e
.getMessage());
715 System
.err
.println("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
718 private static void handleEncapsulatedExceptions(EncapsulatedExceptions e
) {
719 System
.err
.println("Failed to send (some) messages:");
720 for (NetworkFailureException n
: e
.getNetworkExceptions()) {
721 System
.err
.println("Network failure for \"" + n
.getE164number() + "\": " + n
.getMessage());
723 for (UnregisteredUserException n
: e
.getUnregisteredUserExceptions()) {
724 System
.err
.println("Unregistered user \"" + n
.getE164Number() + "\": " + n
.getMessage());
726 for (UntrustedIdentityException n
: e
.getUntrustedIdentityExceptions()) {
727 System
.err
.println("Untrusted Identity for \"" + n
.getE164Number() + "\": " + n
.getMessage());
731 private static void handleIOException(IOException e
) {
732 System
.err
.println("Failed to send message: " + e
.getMessage());
735 private static String
readAll(InputStream
in) throws IOException
{
736 StringWriter output
= new StringWriter();
737 byte[] buffer
= new byte[4096];
740 while (-1 != (n
= System
.in.read(buffer
))) {
741 output
.write(new String(buffer
, 0, n
, Charset
.defaultCharset()));
744 return output
.toString();
747 private static class ReceiveMessageHandler
implements Manager
.ReceiveMessageHandler
{
750 public ReceiveMessageHandler(Manager m
) {
755 public void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
756 SignalServiceAddress source
= envelope
.getSourceAddress();
757 ContactInfo sourceContact
= m
.getContact(source
.getNumber());
758 System
.out
.println(String
.format("Envelope from: %s (device: %d)", (sourceContact
== null ?
"" : "“" + sourceContact
.name
+ "” ") + source
.getNumber(), envelope
.getSourceDevice()));
759 if (source
.getRelay().isPresent()) {
760 System
.out
.println("Relayed by: " + source
.getRelay().get());
762 System
.out
.println("Timestamp: " + envelope
.getTimestamp());
764 if (envelope
.isReceipt()) {
765 System
.out
.println("Got receipt.");
766 } else if (envelope
.isSignalMessage() | envelope
.isPreKeySignalMessage()) {
767 if (content
== null) {
768 System
.out
.println("Failed to decrypt message.");
770 if (content
.getDataMessage().isPresent()) {
771 SignalServiceDataMessage message
= content
.getDataMessage().get();
772 handleSignalServiceDataMessage(message
);
774 if (content
.getSyncMessage().isPresent()) {
775 System
.out
.println("Received a sync message");
776 SignalServiceSyncMessage syncMessage
= content
.getSyncMessage().get();
778 if (syncMessage
.getContacts().isPresent()) {
779 System
.out
.println("Received sync contacts");
780 printAttachment(syncMessage
.getContacts().get());
782 if (syncMessage
.getGroups().isPresent()) {
783 System
.out
.println("Received sync groups");
784 printAttachment(syncMessage
.getGroups().get());
786 if (syncMessage
.getRead().isPresent()) {
787 System
.out
.println("Received sync read messages list");
788 for (ReadMessage rm
: syncMessage
.getRead().get()) {
789 ContactInfo fromContact
= m
.getContact(rm
.getSender());
790 System
.out
.println("From: " + (fromContact
== null ?
"" : "“" + fromContact
.name
+ "” ") + rm
.getSender() + " Message timestamp: " + rm
.getTimestamp());
793 if (syncMessage
.getRequest().isPresent()) {
794 System
.out
.println("Received sync request");
795 if (syncMessage
.getRequest().get().isContactsRequest()) {
796 System
.out
.println(" - contacts request");
798 if (syncMessage
.getRequest().get().isGroupsRequest()) {
799 System
.out
.println(" - groups request");
802 if (syncMessage
.getSent().isPresent()) {
803 System
.out
.println("Received sync sent message");
804 final SentTranscriptMessage sentTranscriptMessage
= syncMessage
.getSent().get();
806 if (sentTranscriptMessage
.getDestination().isPresent()) {
807 String dest
= sentTranscriptMessage
.getDestination().get();
808 ContactInfo destContact
= m
.getContact(dest
);
809 to = (destContact
== null ?
"" : "“" + destContact
.name
+ "” ") + dest
;
813 System
.out
.println("To: " + to + " , Message timestamp: " + sentTranscriptMessage
.getTimestamp());
814 SignalServiceDataMessage message
= sentTranscriptMessage
.getMessage();
815 handleSignalServiceDataMessage(message
);
820 System
.out
.println("Unknown message received.");
822 System
.out
.println();
825 private void handleSignalServiceDataMessage(SignalServiceDataMessage message
) {
826 System
.out
.println("Message timestamp: " + message
.getTimestamp());
828 if (message
.getBody().isPresent()) {
829 System
.out
.println("Body: " + message
.getBody().get());
831 if (message
.getGroupInfo().isPresent()) {
832 SignalServiceGroup groupInfo
= message
.getGroupInfo().get();
833 System
.out
.println("Group info:");
834 System
.out
.println(" Id: " + Base64
.encodeBytes(groupInfo
.getGroupId()));
835 if (groupInfo
.getType() == SignalServiceGroup
.Type
.UPDATE
&& groupInfo
.getName().isPresent()) {
836 System
.out
.println(" Name: " + groupInfo
.getName().get());
838 GroupInfo group
= m
.getGroup(groupInfo
.getGroupId());
840 System
.out
.println(" Name: " + group
.name
);
842 System
.out
.println(" Name: <Unknown group>");
845 System
.out
.println(" Type: " + groupInfo
.getType());
846 if (groupInfo
.getMembers().isPresent()) {
847 for (String member
: groupInfo
.getMembers().get()) {
848 System
.out
.println(" Member: " + member
);
851 if (groupInfo
.getAvatar().isPresent()) {
852 System
.out
.println(" Avatar:");
853 printAttachment(groupInfo
.getAvatar().get());
856 if (message
.isEndSession()) {
857 System
.out
.println("Is end session");
860 if (message
.getAttachments().isPresent()) {
861 System
.out
.println("Attachments: ");
862 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
863 printAttachment(attachment
);
868 private void printAttachment(SignalServiceAttachment attachment
) {
869 System
.out
.println("- " + attachment
.getContentType() + " (" + (attachment
.isPointer() ?
"Pointer" : "") + (attachment
.isStream() ?
"Stream" : "") + ")");
870 if (attachment
.isPointer()) {
871 final SignalServiceAttachmentPointer pointer
= attachment
.asPointer();
872 System
.out
.println(" Id: " + pointer
.getId() + " Key length: " + pointer
.getKey().length
+ (pointer
.getRelay().isPresent() ?
" Relay: " + pointer
.getRelay().get() : ""));
873 System
.out
.println(" Size: " + (pointer
.getSize().isPresent() ? pointer
.getSize().get() + " bytes" : "<unavailable>") + (pointer
.getPreview().isPresent() ?
" (Preview is available: " + pointer
.getPreview().get().length
+ " bytes)" : ""));
874 File file
= m
.getAttachmentFile(pointer
.getId());
876 System
.out
.println(" Stored plaintext in: " + file
);
882 private static class DbusReceiveMessageHandler
extends ReceiveMessageHandler
{
883 final DBusConnection conn
;
885 public DbusReceiveMessageHandler(Manager m
, DBusConnection conn
) {
891 public void handleMessage(SignalServiceEnvelope envelope
, SignalServiceContent content
) {
892 super.handleMessage(envelope
, content
);
894 if (!envelope
.isReceipt() && content
!= null && content
.getDataMessage().isPresent()) {
895 SignalServiceDataMessage message
= content
.getDataMessage().get();
897 if (!message
.isEndSession() &&
898 !(message
.getGroupInfo().isPresent() &&
899 message
.getGroupInfo().get().getType() != SignalServiceGroup
.Type
.DELIVER
)) {
900 List
<String
> attachments
= new ArrayList
<>();
901 if (message
.getAttachments().isPresent()) {
902 for (SignalServiceAttachment attachment
: message
.getAttachments().get()) {
903 if (attachment
.isPointer()) {
904 attachments
.add(m
.getAttachmentFile(attachment
.asPointer().getId()).getAbsolutePath());
910 conn
.sendSignal(new Signal
.MessageReceived(
912 message
.getTimestamp(),
913 envelope
.getSource(),
914 message
.getGroupInfo().isPresent() ? message
.getGroupInfo().get().getGroupId() : new byte[0],
915 message
.getBody().isPresent() ? message
.getBody().get() : "",
917 } catch (DBusException e
) {