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