]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Strip url scheme from captcha string
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusSignalImpl.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.Signal;
4 import org.asamk.signal.BaseConfig;
5 import org.asamk.signal.manager.AttachmentInvalidException;
6 import org.asamk.signal.manager.Manager;
7 import org.asamk.signal.manager.NotMasterDeviceException;
8 import org.asamk.signal.manager.groups.GroupId;
9 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
10 import org.asamk.signal.manager.groups.GroupNotFoundException;
11 import org.asamk.signal.manager.groups.LastGroupAdminException;
12 import org.asamk.signal.manager.groups.NotAGroupMemberException;
13 import org.asamk.signal.manager.storage.identities.IdentityInfo;
14 import org.asamk.signal.util.ErrorUtils;
15 import org.asamk.signal.util.Util;
16 import org.freedesktop.dbus.exceptions.DBusExecutionException;
17 import org.whispersystems.libsignal.util.Pair;
18 import org.whispersystems.libsignal.util.guava.Optional;
19 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
20 import org.whispersystems.signalservice.api.messages.SendMessageResult;
21 import org.whispersystems.signalservice.api.util.InvalidNumberException;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import java.util.stream.Stream;
31
32 import static org.asamk.signal.util.Util.getLegacyIdentifier;
33
34 public class DbusSignalImpl implements Signal {
35
36 private final Manager m;
37
38 public DbusSignalImpl(final Manager m) {
39 this.m = m;
40 }
41
42 @Override
43 public boolean isRemote() {
44 return false;
45 }
46
47 @Override
48 public String getObjectPath() {
49 return null;
50 }
51
52 @Override
53 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
54 var recipients = new ArrayList<String>(1);
55 recipients.add(recipient);
56 return sendMessage(message, attachments, recipients);
57 }
58
59 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
60 var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
61
62 if (error == null) {
63 return;
64 }
65
66 final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
67
68 if (result.getIdentityFailure() != null) {
69 throw new Error.UntrustedIdentity(message);
70 } else {
71 throw new Error.Failure(message);
72 }
73 }
74
75 private static void checkSendMessageResults(
76 long timestamp, List<SendMessageResult> results
77 ) throws DBusExecutionException {
78 if (results.size() == 1) {
79 checkSendMessageResult(timestamp, results.get(0));
80 return;
81 }
82
83 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
84 if (errors.size() == 0) {
85 return;
86 }
87
88 var message = new StringBuilder();
89 message.append(timestamp).append('\n');
90 message.append("Failed to send (some) messages:\n");
91 for (var error : errors) {
92 message.append(error).append('\n');
93 }
94
95 throw new Error.Failure(message.toString());
96 }
97
98 @Override
99 public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
100 try {
101 final var results = m.sendMessage(message, attachments, recipients);
102 checkSendMessageResults(results.first(), results.second());
103 return results.first();
104 } catch (InvalidNumberException e) {
105 throw new Error.InvalidNumber(e.getMessage());
106 } catch (AttachmentInvalidException e) {
107 throw new Error.AttachmentInvalid(e.getMessage());
108 } catch (IOException e) {
109 throw new Error.Failure(e.getMessage());
110 }
111 }
112
113 @Override
114 public long sendRemoteDeleteMessage(
115 final long targetSentTimestamp, final String recipient
116 ) {
117 var recipients = new ArrayList<String>(1);
118 recipients.add(recipient);
119 return sendRemoteDeleteMessage(targetSentTimestamp, recipients);
120 }
121
122 @Override
123 public long sendRemoteDeleteMessage(
124 final long targetSentTimestamp, final List<String> recipients
125 ) {
126 try {
127 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, recipients);
128 checkSendMessageResults(results.first(), results.second());
129 return results.first();
130 } catch (IOException e) {
131 throw new Error.Failure(e.getMessage());
132 } catch (InvalidNumberException e) {
133 throw new Error.InvalidNumber(e.getMessage());
134 }
135 }
136
137 @Override
138 public long sendGroupRemoteDeleteMessage(
139 final long targetSentTimestamp, final byte[] groupId
140 ) {
141 try {
142 final var results = m.sendGroupRemoteDeleteMessage(targetSentTimestamp, GroupId.unknownVersion(groupId));
143 checkSendMessageResults(results.first(), results.second());
144 return results.first();
145 } catch (IOException e) {
146 throw new Error.Failure(e.getMessage());
147 } catch (GroupNotFoundException | NotAGroupMemberException e) {
148 throw new Error.GroupNotFound(e.getMessage());
149 }
150 }
151
152 @Override
153 public long sendMessageReaction(
154 final String emoji,
155 final boolean remove,
156 final String targetAuthor,
157 final long targetSentTimestamp,
158 final String recipient
159 ) {
160 var recipients = new ArrayList<String>(1);
161 recipients.add(recipient);
162 return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
163 }
164
165 @Override
166 public long sendMessageReaction(
167 final String emoji,
168 final boolean remove,
169 final String targetAuthor,
170 final long targetSentTimestamp,
171 final List<String> recipients
172 ) {
173 try {
174 final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
175 checkSendMessageResults(results.first(), results.second());
176 return results.first();
177 } catch (InvalidNumberException e) {
178 throw new Error.InvalidNumber(e.getMessage());
179 } catch (IOException e) {
180 throw new Error.Failure(e.getMessage());
181 }
182 }
183
184 @Override
185 public long sendNoteToSelfMessage(
186 final String message, final List<String> attachments
187 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
188 try {
189 final var results = m.sendSelfMessage(message, attachments);
190 checkSendMessageResult(results.first(), results.second());
191 return results.first();
192 } catch (AttachmentInvalidException e) {
193 throw new Error.AttachmentInvalid(e.getMessage());
194 } catch (IOException e) {
195 throw new Error.Failure(e.getMessage());
196 }
197 }
198
199 @Override
200 public void sendEndSessionMessage(final List<String> recipients) {
201 try {
202 final var results = m.sendEndSessionMessage(recipients);
203 checkSendMessageResults(results.first(), results.second());
204 } catch (IOException e) {
205 throw new Error.Failure(e.getMessage());
206 } catch (InvalidNumberException e) {
207 throw new Error.InvalidNumber(e.getMessage());
208 }
209 }
210
211 @Override
212 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
213 try {
214 var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId));
215 checkSendMessageResults(results.first(), results.second());
216 return results.first();
217 } catch (IOException e) {
218 throw new Error.Failure(e.getMessage());
219 } catch (GroupNotFoundException | NotAGroupMemberException e) {
220 throw new Error.GroupNotFound(e.getMessage());
221 } catch (AttachmentInvalidException e) {
222 throw new Error.AttachmentInvalid(e.getMessage());
223 }
224 }
225
226 @Override
227 public long sendGroupMessageReaction(
228 final String emoji,
229 final boolean remove,
230 final String targetAuthor,
231 final long targetSentTimestamp,
232 final byte[] groupId
233 ) {
234 try {
235 final var results = m.sendGroupMessageReaction(emoji,
236 remove,
237 targetAuthor,
238 targetSentTimestamp,
239 GroupId.unknownVersion(groupId));
240 checkSendMessageResults(results.first(), results.second());
241 return results.first();
242 } catch (IOException e) {
243 throw new Error.Failure(e.getMessage());
244 } catch (InvalidNumberException e) {
245 throw new Error.InvalidNumber(e.getMessage());
246 } catch (GroupNotFoundException | NotAGroupMemberException e) {
247 throw new Error.GroupNotFound(e.getMessage());
248 }
249 }
250
251 // Since contact names might be empty if not defined, also potentially return
252 // the profile name
253 @Override
254 public String getContactName(final String number) {
255 try {
256 return m.getContactOrProfileName(number);
257 } catch (InvalidNumberException e) {
258 throw new Error.InvalidNumber(e.getMessage());
259 }
260 }
261
262 @Override
263 public void setContactName(final String number, final String name) {
264 try {
265 m.setContactName(number, name);
266 } catch (InvalidNumberException e) {
267 throw new Error.InvalidNumber(e.getMessage());
268 } catch (NotMasterDeviceException e) {
269 throw new Error.Failure("This command doesn't work on linked devices.");
270 }
271 }
272
273 @Override
274 public void setContactBlocked(final String number, final boolean blocked) {
275 try {
276 m.setContactBlocked(number, blocked);
277 } catch (InvalidNumberException e) {
278 throw new Error.InvalidNumber(e.getMessage());
279 } catch (NotMasterDeviceException e) {
280 throw new Error.Failure("This command doesn't work on linked devices.");
281 }
282 }
283
284 @Override
285 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
286 try {
287 m.setGroupBlocked(GroupId.unknownVersion(groupId), blocked);
288 } catch (GroupNotFoundException e) {
289 throw new Error.GroupNotFound(e.getMessage());
290 }
291 }
292
293 @Override
294 public List<byte[]> getGroupIds() {
295 var groups = m.getGroups();
296 var ids = new ArrayList<byte[]>(groups.size());
297 for (var group : groups) {
298 ids.add(group.getGroupId().serialize());
299 }
300 return ids;
301 }
302
303 @Override
304 public String getGroupName(final byte[] groupId) {
305 var group = m.getGroup(GroupId.unknownVersion(groupId));
306 if (group == null) {
307 return "";
308 } else {
309 return group.getTitle();
310 }
311 }
312
313 @Override
314 public List<String> getGroupMembers(final byte[] groupId) {
315 var group = m.getGroup(GroupId.unknownVersion(groupId));
316 if (group == null) {
317 return List.of();
318 } else {
319 return group.getMembers()
320 .stream()
321 .map(m::resolveSignalServiceAddress)
322 .map(Util::getLegacyIdentifier)
323 .collect(Collectors.toList());
324 }
325 }
326
327 @Override
328 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
329 try {
330 if (groupId.length == 0) {
331 groupId = null;
332 }
333 if (name.isEmpty()) {
334 name = null;
335 }
336 if (members.isEmpty()) {
337 members = null;
338 }
339 if (avatar.isEmpty()) {
340 avatar = null;
341 }
342 if (groupId == null) {
343 final var results = m.createGroup(name, members, avatar == null ? null : new File(avatar));
344 checkSendMessageResults(0, results.second());
345 return results.first().serialize();
346 } else {
347 final var results = m.updateGroup(GroupId.unknownVersion(groupId),
348 name,
349 null,
350 members,
351 null,
352 null,
353 null,
354 false,
355 null,
356 null,
357 null,
358 avatar == null ? null : new File(avatar),
359 null);
360 if (results != null) {
361 checkSendMessageResults(results.first(), results.second());
362 }
363 return groupId;
364 }
365 } catch (IOException e) {
366 throw new Error.Failure(e.getMessage());
367 } catch (GroupNotFoundException | NotAGroupMemberException e) {
368 throw new Error.GroupNotFound(e.getMessage());
369 } catch (InvalidNumberException e) {
370 throw new Error.InvalidNumber(e.getMessage());
371 } catch (AttachmentInvalidException e) {
372 throw new Error.AttachmentInvalid(e.getMessage());
373 }
374 }
375
376 @Override
377 public boolean isRegistered() {
378 return true;
379 }
380
381 @Override
382 public void updateProfile(
383 final String name,
384 final String about,
385 final String aboutEmoji,
386 String avatarPath,
387 final boolean removeAvatar
388 ) {
389 try {
390 if (avatarPath.isEmpty()) {
391 avatarPath = null;
392 }
393 Optional<File> avatarFile = removeAvatar
394 ? Optional.absent()
395 : avatarPath == null ? null : Optional.of(new File(avatarPath));
396 m.setProfile(name, null, about, aboutEmoji, avatarFile);
397 } catch (IOException e) {
398 throw new Error.Failure(e.getMessage());
399 }
400 }
401
402 // Provide option to query a version string in order to react on potential
403 // future interface changes
404 @Override
405 public String version() {
406 return BaseConfig.PROJECT_VERSION;
407 }
408
409 // Create a unique list of Numbers from Identities and Contacts to really get
410 // all numbers the system knows
411 @Override
412 public List<String> listNumbers() {
413 return Stream.concat(m.getIdentities().stream().map(IdentityInfo::getRecipientId),
414 m.getContacts().stream().map(Pair::first))
415 .map(m::resolveSignalServiceAddress)
416 .map(a -> a.getNumber().orNull())
417 .filter(Objects::nonNull)
418 .distinct()
419 .collect(Collectors.toList());
420 }
421
422 @Override
423 public List<String> getContactNumber(final String name) {
424 // Contact names have precedence.
425 var numbers = new ArrayList<String>();
426 var contacts = m.getContacts();
427 for (var c : contacts) {
428 if (name.equals(c.second().getName())) {
429 numbers.add(getLegacyIdentifier(m.resolveSignalServiceAddress(c.first())));
430 }
431 }
432 // Try profiles if no contact name was found
433 for (var identity : m.getIdentities()) {
434 final var recipientId = identity.getRecipientId();
435 final var address = m.resolveSignalServiceAddress(recipientId);
436 var number = address.getNumber().orNull();
437 if (number != null) {
438 var profile = m.getRecipientProfile(recipientId);
439 if (profile != null && profile.getDisplayName().equals(name)) {
440 numbers.add(number);
441 }
442 }
443 }
444 return numbers;
445 }
446
447 @Override
448 public void quitGroup(final byte[] groupId) {
449 var group = GroupId.unknownVersion(groupId);
450 try {
451 m.sendQuitGroupMessage(group, Set.of());
452 } catch (GroupNotFoundException | NotAGroupMemberException e) {
453 throw new Error.GroupNotFound(e.getMessage());
454 } catch (IOException | LastGroupAdminException e) {
455 throw new Error.Failure(e.getMessage());
456 } catch (InvalidNumberException e) {
457 throw new Error.InvalidNumber(e.getMessage());
458 }
459 }
460
461 @Override
462 public void joinGroup(final String groupLink) {
463 try {
464 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
465 if (linkUrl == null) {
466 throw new Error.Failure("Group link is invalid:");
467 }
468 m.joinGroup(linkUrl);
469 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
470 throw new Error.Failure("Group link is invalid: " + e.getMessage());
471 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
472 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
473 } catch (IOException e) {
474 throw new Error.Failure(e.getMessage());
475 }
476 }
477
478 @Override
479 public boolean isContactBlocked(final String number) {
480 try {
481 return m.isContactBlocked(number);
482 } catch (InvalidNumberException e) {
483 throw new Error.InvalidNumber(e.getMessage());
484 }
485 }
486
487 @Override
488 public boolean isGroupBlocked(final byte[] groupId) {
489 var group = m.getGroup(GroupId.unknownVersion(groupId));
490 if (group == null) {
491 return false;
492 } else {
493 return group.isBlocked();
494 }
495 }
496
497 @Override
498 public boolean isMember(final byte[] groupId) {
499 var group = m.getGroup(GroupId.unknownVersion(groupId));
500 if (group == null) {
501 return false;
502 } else {
503 return group.isMember(m.getSelfRecipientId());
504 }
505 }
506 }