]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Refactor Manager interface
[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.Identity;
12 import org.asamk.signal.manager.api.Message;
13 import org.asamk.signal.manager.api.RecipientIdentifier;
14 import org.asamk.signal.manager.api.TypingAction;
15 import org.asamk.signal.manager.groups.GroupId;
16 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
17 import org.asamk.signal.manager.groups.GroupNotFoundException;
18 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
19 import org.asamk.signal.manager.groups.LastGroupAdminException;
20 import org.asamk.signal.manager.groups.NotAGroupMemberException;
21 import org.asamk.signal.manager.storage.recipients.Profile;
22 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
23 import org.asamk.signal.util.ErrorUtils;
24 import org.freedesktop.dbus.exceptions.DBusExecutionException;
25 import org.whispersystems.libsignal.InvalidKeyException;
26 import org.whispersystems.libsignal.util.Pair;
27 import org.whispersystems.libsignal.util.guava.Optional;
28 import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
29 import org.whispersystems.signalservice.api.messages.SendMessageResult;
30 import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
31 import org.whispersystems.signalservice.api.util.InvalidNumberException;
32 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
33
34 import java.io.File;
35 import java.io.IOException;
36 import java.net.URI;
37 import java.net.URISyntaxException;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 import java.util.Set;
45 import java.util.UUID;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
48
49 public class DbusSignalImpl implements Signal {
50
51 private final Manager m;
52 private final String objectPath;
53
54 public DbusSignalImpl(final Manager m, final String objectPath) {
55 this.m = m;
56 this.objectPath = objectPath;
57 }
58
59 @Override
60 public boolean isRemote() {
61 return false;
62 }
63
64 @Override
65 public String getObjectPath() {
66 return objectPath;
67 }
68
69 @Override
70 public String getNumber() {
71 return m.getSelfNumber();
72 }
73
74 @Override
75 public void addDevice(String uri) {
76 try {
77 m.addDeviceLink(new URI(uri));
78 } catch (IOException | InvalidKeyException e) {
79 throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage());
80 } catch (URISyntaxException e) {
81 throw new Error.InvalidUri(e.getClass().getSimpleName()
82 + " Device link uri has invalid format: "
83 + e.getMessage());
84 }
85 }
86
87 @Override
88 public void removeDevice(int deviceId) {
89 try {
90 m.removeLinkedDevices(deviceId);
91 } catch (IOException e) {
92 throw new Error.Failure(e.getClass().getSimpleName() + ": Error while removing device: " + e.getMessage());
93 }
94 }
95
96 @Override
97 public List<String> listDevices() {
98 List<Device> devices;
99 List<String> results = new ArrayList<String>();
100
101 try {
102 devices = m.getLinkedDevices();
103 } catch (IOException | Error.Failure e) {
104 throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
105 }
106
107 return devices.stream().map(d -> d.getName() == null ? "" : d.getName()).collect(Collectors.toList());
108 }
109
110 @Override
111 public void updateDeviceName(String deviceName) {
112 try {
113 m.updateAccountAttributes(deviceName);
114 } catch (IOException | Signal.Error.Failure e) {
115 throw new Error.Failure("UpdateAccount error: " + e.getMessage());
116 }
117 }
118
119 @Override
120 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
121 var recipients = new ArrayList<String>(1);
122 recipients.add(recipient);
123 return sendMessage(message, attachments, recipients);
124 }
125
126 @Override
127 public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
128 try {
129 final var results = m.sendMessage(new Message(message, attachments),
130 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
131 .map(RecipientIdentifier.class::cast)
132 .collect(Collectors.toSet()));
133
134 checkSendMessageResults(results.getTimestamp(), results.getResults());
135 return results.getTimestamp();
136 } catch (AttachmentInvalidException e) {
137 throw new Error.AttachmentInvalid(e.getMessage());
138 } catch (IOException e) {
139 throw new Error.Failure(e.getMessage());
140 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
141 throw new Error.GroupNotFound(e.getMessage());
142 }
143 }
144
145 @Override
146 public long sendRemoteDeleteMessage(
147 final long targetSentTimestamp, final String recipient
148 ) {
149 var recipients = new ArrayList<String>(1);
150 recipients.add(recipient);
151 return sendRemoteDeleteMessage(targetSentTimestamp, recipients);
152 }
153
154 @Override
155 public long sendRemoteDeleteMessage(
156 final long targetSentTimestamp, final List<String> recipients
157 ) {
158 try {
159 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
160 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
161 .map(RecipientIdentifier.class::cast)
162 .collect(Collectors.toSet()));
163 checkSendMessageResults(results.getTimestamp(), results.getResults());
164 return results.getTimestamp();
165 } catch (IOException e) {
166 throw new Error.Failure(e.getMessage());
167 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
168 throw new Error.GroupNotFound(e.getMessage());
169 }
170 }
171
172 @Override
173 public long sendGroupRemoteDeleteMessage(
174 final long targetSentTimestamp, final byte[] groupId
175 ) {
176 try {
177 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
178 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
179 checkSendMessageResults(results.getTimestamp(), results.getResults());
180 return results.getTimestamp();
181 } catch (IOException e) {
182 throw new Error.Failure(e.getMessage());
183 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
184 throw new Error.GroupNotFound(e.getMessage());
185 }
186 }
187
188 @Override
189 public long sendMessageReaction(
190 final String emoji,
191 final boolean remove,
192 final String targetAuthor,
193 final long targetSentTimestamp,
194 final String recipient
195 ) {
196 var recipients = new ArrayList<String>(1);
197 recipients.add(recipient);
198 return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
199 }
200
201 @Override
202 public long sendMessageReaction(
203 final String emoji,
204 final boolean remove,
205 final String targetAuthor,
206 final long targetSentTimestamp,
207 final List<String> recipients
208 ) {
209 try {
210 final var results = m.sendMessageReaction(emoji,
211 remove,
212 getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
213 targetSentTimestamp,
214 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
215 .map(RecipientIdentifier.class::cast)
216 .collect(Collectors.toSet()));
217 checkSendMessageResults(results.getTimestamp(), results.getResults());
218 return results.getTimestamp();
219 } catch (IOException e) {
220 throw new Error.Failure(e.getMessage());
221 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
222 throw new Error.GroupNotFound(e.getMessage());
223 }
224 }
225
226 @Override
227 public void sendTyping(
228 final String recipient, final boolean stop
229 ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity {
230 try {
231 var recipients = new ArrayList<String>(1);
232 recipients.add(recipient);
233 m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
234 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
235 .map(RecipientIdentifier.class::cast)
236 .collect(Collectors.toSet()));
237 } catch (IOException e) {
238 throw new Error.Failure(e.getMessage());
239 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
240 throw new Error.GroupNotFound(e.getMessage());
241 } catch (UntrustedIdentityException e) {
242 throw new Error.UntrustedIdentity(e.getMessage());
243 }
244 }
245
246 @Override
247 public void sendReadReceipt(
248 final String recipient, final List<Long> messageIds
249 ) throws Error.Failure, Error.UntrustedIdentity {
250 try {
251 m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
252 } catch (IOException e) {
253 throw new Error.Failure(e.getMessage());
254 } catch (UntrustedIdentityException e) {
255 throw new Error.UntrustedIdentity(e.getMessage());
256 }
257 }
258
259 @Override
260 public void sendContacts() {
261 try {
262 m.sendContacts();
263 } catch (IOException e) {
264 throw new Error.Failure("SendContacts error: " + e.getMessage());
265 }
266 }
267
268 @Override
269 public void sendSyncRequest() {
270 try {
271 m.requestAllSyncData();
272 } catch (IOException e) {
273 throw new Error.Failure("Request sync data error: " + e.getMessage());
274 }
275 }
276
277 @Override
278 public long sendNoteToSelfMessage(
279 final String message, final List<String> attachments
280 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
281 try {
282 final var results = m.sendMessage(new Message(message, attachments),
283 Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
284 checkSendMessageResults(results.getTimestamp(), results.getResults());
285 return results.getTimestamp();
286 } catch (AttachmentInvalidException e) {
287 throw new Error.AttachmentInvalid(e.getMessage());
288 } catch (IOException e) {
289 throw new Error.Failure(e.getMessage());
290 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
291 throw new Error.GroupNotFound(e.getMessage());
292 }
293 }
294
295 @Override
296 public void sendEndSessionMessage(final List<String> recipients) {
297 try {
298 final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
299 checkSendMessageResults(results.getTimestamp(), results.getResults());
300 } catch (IOException e) {
301 throw new Error.Failure(e.getMessage());
302 }
303 }
304
305 @Override
306 public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
307 try {
308 var results = m.sendMessage(new Message(message, attachments),
309 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
310 checkSendMessageResults(results.getTimestamp(), results.getResults());
311 return results.getTimestamp();
312 } catch (IOException e) {
313 throw new Error.Failure(e.getMessage());
314 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
315 throw new Error.GroupNotFound(e.getMessage());
316 } catch (AttachmentInvalidException e) {
317 throw new Error.AttachmentInvalid(e.getMessage());
318 }
319 }
320
321 @Override
322 public long sendGroupMessageReaction(
323 final String emoji,
324 final boolean remove,
325 final String targetAuthor,
326 final long targetSentTimestamp,
327 final byte[] groupId
328 ) {
329 try {
330 final var results = m.sendMessageReaction(emoji,
331 remove,
332 getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
333 targetSentTimestamp,
334 Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
335 checkSendMessageResults(results.getTimestamp(), results.getResults());
336 return results.getTimestamp();
337 } catch (IOException e) {
338 throw new Error.Failure(e.getMessage());
339 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
340 throw new Error.GroupNotFound(e.getMessage());
341 }
342 }
343
344 // Since contact names might be empty if not defined, also potentially return
345 // the profile name
346 @Override
347 public String getContactName(final String number) {
348 return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber()));
349 }
350
351 @Override
352 public void setContactName(final String number, final String name) {
353 try {
354 m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name);
355 } catch (NotMasterDeviceException e) {
356 throw new Error.Failure("This command doesn't work on linked devices.");
357 } catch (UnregisteredUserException e) {
358 throw new Error.Failure("Contact is not registered.");
359 }
360 }
361
362 @Override
363 public void setExpirationTimer(final String number, final int expiration) {
364 try {
365 m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getSelfNumber()), expiration);
366 } catch (IOException e) {
367 throw new Error.Failure(e.getMessage());
368 }
369 }
370
371 @Override
372 public void setContactBlocked(final String number, final boolean blocked) {
373 try {
374 m.setContactBlocked(getSingleRecipientIdentifier(number, m.getSelfNumber()), blocked);
375 } catch (NotMasterDeviceException e) {
376 throw new Error.Failure("This command doesn't work on linked devices.");
377 } catch (IOException e) {
378 throw new Error.Failure(e.getMessage());
379 }
380 }
381
382 @Override
383 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
384 try {
385 m.setGroupBlocked(getGroupId(groupId), blocked);
386 } catch (GroupNotFoundException e) {
387 throw new Error.GroupNotFound(e.getMessage());
388 } catch (IOException e) {
389 throw new Error.Failure(e.getMessage());
390 }
391 }
392
393 @Override
394 public List<byte[]> getGroupIds() {
395 var groups = m.getGroups();
396 var ids = new ArrayList<byte[]>(groups.size());
397 for (var group : groups) {
398 ids.add(group.getGroupId().serialize());
399 }
400 return ids;
401 }
402
403 @Override
404 public String getGroupName(final byte[] groupId) {
405 var group = m.getGroup(getGroupId(groupId));
406 if (group == null) {
407 return "";
408 } else {
409 return group.getTitle();
410 }
411 }
412
413 @Override
414 public List<String> getGroupMembers(final byte[] groupId) {
415 var group = m.getGroup(getGroupId(groupId));
416 if (group == null) {
417 return List.of();
418 } else {
419 return group.getMembers().stream().map(RecipientAddress::getLegacyIdentifier).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.getSelfNumber());
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(Identity::getRecipient),
577 m.getContacts().stream().map(Pair::first))
578 .map(a -> a.getNumber().orElse(null))
579 .filter(Objects::nonNull)
580 .distinct()
581 .collect(Collectors.toList());
582 }
583
584 @Override
585 public List<String> getContactNumber(final String name) {
586 // Contact names have precedence.
587 var numbers = new ArrayList<String>();
588 var contacts = m.getContacts();
589 for (var c : contacts) {
590 if (name.equals(c.second().getName())) {
591 numbers.add(c.first().getLegacyIdentifier());
592 }
593 }
594 // Try profiles if no contact name was found
595 for (var identity : m.getIdentities()) {
596 final var address = identity.getRecipient();
597 var number = address.getNumber().orElse(null);
598 if (number != null) {
599 Profile profile = null;
600 try {
601 profile = m.getRecipientProfile(RecipientIdentifier.Single.fromAddress(address));
602 } catch (UnregisteredUserException ignored) {
603 }
604 if (profile != null && profile.getDisplayName().equals(name)) {
605 numbers.add(number);
606 }
607 }
608 }
609 return numbers;
610 }
611
612 @Override
613 public void quitGroup(final byte[] groupId) {
614 var group = getGroupId(groupId);
615 try {
616 m.quitGroup(group, Set.of());
617 } catch (GroupNotFoundException | NotAGroupMemberException e) {
618 throw new Error.GroupNotFound(e.getMessage());
619 } catch (IOException | LastGroupAdminException e) {
620 throw new Error.Failure(e.getMessage());
621 }
622 }
623
624 @Override
625 public byte[] joinGroup(final String groupLink) {
626 try {
627 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
628 if (linkUrl == null) {
629 throw new Error.Failure("Group link is invalid:");
630 }
631 final var result = m.joinGroup(linkUrl);
632 return result.first().serialize();
633 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
634 throw new Error.Failure("Group link is invalid: " + e.getMessage());
635 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
636 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
637 } catch (IOException e) {
638 throw new Error.Failure(e.getMessage());
639 }
640 }
641
642 @Override
643 public boolean isContactBlocked(final String number) {
644 return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getSelfNumber()));
645 }
646
647 @Override
648 public boolean isGroupBlocked(final byte[] groupId) {
649 var group = m.getGroup(getGroupId(groupId));
650 if (group == null) {
651 return false;
652 } else {
653 return group.isBlocked();
654 }
655 }
656
657 @Override
658 public boolean isMember(final byte[] groupId) {
659 var group = m.getGroup(getGroupId(groupId));
660 if (group == null) {
661 return false;
662 } else {
663 return group.isMember();
664 }
665 }
666
667 @Override
668 public String uploadStickerPack(String stickerPackPath) {
669 File path = new File(stickerPackPath);
670 try {
671 return m.uploadStickerPack(path).toString();
672 } catch (IOException e) {
673 throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage());
674 } catch (StickerPackInvalidException e) {
675 throw new Error.Failure("Invalid sticker pack: " + e.getMessage());
676 }
677 }
678
679 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
680 var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
681
682 if (error == null) {
683 return;
684 }
685
686 final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
687
688 if (result.getIdentityFailure() != null) {
689 throw new Error.UntrustedIdentity(message);
690 } else {
691 throw new Error.Failure(message);
692 }
693 }
694
695 private static void checkSendMessageResults(
696 long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results
697 ) throws DBusExecutionException {
698 final var sendMessageResults = results.values().stream().findFirst();
699 if (results.size() == 1 && sendMessageResults.get().size() == 1) {
700 checkSendMessageResult(timestamp, sendMessageResults.get().stream().findFirst().get());
701 return;
702 }
703
704 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
705 if (errors.size() == 0) {
706 return;
707 }
708
709 var message = new StringBuilder();
710 message.append(timestamp).append('\n');
711 message.append("Failed to send (some) messages:\n");
712 for (var error : errors) {
713 message.append(error).append('\n');
714 }
715
716 throw new Error.Failure(message.toString());
717 }
718
719 private static void checkSendMessageResults(
720 long timestamp, Collection<SendMessageResult> results
721 ) throws DBusExecutionException {
722 if (results.size() == 1) {
723 checkSendMessageResult(timestamp, results.stream().findFirst().get());
724 return;
725 }
726
727 var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
728 if (errors.size() == 0) {
729 return;
730 }
731
732 var message = new StringBuilder();
733 message.append(timestamp).append('\n');
734 message.append("Failed to send (some) messages:\n");
735 for (var error : errors) {
736 message.append(error).append('\n');
737 }
738
739 throw new Error.Failure(message.toString());
740 }
741
742 private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
743 final Collection<String> recipientStrings, final String localNumber
744 ) throws DBusExecutionException {
745 final var identifiers = new HashSet<RecipientIdentifier.Single>();
746 for (var recipientString : recipientStrings) {
747 identifiers.add(getSingleRecipientIdentifier(recipientString, localNumber));
748 }
749 return identifiers;
750 }
751
752 private static RecipientIdentifier.Single getSingleRecipientIdentifier(
753 final String recipientString, final String localNumber
754 ) throws DBusExecutionException {
755 try {
756 return RecipientIdentifier.Single.fromString(recipientString, localNumber);
757 } catch (InvalidNumberException e) {
758 throw new Error.InvalidNumber(e.getMessage());
759 }
760 }
761
762 private static GroupId getGroupId(byte[] groupId) throws DBusExecutionException {
763 try {
764 return GroupId.unknownVersion(groupId);
765 } catch (Throwable e) {
766 throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage());
767 }
768 }
769 }