]> nmode's Git Repositories - signal-cli/blob - src/main/java/cli/Main.java
Extract code from main() to functions
[signal-cli] / src / main / java / cli / 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 cli;
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.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
24 import org.whispersystems.textsecure.api.messages.*;
25 import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
26 import org.whispersystems.textsecure.api.push.TextSecureAddress;
27 import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
28 import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
29 import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException;
30 import org.whispersystems.textsecure.api.util.InvalidNumberException;
31
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.nio.file.Files;
37 import java.nio.file.Paths;
38 import java.security.Security;
39 import java.util.ArrayList;
40 import java.util.List;
41
42 public class Main {
43
44 public static void main(String[] args) {
45 // Workaround for BKS truststore
46 Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
47
48 Namespace ns = parseArgs(args);
49 if (ns == null) {
50 System.exit(1);
51 }
52
53 final String username = ns.getString("username");
54 final Manager m = new Manager(username);
55 if (m.userExists()) {
56 try {
57 m.load();
58 } catch (Exception e) {
59 System.out.println("Error loading state file \"" + m.getFileName() + "\": " + e.getMessage());
60 System.exit(2);
61 }
62 }
63
64 switch (ns.getString("command")) {
65 case "register":
66 if (!m.userHasKeys()) {
67 m.createNewIdentity();
68 }
69 try {
70 m.register(ns.getBoolean("voice"));
71 } catch (IOException e) {
72 System.out.println("Request verify error: " + e.getMessage());
73 System.exit(3);
74 }
75 break;
76 case "verify":
77 if (!m.userHasKeys()) {
78 System.out.println("User has no keys, first call register.");
79 System.exit(1);
80 }
81 if (m.isRegistered()) {
82 System.out.println("User registration is already verified");
83 System.exit(1);
84 }
85 try {
86 m.verifyAccount(ns.getString("verificationCode"));
87 } catch (IOException e) {
88 System.out.println("Verify error: " + e.getMessage());
89 System.exit(3);
90 }
91 break;
92 case "send":
93 if (!m.isRegistered()) {
94 System.out.println("User is not registered.");
95 System.exit(1);
96 }
97 String messageText = ns.getString("message");
98 if (messageText == null) {
99 try {
100 messageText = IOUtils.toString(System.in);
101 } catch (IOException e) {
102 System.out.println("Failed to read message from stdin: " + e.getMessage());
103 System.exit(1);
104 }
105 }
106
107 final List<String> attachments = ns.<String>getList("attachment");
108 List<TextSecureAttachment> textSecureAttachments = null;
109 if (attachments != null) {
110 textSecureAttachments = new ArrayList<TextSecureAttachment>(attachments.size());
111 for (String attachment : attachments) {
112 try {
113 File attachmentFile = new File(attachment);
114 InputStream attachmentStream = new FileInputStream(attachmentFile);
115 final long attachmentSize = attachmentFile.length();
116 String mime = Files.probeContentType(Paths.get(attachment));
117 textSecureAttachments.add(new TextSecureAttachmentStream(attachmentStream, mime, attachmentSize, null));
118 } catch (IOException e) {
119 System.out.println("Failed to add attachment \"" + attachment + "\": " + e.getMessage());
120 System.out.println("Aborting sending.");
121 System.exit(1);
122 }
123 }
124 }
125
126 List<TextSecureAddress> recipients = new ArrayList<>(ns.<String>getList("recipient").size());
127 for (String recipient : ns.<String>getList("recipient")) {
128 try {
129 recipients.add(m.getPushAddress(recipient));
130 } catch (InvalidNumberException e) {
131 System.out.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
132 System.out.println("Aborting sending.");
133 System.exit(1);
134 }
135 }
136 sendMessage(m, messageText, textSecureAttachments, recipients);
137 break;
138 case "receive":
139 if (!m.isRegistered()) {
140 System.out.println("User is not registered.");
141 System.exit(1);
142 }
143 try {
144 m.receiveMessages(5, true, new ReceiveMessageHandler(m));
145 } catch (IOException e) {
146 System.out.println("Error while receiving message: " + e.getMessage());
147 }
148 break;
149 }
150 m.save();
151 System.exit(0);
152 }
153
154 private static Namespace parseArgs(String[] args) {
155 ArgumentParser parser = ArgumentParsers.newArgumentParser("textsecure-cli")
156 .defaultHelp(true)
157 .description("Commandline interface for TextSecure.");
158 Subparsers subparsers = parser.addSubparsers()
159 .title("subcommands")
160 .dest("command")
161 .description("valid subcommands")
162 .help("additional help");
163
164 Subparser parserRegister = subparsers.addParser("register");
165 parserRegister.addArgument("-v", "--voice")
166 .help("The verification should be done over voice, not sms.")
167 .action(Arguments.storeTrue());
168
169 Subparser parserVerify = subparsers.addParser("verify");
170 parserVerify.addArgument("verificationCode")
171 .help("The verification code you received via sms or voice call.");
172
173 Subparser parserSend = subparsers.addParser("send");
174 parserSend.addArgument("recipient")
175 .help("Specify the recipients' phone number.")
176 .nargs("*");
177 parserSend.addArgument("-m", "--message")
178 .help("Specify the message, if missing standard input is used.");
179 parserSend.addArgument("-a", "--attachment")
180 .nargs("*")
181 .help("Add file as attachment");
182
183 Subparser parserReceive = subparsers.addParser("receive");
184 parser.addArgument("-u", "--username")
185 .required(true)
186 .help("Specify your phone number, that will be used for verification.");
187
188 try {
189 return parser.parseArgs(args);
190 } catch (ArgumentParserException e) {
191 parser.handleError(e);
192 return null;
193 }
194 }
195
196 private static void sendMessage(Manager m, String messageText, List<TextSecureAttachment> textSecureAttachments,
197 List<TextSecureAddress> recipients) {
198 final TextSecureDataMessage.Builder messageBuilder = TextSecureDataMessage.newBuilder().withBody(messageText);
199 if (textSecureAttachments != null) {
200 messageBuilder.withAttachments(textSecureAttachments);
201 }
202 TextSecureDataMessage message = messageBuilder.build();
203
204 try {
205 m.sendMessage(recipients, message);
206 } catch (IOException e) {
207 System.out.println("Failed to send message: " + e.getMessage());
208 } catch (EncapsulatedExceptions e) {
209 System.out.println("Failed to send (some) messages:");
210 for (NetworkFailureException n : e.getNetworkExceptions()) {
211 System.out.println("Network failure for \"" + n.getE164number() + "\": " + n.getMessage());
212 }
213 for (UnregisteredUserException n : e.getUnregisteredUserExceptions()) {
214 System.out.println("Unregistered user \"" + n.getE164Number() + "\": " + n.getMessage());
215 }
216 for (UntrustedIdentityException n : e.getUntrustedIdentityExceptions()) {
217 System.out.println("Untrusted Identity for \"" + n.getE164Number() + "\": " + n.getMessage());
218 }
219 }
220 }
221
222 private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
223 Manager m;
224
225 public ReceiveMessageHandler(Manager m) {
226 this.m = m;
227 }
228
229 @Override
230 public void handleMessage(TextSecureEnvelope envelope) {
231 System.out.println("Envelope from: " + envelope.getSource());
232 System.out.println("Timestamp: " + envelope.getTimestamp());
233
234 if (envelope.isReceipt()) {
235 System.out.println("Got receipt.");
236 } else if (envelope.isWhisperMessage() | envelope.isPreKeyWhisperMessage()) {
237 TextSecureContent content = m.decryptMessage(envelope);
238
239 if (content == null) {
240 System.out.println("Failed to decrypt message.");
241 } else {
242 if (content.getDataMessage().isPresent()) {
243 TextSecureDataMessage message = content.getDataMessage().get();
244 System.out.println("Body: " + message.getBody().get());
245
246 if (message.isEndSession()) {
247 m.handleEndSession(envelope.getSource());
248 } else if (message.getAttachments().isPresent()) {
249 System.out.println("Attachments: ");
250 for (TextSecureAttachment attachment : message.getAttachments().get()) {
251 System.out.println("- " + attachment.getContentType() + " (" + (attachment.isPointer() ? "Pointer" : "") + (attachment.isStream() ? "Stream" : "") + ")");
252 if (attachment.isPointer()) {
253 System.out.println(" Id: " + attachment.asPointer().getId() + " Key length: " + attachment.asPointer().getKey().length + (attachment.asPointer().getRelay().isPresent() ? " Relay: " + attachment.asPointer().getRelay().get() : ""));
254 }
255 }
256 }
257 }
258 if (content.getSyncMessage().isPresent()) {
259 TextSecureSyncMessage syncMessage = content.getSyncMessage().get();
260 System.out.println("Received sync message");
261 }
262 }
263 } else {
264 System.out.println("Unknown message received.");
265 }
266 System.out.println();
267 }
268 }
269 }