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