]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Reformat code
[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.StickerPackInvalidException;
9 import org.asamk.signal.manager.UntrustedIdentityException;
10 import org.asamk.signal.manager.api.Device;
11 import org.asamk.signal.manager.api.Message;
12 import org.asamk.signal.manager.api.RecipientIdentifier;
13 import org.asamk.signal.manager.api.TypingAction;
14 import org.asamk.signal.manager.groups.GroupId;
15 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
16 import org.asamk.signal.manager.groups.GroupNotFoundException;
17 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
18 import org.asamk.signal.manager.groups.LastGroupAdminException;
19 import org.asamk.signal.manager.groups.NotAGroupMemberException;
20 import org.asamk.signal.manager.storage.identities.IdentityInfo;
21 import org.asamk.signal.util.ErrorUtils;
22 import org.asamk.signal.util.Util;
23 import org.freedesktop.dbus.exceptions.DBusExecutionException;
24 import org.whispersystems.libsignal.InvalidKeyException;
25 import org.whispersystems.libsignal.util.Pair;
26 import org.whispersystems.libsignal.util.guava.Optional;
27 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
28 import org.whispersystems.signalservice.api.messages.SendMessageResult;
29 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
30 import org.whispersystems.signalservice.api.util.InvalidNumberException;
31 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
32
33 import java.io.File;
34 import java.io.IOException;
35 import java.net.URI;
36 import java.net.URISyntaxException;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.UUID;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47
48 import static org.asamk.signal.util.Util.getLegacyIdentifier;
49
50 public class DbusSignalImpl implements Signal {
51
52 private final Manager m;
53 private final String objectPath;
54
55 public DbusSignalImpl(final Manager m, final String objectPath) {
56 this.m = m;
57 this.objectPath = objectPath;
58 }
59
60 @Override
61 public boolean isRemote() {
62 return false;
63 }
64
65 @Override
66 public String getObjectPath() {
67 return objectPath;
68 }
69
70 @Override
71 public void addDevice(String uri) {
72 try {
73 m.addDeviceLink(new URI(uri));
74 } catch (IOException | InvalidKeyException e) {
75 throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage());
76 } catch (URISyntaxException e) {
77 throw new Error.InvalidUri(e.getClass().getSimpleName()
78 + " Device link uri has invalid format: "
79 + e.getMessage());
80 }
81 }
82
83 @Override
84 public void removeDevice(int deviceId) {
85 try {
86 m.removeLinkedDevices(deviceId);
87 } catch (IOException e) {
88 throw new Error.Failure(e.getClass().getSimpleName() + ": Error while removing device: " + e.getMessage());
89 }
90 }
91
92 @Override
93 public List<String> listDevices() {
94 List<Device> devices;
95 List<String> results = new ArrayList<String>();
96
97 try {
98 devices = m.getLinkedDevices();
99 } catch (IOException | Error.Failure e) {
100 throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
101 }
102
103 return devices.stream().map(d -> d.getName() == null ? "" : d.getName()).collect(Collectors.toList());
104 }
105
106 @Override
107 public void updateDeviceName(String deviceName) {
108 try {
109 m.updateAccountAttributes(deviceName);
110 } catch (IOException | Signal.Error.Failure e) {
111 throw new Error.Failure("UpdateAccount error: " + e.getMessage());
112 }
113 }
114
115 @Override
116 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
117 var recipients = new ArrayList<String>(1);
118 recipients.add(recipient);
119 return sendMessage(message, attachments, recipients);
120 }
121
122 @Override
123 public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
124 try {
125 final var results = m.sendMessage(new Message(message, attachments),
126 getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
127 .map(RecipientIdentifier.class::cast)
128 .collect(Collectors.toSet()));
129
130 checkSendMessageResults(results.getTimestamp(), results.getResults());
131 return results.getTimestamp();
132 } catch (AttachmentInvalidException e) {
133 throw new Error.AttachmentInvalid(e.getMessage());
134 } catch (IOException e) {
135 throw new Error.Failure(e.getMessage());
136 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
137 throw new Error.GroupNotFound(e.getMessage());
138 }
139 }
140
141 @Override
142 public long sendRemoteDeleteMessage(
143 final long targetSentTimestamp, final String recipient
144 ) {
145 var recipients = new ArrayList<String>(1);
146 recipients.add(recipient);
147 return sendRemoteDeleteMessage(targetSentTimestamp, recipients);
148 }
149
150 @Override
151 public long sendRemoteDeleteMessage(
152 final long targetSentTimestamp, final List<String> recipients
153 ) {
154 try {
155 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
156 getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
157 .map(RecipientIdentifier.class::cast)
158 .collect(Collectors.toSet()));
159 checkSendMessageResults(results.getTimestamp(), results.getResults());
160 return results.getTimestamp();
161 } catch (IOException e) {
162 throw new Error.Failure(e.getMessage());
163 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
164 throw new Error.GroupNotFound(e.getMessage());
165 }
166 }
167
168 @Override
169 public long sendGroupRemoteDeleteMessage(
170 final long targetSentTimestamp, final byte[] groupId
171 ) {
172 try {
173 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
174 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
175 checkSendMessageResults(results.getTimestamp(), results.getResults());
176 return results.getTimestamp();
177 } catch (IOException e) {
178 throw new Error.Failure(e.getMessage());
179 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
180 throw new Error.GroupNotFound(e.getMessage());
181 }
182 }
183
184 @Override
185 public long sendMessageReaction(
186 final String emoji,
187 final boolean remove,
188 final String targetAuthor,
189 final long targetSentTimestamp,
190 final String recipient
191 ) {
192 var recipients = new ArrayList<String>(1);
193 recipients.add(recipient);
194 return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
195 }
196
197 @Override
198 public long sendMessageReaction(
199 final String emoji,
200 final boolean remove,
201 final String targetAuthor,
202 final long targetSentTimestamp,
203 final List<String> recipients
204 ) {
205 try {
206 final var results = m.sendMessageReaction(emoji,
207 remove,
208 getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
209 targetSentTimestamp,
210 getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
211 .map(RecipientIdentifier.class::cast)
212 .collect(Collectors.toSet()));
213 checkSendMessageResults(results.getTimestamp(), results.getResults());
214 return results.getTimestamp();
215 } catch (IOException e) {
216 throw new Error.Failure(e.getMessage());
217 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
218 throw new Error.GroupNotFound(e.getMessage());
219 }
220 }
221
222 @Override
223 public void sendTyping(
224 final String recipient, final boolean stop
225 ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity {
226 try {
227 var recipients = new ArrayList<String>(1);
228 recipients.add(recipient);
229 m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
230 getSingleRecipientIdentifiers(recipients, m.getUsername()).stream()
231 .map(RecipientIdentifier.class::cast)
232 .collect(Collectors.toSet()));
233 } catch (IOException e) {
234 throw new Error.Failure(e.getMessage());
235 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
236 throw new Error.GroupNotFound(e.getMessage());
237 } catch (UntrustedIdentityException e) {
238 throw new Error.UntrustedIdentity(e.getMessage());
239 }
240 }
241
242 @Override
243 public void sendReadReceipt(
244 final String recipient, final List<Long> timestamps
245 ) throws Error.Failure, Error.UntrustedIdentity {
246 try {
247 m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getUsername()), timestamps);
248 } catch (IOException e) {
249 throw new Error.Failure(e.getMessage());
250 } catch (UntrustedIdentityException e) {
251 throw new Error.UntrustedIdentity(e.getMessage());
252 }
253 }
254
255 @Override
256 public void sendContacts() {
257 try {
258 m.sendContacts();
259 } catch (IOException e) {
260 throw new Error.Failure("SendContacts error: " + e.getMessage());
261 }
262 }
263
264 @Override
265 public void sendSyncRequest() {
266 try {
267 m.requestAllSyncData();
268 } catch (IOException e) {
269 throw new Error.Failure("Request sync data error: " + e.getMessage());
270 }
271 }
272
273 @Override
274 public long sendNoteToSelfMessage(
275 final String message, final List<String> attachments
276 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
277 try {
278 final var results = m.sendMessage(new Message(message, attachments),
279 Set.of(new RecipientIdentifier.NoteToSelf()));
280 checkSendMessageResults(results.getTimestamp(), results.getResults());
281 return results.getTimestamp();
282 } catch (AttachmentInvalidException e) {
283 throw new Error.AttachmentInvalid(e.getMessage());
284 } catch (IOException e) {
285 throw new Error.Failure(e.getMessage());
286 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
287 throw new Error.GroupNotFound(e.getMessage());
288 }
289 }
290
291 @Override
292 public void sendEndSessionMessage(final List<String> recipients) {
293 try {
294 final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getUsername()));
295 checkSendMessageResults(results.getTimestamp(), results.getResults());
296 } catch (IOException e) {
297 throw new Error.Failure(e.getMessage());
298 }
299 }
300
301 @Override
302 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
303 try {
304 var results = m.sendMessage(new Message(message, attachments),
305 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
306 checkSendMessageResults(results.getTimestamp(), results.getResults());
307 return results.getTimestamp();
308 } catch (IOException e) {
309 throw new Error.Failure(e.getMessage());
310 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
311 throw new Error.GroupNotFound(e.getMessage());
312 } catch (AttachmentInvalidException e) {
313 throw new Error.AttachmentInvalid(e.getMessage());
314 }
315 }
316
317 @Override
318 public long sendGroupMessageReaction(
319 final String emoji,
320 final boolean remove,
321 final String targetAuthor,
322 final long targetSentTimestamp,
323 final byte[] groupId
324 ) {
325 try {
326 final var results = m.sendMessageReaction(emoji,
327 remove,
328 getSingleRecipientIdentifier(targetAuthor, m.getUsername()),
329 targetSentTimestamp,
330 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
331 checkSendMessageResults(results.getTimestamp(), results.getResults());
332 return results.getTimestamp();
333 } catch (IOException e) {
334 throw new Error.Failure(e.getMessage());
335 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
336 throw new Error.GroupNotFound(e.getMessage());
337 }
338 }
339
340 // Since contact names might be empty if not defined, also potentially return
341 // the profile name
342 @Override
343 public String getContactName(final String number) {
344 return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getUsername()));
345 }
346
347 @Override
348 public void setContactName(final String number, final String name) {
349 try {
350 m.setContactName(getSingleRecipientIdentifier(number, m.getUsername()), name);
351 } catch (NotMasterDeviceException e) {
352 throw new Error.Failure("This command doesn't work on linked devices.");
353 } catch (UnregisteredUserException e) {
354 throw new Error.Failure("Contact is not registered.");
355 }
356 }
357
358 @Override
359 public void setExpirationTimer(final String number, final int expiration) {
360 try {
361 m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getUsername()), expiration);
362 } catch (IOException e) {
363 throw new Error.Failure(e.getMessage());
364 }
365 }
366
367 @Override
368 public void setContactBlocked(final String number, final boolean blocked) {
369 try {
370 m.setContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()), blocked);
371 } catch (NotMasterDeviceException e) {
372 throw new Error.Failure("This command doesn't work on linked devices.");
373 } catch (IOException e) {
374 throw new Error.Failure(e.getMessage());
375 }
376 }
377
378 @Override
379 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
380 try {
381 m.setGroupBlocked(getGroupId(groupId), blocked);
382 } catch (GroupNotFoundException e) {
383 throw new Error.GroupNotFound(e.getMessage());
384 } catch (IOException e) {
385 throw new Error.Failure(e.getMessage());
386 }
387 }
388
389 @Override
390 public List<byte[]> getGroupIds() {
391 var groups = m.getGroups();
392 var ids = new ArrayList<byte[]>(groups.size());
393 for (var group : groups) {
394 ids.add(group.getGroupId().serialize());
395 }
396 return ids;
397 }
398
399 @Override
400 public String getGroupName(final byte[] groupId) {
401 var group = m.getGroup(getGroupId(groupId));
402 if (group == null) {
403 return "";
404 } else {
405 return group.getTitle();
406 }
407 }
408
409 @Override
410 public List<String> getGroupMembers(final byte[] groupId) {
411 var group = m.getGroup(getGroupId(groupId));
412 if (group == null) {
413 return List.of();
414 } else {
415 return group.getMembers()
416 .stream()
417 .map(m::resolveSignalServiceAddress)
418 .map(Util::getLegacyIdentifier)
419 .collect(Collectors.toList());
420 }
421 }
422
423 @Override
424 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
425 try {
426 if (groupId.length == 0) {
427 groupId = null;
428 }
429 if (name.isEmpty()) {
430 name = null;
431 }
432 if (avatar.isEmpty()) {
433 avatar = null;
434 }
435 final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getUsername());
436 if (groupId == null) {
437 final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar));
438 checkSendMessageResults(results.second().getTimestamp(), results.second().getResults());
439 return results.first().serialize();
440 } else {
441 final var results = m.updateGroup(getGroupId(groupId),
442 name,
443 null,
444 memberIdentifiers,
445 null,
446 null,
447 null,
448 false,
449 null,
450 null,
451 null,
452 avatar == null ? null : new File(avatar),
453 null,
454 null);
455 if (results != null) {
456 checkSendMessageResults(results.getTimestamp(), results.getResults());
457 }
458 return groupId;
459 }
460 } catch (IOException e) {
461 throw new Error.Failure(e.getMessage());
462 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
463 throw new Error.GroupNotFound(e.getMessage());
464 } catch (AttachmentInvalidException e) {
465 throw new Error.AttachmentInvalid(e.getMessage());
466 }
467 }
468
469 @Override
470 public boolean isRegistered() {
471 return true;
472 }
473
474 @Override
475 public boolean isRegistered(String number) {
476 var result = isRegistered(List.of(number));
477 return result.get(0);
478 }
479
480 @Override
481 public List<Boolean> isRegistered(List<String> numbers) {
482 var results = new ArrayList<Boolean>();
483 if (numbers.isEmpty()) {
484 return results;
485 }
486
487 Map<String, Pair<String, UUID>> registered;
488 try {
489 registered = m.areUsersRegistered(new HashSet<>(numbers));
490 } catch (IOException e) {
491 throw new Error.Failure(e.getMessage());
492 }
493
494 return numbers.stream().map(number -> {
495 var uuid = registered.get(number).second();
496 return uuid != null;
497 }).collect(Collectors.toList());
498 }
499
500 @Override
501 public void updateProfile(
502 final String givenName,
503 final String familyName,
504 final String about,
505 final String aboutEmoji,
506 String avatarPath,
507 final boolean removeAvatar
508 ) {
509 try {
510 if (avatarPath.isEmpty()) {
511 avatarPath = null;
512 }
513 Optional<File> avatarFile = removeAvatar
514 ? Optional.absent()
515 : avatarPath == null ? null : Optional.of(new File(avatarPath));
516 m.setProfile(givenName, familyName, about, aboutEmoji, avatarFile);
517 } catch (IOException e) {
518 throw new Error.Failure(e.getMessage());
519 }
520 }
521
522 @Override
523 public void updateProfile(
524 final String name,
525 final String about,
526 final String aboutEmoji,
527 String avatarPath,
528 final boolean removeAvatar
529 ) {
530 try {
531 if (avatarPath.isEmpty()) {
532 avatarPath = null;
533 }
534 Optional<File> avatarFile = removeAvatar
535 ? Optional.absent()
536 : avatarPath == null ? null : Optional.of(new File(avatarPath));
537 m.setProfile(name, null, about, aboutEmoji, avatarFile);
538 } catch (IOException e) {
539 throw new Error.Failure(e.getMessage());
540 }
541 }
542
543 @Override
544 public void removePin() {
545 try {
546 m.setRegistrationLockPin(Optional.absent());
547 } catch (UnauthenticatedResponseException e) {
548 throw new Error.Failure("Remove pin failed with unauthenticated response: " + e.getMessage());
549 } catch (IOException e) {
550 throw new Error.Failure("Remove pin error: " + e.getMessage());
551 }
552 }
553
554 @Override
555 public void setPin(String registrationLockPin) {
556 try {
557 m.setRegistrationLockPin(Optional.of(registrationLockPin));
558 } catch (UnauthenticatedResponseException e) {
559 throw new Error.Failure("Set pin error failed with unauthenticated response: " + e.getMessage());
560 } catch (IOException e) {
561 throw new Error.Failure("Set pin error: " + e.getMessage());
562 }
563 }
564
565 // Provide option to query a version string in order to react on potential
566 // future interface changes
567 @Override
568 public String version() {
569 return BaseConfig.PROJECT_VERSION;
570 }
571
572 // Create a unique list of Numbers from Identities and Contacts to really get
573 // all numbers the system knows
574 @Override
575 public List<String> listNumbers() {
576 return Stream.concat(m.getIdentities().stream().map(IdentityInfo::getRecipientId),
577 m.getContacts().stream().map(Pair::first))
578 .map(m::resolveSignalServiceAddress)
579 .map(a -> a.getNumber().orNull())
580 .filter(Objects::nonNull)
581 .distinct()
582 .collect(Collectors.toList());
583 }
584
585 @Override
586 public List<String> getContactNumber(final String name) {
587 // Contact names have precedence.
588 var numbers = new ArrayList<String>();
589 var contacts = m.getContacts();
590 for (var c : contacts) {
591 if (name.equals(c.second().getName())) {
592 numbers.add(getLegacyIdentifier(m.resolveSignalServiceAddress(c.first())));
593 }
594 }
595 // Try profiles if no contact name was found
596 for (var identity : m.getIdentities()) {
597 final var recipientId = identity.getRecipientId();
598 final var address = m.resolveSignalServiceAddress(recipientId);
599 var number = address.getNumber().orNull();
600 if (number != null) {
601 var profile = m.getRecipientProfile(recipientId);
602 if (profile != null && profile.getDisplayName().equals(name)) {
603 numbers.add(number);
604 }
605 }
606 }
607 return numbers;
608 }
609
610 @Override
611 public void quitGroup(final byte[] groupId) {
612 var group = getGroupId(groupId);
613 try {
614 m.quitGroup(group, Set.of());
615 } catch (GroupNotFoundException | NotAGroupMemberException e) {
616 throw new Error.GroupNotFound(e.getMessage());
617 } catch (IOException | LastGroupAdminException e) {
618 throw new Error.Failure(e.getMessage());
619 }
620 }
621
622 @Override
623 public byte[] joinGroup(final String groupLink) {
624 try {
625 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
626 if (linkUrl == null) {
627 throw new Error.Failure("Group link is invalid:");
628 }
629 final var result = m.joinGroup(linkUrl);
630 return result.first().serialize();
631 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
632 throw new Error.Failure("Group link is invalid: " + e.getMessage());
633 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
634 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
635 } catch (IOException e) {
636 throw new Error.Failure(e.getMessage());
637 }
638 }
639
640 @Override
641 public boolean isContactBlocked(final String number) {
642 return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()));
643 }
644
645 @Override
646 public boolean isGroupBlocked(final byte[] groupId) {
647 var group = m.getGroup(getGroupId(groupId));
648 if (group == null) {
649 return false;
650 } else {
651 return group.isBlocked();
652 }
653 }
654
655 @Override
656 public boolean isMember(final byte[] groupId) {
657 var group = m.getGroup(getGroupId(groupId));
658 if (group == null) {
659 return false;
660 } else {
661 return group.isMember(m.getSelfRecipientId());
662 }
663 }
664
665 @Override
666 public String uploadStickerPack(String stickerPackPath) {
667 File path = new File(stickerPackPath);
668 try {
669 return m.uploadStickerPack(path).toString();
670 } catch (IOException e) {
671 throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage());
672 } catch (StickerPackInvalidException e) {
673 throw new Error.Failure("Invalid sticker pack: " + e.getMessage());
674 }
675 }
676
677 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
678 var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
679
680 if (error == null) {
681 return;
682 }
683
684 final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
685
686 if (result.getIdentityFailure() != null) {
687 throw new Error.UntrustedIdentity(message);
688 } else {
689 throw new Error.Failure(message);
690 }
691 }
692
693 private static void checkSendMessageResults(
694 long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results
695 ) throws DBusExecutionException {
696 final var sendMessageResults = results.values().stream().findFirst();
697 if (results.size() == 1 && sendMessageResults.get().size() == 1) {
698 checkSendMessageResult(timestamp, sendMessageResults.get().stream().findFirst().get());
699 return;
700 }
701
702 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
703 if (errors.size() == 0) {
704 return;
705 }
706
707 var message = new StringBuilder();
708 message.append(timestamp).append('\n');
709 message.append("Failed to send (some) messages:\n");
710 for (var error : errors) {
711 message.append(error).append('\n');
712 }
713
714 throw new Error.Failure(message.toString());
715 }
716
717 private static void checkSendMessageResults(
718 long timestamp, Collection<SendMessageResult> results
719 ) throws DBusExecutionException {
720 if (results.size() == 1) {
721 checkSendMessageResult(timestamp, results.stream().findFirst().get());
722 return;
723 }
724
725 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
726 if (errors.size() == 0) {
727 return;
728 }
729
730 var message = new StringBuilder();
731 message.append(timestamp).append('\n');
732 message.append("Failed to send (some) messages:\n");
733 for (var error : errors) {
734 message.append(error).append('\n');
735 }
736
737 throw new Error.Failure(message.toString());
738 }
739
740 private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
741 final Collection<String> recipientStrings, final String localNumber
742 ) throws DBusExecutionException {
743 final var identifiers = new HashSet<RecipientIdentifier.Single>();
744 for (var recipientString : recipientStrings) {
745 identifiers.add(getSingleRecipientIdentifier(recipientString, localNumber));
746 }
747 return identifiers;
748 }
749
750 private static RecipientIdentifier.Single getSingleRecipientIdentifier(
751 final String recipientString, final String localNumber
752 ) throws DBusExecutionException {
753 try {
754 return RecipientIdentifier.Single.fromString(recipientString, localNumber);
755 } catch (InvalidNumberException e) {
756 throw new Error.InvalidNumber(e.getMessage());
757 }
758 }
759
760 private static GroupId getGroupId(byte[] groupId) throws DBusExecutionException {
761 try {
762 return GroupId.unknownVersion(groupId);
763 } catch (Throwable e) {
764 throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage());
765 }
766 }
767 }