]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
Add --notify-self parmeter
[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.Manager;
6 import org.asamk.signal.manager.api.AttachmentInvalidException;
7 import org.asamk.signal.manager.api.DeviceLinkUrl;
8 import org.asamk.signal.manager.api.GroupId;
9 import org.asamk.signal.manager.api.GroupInviteLinkUrl;
10 import org.asamk.signal.manager.api.GroupLinkState;
11 import org.asamk.signal.manager.api.GroupNotFoundException;
12 import org.asamk.signal.manager.api.GroupPermission;
13 import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
14 import org.asamk.signal.manager.api.IdentityVerificationCode;
15 import org.asamk.signal.manager.api.InactiveGroupLinkException;
16 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
17 import org.asamk.signal.manager.api.InvalidNumberException;
18 import org.asamk.signal.manager.api.InvalidStickerException;
19 import org.asamk.signal.manager.api.LastGroupAdminException;
20 import org.asamk.signal.manager.api.Message;
21 import org.asamk.signal.manager.api.NotAGroupMemberException;
22 import org.asamk.signal.manager.api.NotPrimaryDeviceException;
23 import org.asamk.signal.manager.api.PendingAdminApprovalException;
24 import org.asamk.signal.manager.api.RateLimitException;
25 import org.asamk.signal.manager.api.RecipientAddress;
26 import org.asamk.signal.manager.api.RecipientIdentifier;
27 import org.asamk.signal.manager.api.SendMessageResult;
28 import org.asamk.signal.manager.api.SendMessageResults;
29 import org.asamk.signal.manager.api.StickerPackInvalidException;
30 import org.asamk.signal.manager.api.TypingAction;
31 import org.asamk.signal.manager.api.UnregisteredRecipientException;
32 import org.asamk.signal.manager.api.UpdateGroup;
33 import org.asamk.signal.manager.api.UpdateProfile;
34 import org.asamk.signal.manager.api.UserStatus;
35 import org.asamk.signal.util.DateUtils;
36 import org.asamk.signal.util.SendMessageResultUtils;
37 import org.freedesktop.dbus.DBusPath;
38 import org.freedesktop.dbus.connections.impl.DBusConnection;
39 import org.freedesktop.dbus.exceptions.DBusException;
40 import org.freedesktop.dbus.exceptions.DBusExecutionException;
41 import org.freedesktop.dbus.interfaces.DBusInterface;
42 import org.freedesktop.dbus.types.Variant;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.net.URI;
49 import java.net.URISyntaxException;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Base64;
53 import java.util.Collection;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Objects;
58 import java.util.Optional;
59 import java.util.Set;
60 import java.util.UUID;
61 import java.util.stream.Collectors;
62
63 import static org.asamk.signal.dbus.DbusUtils.makeValidObjectPathElement;
64
65 public class DbusSignalImpl implements Signal, AutoCloseable {
66
67 private final Manager m;
68 private final DBusConnection connection;
69 private final String objectPath;
70 private final boolean noReceiveOnStart;
71
72 private DBusPath thisDevice;
73 private final List<StructDevice> devices = new ArrayList<>();
74 private final List<StructGroup> groups = new ArrayList<>();
75 private final List<StructIdentity> identities = new ArrayList<>();
76 private DbusReceiveMessageHandler dbusMessageHandler;
77 private int subscriberCount;
78
79 private static final Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class);
80
81 public DbusSignalImpl(
82 final Manager m, DBusConnection connection, final String objectPath, final boolean noReceiveOnStart
83 ) {
84 this.m = m;
85 this.connection = connection;
86 this.objectPath = objectPath;
87 this.noReceiveOnStart = noReceiveOnStart;
88
89 m.addAddressChangedListener(() -> {
90 unExportObjects();
91 exportObjects();
92 });
93 }
94
95 public void initObjects() {
96 exportObjects();
97 if (!noReceiveOnStart) {
98 subscribeReceive();
99 }
100 }
101
102 private void exportObjects() {
103 exportObject(this);
104
105 updateDevices();
106 updateGroups();
107 updateConfiguration();
108 updateIdentities();
109 }
110
111 @Override
112 public void close() {
113 if (dbusMessageHandler != null) {
114 m.removeReceiveHandler(dbusMessageHandler);
115 dbusMessageHandler = null;
116 }
117 unExportObjects();
118 }
119
120 private void unExportObjects() {
121 unExportDevices();
122 unExportGroups();
123 unExportConfiguration();
124 unExportIdentities();
125 connection.unExportObject(this.objectPath);
126 }
127
128 @Override
129 public String getObjectPath() {
130 return objectPath;
131 }
132
133 @Override
134 public String getSelfNumber() {
135 return m.getSelfNumber();
136 }
137
138 @Override
139 public void subscribeReceive() {
140 if (dbusMessageHandler == null) {
141 dbusMessageHandler = new DbusReceiveMessageHandler(connection, objectPath);
142 m.addReceiveHandler(dbusMessageHandler);
143 }
144 subscriberCount++;
145 }
146
147 @Override
148 public void unsubscribeReceive() {
149 subscriberCount = Math.max(0, subscriberCount - 1);
150 if (subscriberCount == 0 && dbusMessageHandler != null) {
151 m.removeReceiveHandler(dbusMessageHandler);
152 dbusMessageHandler = null;
153 }
154 }
155
156 @Override
157 public void submitRateLimitChallenge(String challenge, String captcha) {
158 try {
159 m.submitRateLimitRecaptchaChallenge(challenge, captcha);
160 } catch (IOException e) {
161 throw new Error.Failure("Submit challenge error: " + e.getMessage());
162 }
163
164 }
165
166 @Override
167 public void unregister() throws Error.Failure {
168 try {
169 m.unregister();
170 } catch (IOException e) {
171 throw new Error.Failure("Failed to unregister: " + e.getMessage());
172 }
173 }
174
175 @Override
176 public void deleteAccount() throws Error.Failure {
177 try {
178 m.deleteAccount();
179 } catch (IOException e) {
180 throw new Error.Failure("Failed to delete account: " + e.getMessage());
181 }
182 }
183
184 @Override
185 public void addDevice(String uri) {
186 try {
187 var deviceLinkUrl = DeviceLinkUrl.parseDeviceLinkUri(new URI(uri));
188 m.addDeviceLink(deviceLinkUrl);
189 } catch (IOException | InvalidDeviceLinkException e) {
190 throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage());
191 } catch (NotPrimaryDeviceException e) {
192 throw new Error.Failure("This command doesn't work on linked devices.");
193 } catch (URISyntaxException e) {
194 throw new Error.InvalidUri(e.getClass().getSimpleName()
195 + " Device link uri has invalid format: "
196 + e.getMessage());
197 }
198 }
199
200 @Override
201 public DBusPath getDevice(long deviceId) {
202 updateDevices();
203 final var deviceOptional = devices.stream().filter(g -> g.getId().equals(deviceId)).findFirst();
204 if (deviceOptional.isEmpty()) {
205 throw new Error.DeviceNotFound("Device not found");
206 }
207 return deviceOptional.get().getObjectPath();
208 }
209
210 @Override
211 public List<StructDevice> listDevices() {
212 updateDevices();
213 return this.devices;
214 }
215
216 @Override
217 public DBusPath getThisDevice() {
218 updateDevices();
219 return thisDevice;
220 }
221
222 @Override
223 public long sendMessage(final String message, final List<String> attachments, final String recipient) {
224 return sendMessage(message, attachments, List.of(recipient));
225 }
226
227 @Override
228 public long sendMessage(final String messageText, final List<String> attachments, final List<String> recipients) {
229 try {
230 final var message = new Message(messageText,
231 attachments,
232 List.of(),
233 Optional.empty(),
234 Optional.empty(),
235 List.of(),
236 Optional.empty(),
237 List.of());
238 final var recipientIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
239 .map(RecipientIdentifier.class::cast)
240 .collect(Collectors.toSet());
241 final var results = m.sendMessage(message, recipientIdentifiers, false);
242
243 checkSendMessageResults(results);
244 return results.timestamp();
245 } catch (AttachmentInvalidException e) {
246 throw new Error.AttachmentInvalid(e.getMessage());
247 } catch (IOException | InvalidStickerException e) {
248 throw new Error.Failure(e);
249 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
250 throw new Error.GroupNotFound(e.getMessage());
251 } catch (UnregisteredRecipientException e) {
252 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
253 }
254 }
255
256 @Override
257 public long sendRemoteDeleteMessage(
258 final long targetSentTimestamp, final String recipient
259 ) {
260 return sendRemoteDeleteMessage(targetSentTimestamp, List.of(recipient));
261 }
262
263 @Override
264 public long sendRemoteDeleteMessage(
265 final long targetSentTimestamp, final List<String> recipients
266 ) {
267 try {
268 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
269 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
270 .map(RecipientIdentifier.class::cast)
271 .collect(Collectors.toSet()));
272 checkSendMessageResults(results);
273 return results.timestamp();
274 } catch (IOException e) {
275 throw new Error.Failure(e.getMessage());
276 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
277 throw new Error.GroupNotFound(e.getMessage());
278 }
279 }
280
281 @Override
282 public long sendMessageReaction(
283 final String emoji,
284 final boolean remove,
285 final String targetAuthor,
286 final long targetSentTimestamp,
287 final String recipient
288 ) {
289 return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, List.of(recipient));
290 }
291
292 @Override
293 public long sendMessageReaction(
294 final String emoji,
295 final boolean remove,
296 final String targetAuthor,
297 final long targetSentTimestamp,
298 final List<String> recipients
299 ) {
300 try {
301 final var results = m.sendMessageReaction(emoji,
302 remove,
303 getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
304 targetSentTimestamp,
305 getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
306 .map(RecipientIdentifier.class::cast)
307 .collect(Collectors.toSet()),
308 false);
309 checkSendMessageResults(results);
310 return results.timestamp();
311 } catch (IOException e) {
312 throw new Error.Failure(e.getMessage());
313 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
314 throw new Error.GroupNotFound(e.getMessage());
315 } catch (UnregisteredRecipientException e) {
316 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
317 }
318 }
319
320 @Override
321 public long sendPaymentNotification(
322 final byte[] receipt, final String note, final String recipient
323 ) throws Error.Failure {
324 try {
325 final var results = m.sendPaymentNotificationMessage(receipt,
326 note,
327 getSingleRecipientIdentifier(recipient, m.getSelfNumber()));
328 checkSendMessageResults(results);
329 return results.timestamp();
330 } catch (IOException e) {
331 throw new Error.Failure(e.getMessage());
332 }
333 }
334
335 @Override
336 public void sendTyping(
337 final String recipient, final boolean stop
338 ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity {
339 try {
340 final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
341 getSingleRecipientIdentifiers(List.of(recipient), m.getSelfNumber()).stream()
342 .map(RecipientIdentifier.class::cast)
343 .collect(Collectors.toSet()));
344 checkSendMessageResults(results);
345 } catch (IOException e) {
346 throw new Error.Failure(e.getMessage());
347 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
348 throw new Error.GroupNotFound(e.getMessage());
349 }
350 }
351
352 @Override
353 public void sendReadReceipt(
354 final String recipient, final List<Long> messageIds
355 ) throws Error.Failure, Error.UntrustedIdentity {
356 final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
357 checkSendMessageResults(results);
358 }
359
360 @Override
361 public void sendViewedReceipt(
362 final String recipient, final List<Long> messageIds
363 ) throws Error.Failure, Error.UntrustedIdentity {
364 final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
365 checkSendMessageResults(results);
366 }
367
368 @Override
369 public void sendContacts() {
370 try {
371 m.sendContacts();
372 } catch (IOException e) {
373 throw new Error.Failure("SendContacts error: " + e.getMessage());
374 }
375 }
376
377 @Override
378 public void sendSyncRequest() {
379 try {
380 m.requestAllSyncData();
381 } catch (IOException e) {
382 throw new Error.Failure("Request sync data error: " + e.getMessage());
383 }
384 }
385
386 @Override
387 public long sendNoteToSelfMessage(
388 final String messageText, final List<String> attachments
389 ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
390 try {
391 final var message = new Message(messageText,
392 attachments,
393 List.of(),
394 Optional.empty(),
395 Optional.empty(),
396 List.of(),
397 Optional.empty(),
398 List.of());
399 final var results = m.sendMessage(message, Set.of(RecipientIdentifier.NoteToSelf.INSTANCE), false);
400 checkSendMessageResults(results);
401 return results.timestamp();
402 } catch (AttachmentInvalidException e) {
403 throw new Error.AttachmentInvalid(e.getMessage());
404 } catch (IOException | InvalidStickerException e) {
405 throw new Error.Failure(e.getMessage());
406 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
407 throw new Error.GroupNotFound(e.getMessage());
408 } catch (UnregisteredRecipientException e) {
409 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
410 }
411 }
412
413 @Override
414 public void sendEndSessionMessage(final List<String> recipients) {
415 try {
416 final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
417 checkSendMessageResults(results);
418 } catch (IOException e) {
419 throw new Error.Failure(e.getMessage());
420 }
421 }
422
423 @Override
424 public void deleteRecipient(final String recipient) throws Error.Failure {
425 m.deleteRecipient(getSingleRecipientIdentifier(recipient, m.getSelfNumber()));
426 }
427
428 @Override
429 public void deleteContact(final String recipient) throws Error.Failure {
430 m.deleteContact(getSingleRecipientIdentifier(recipient, m.getSelfNumber()));
431 }
432
433 @Override
434 public long sendGroupMessage(final String messageText, final List<String> attachments, final byte[] groupId) {
435 try {
436 final var message = new Message(messageText,
437 attachments,
438 List.of(),
439 Optional.empty(),
440 Optional.empty(),
441 List.of(),
442 Optional.empty(),
443 List.of());
444 var results = m.sendMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false);
445 checkSendMessageResults(results);
446 return results.timestamp();
447 } catch (IOException | InvalidStickerException e) {
448 throw new Error.Failure(e.getMessage());
449 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
450 throw new Error.GroupNotFound(e.getMessage());
451 } catch (AttachmentInvalidException e) {
452 throw new Error.AttachmentInvalid(e.getMessage());
453 } catch (UnregisteredRecipientException e) {
454 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
455 }
456 }
457
458 @Override
459 public void sendGroupTyping(
460 final byte[] groupId, final boolean stop
461 ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity {
462 try {
463 final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
464 Set.of(getGroupRecipientIdentifier(groupId)));
465 checkSendMessageResults(results);
466 } catch (IOException e) {
467 throw new Error.Failure(e.getMessage());
468 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
469 throw new Error.GroupNotFound(e.getMessage());
470 }
471 }
472
473 @Override
474 public long sendGroupRemoteDeleteMessage(
475 final long targetSentTimestamp, final byte[] groupId
476 ) {
477 try {
478 final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
479 Set.of(getGroupRecipientIdentifier(groupId)));
480 checkSendMessageResults(results);
481 return results.timestamp();
482 } catch (IOException e) {
483 throw new Error.Failure(e.getMessage());
484 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
485 throw new Error.GroupNotFound(e.getMessage());
486 }
487 }
488
489 @Override
490 public long sendGroupMessageReaction(
491 final String emoji,
492 final boolean remove,
493 final String targetAuthor,
494 final long targetSentTimestamp,
495 final byte[] groupId
496 ) {
497 try {
498 final var results = m.sendMessageReaction(emoji,
499 remove,
500 getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
501 targetSentTimestamp,
502 Set.of(getGroupRecipientIdentifier(groupId)),
503 false);
504 checkSendMessageResults(results);
505 return results.timestamp();
506 } catch (IOException e) {
507 throw new Error.Failure(e.getMessage());
508 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
509 throw new Error.GroupNotFound(e.getMessage());
510 } catch (UnregisteredRecipientException e) {
511 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
512 }
513 }
514
515 // Since contact names might be empty if not defined, also potentially return
516 // the profile name
517 @Override
518 public String getContactName(final String number) {
519 final var name = m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber()));
520 return name == null ? "" : name;
521 }
522
523 @Override
524 public void setContactName(final String number, final String name) {
525 try {
526 m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name, "");
527 } catch (NotPrimaryDeviceException e) {
528 throw new Error.Failure("This command doesn't work on linked devices.");
529 } catch (UnregisteredRecipientException e) {
530 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
531 }
532 }
533
534 @Override
535 public void setExpirationTimer(final String number, final int expiration) {
536 try {
537 m.setExpirationTimer(getSingleRecipientIdentifier(number, m.getSelfNumber()), expiration);
538 } catch (IOException e) {
539 throw new Error.Failure(e.getMessage());
540 } catch (UnregisteredRecipientException e) {
541 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
542 }
543 }
544
545 @Override
546 public void setContactBlocked(final String number, final boolean blocked) {
547 try {
548 m.setContactsBlocked(List.of(getSingleRecipientIdentifier(number, m.getSelfNumber())), blocked);
549 } catch (NotPrimaryDeviceException e) {
550 throw new Error.Failure("This command doesn't work on linked devices.");
551 } catch (IOException e) {
552 throw new Error.Failure(e.getMessage());
553 } catch (UnregisteredRecipientException e) {
554 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
555 }
556 }
557
558 @Override
559 @Deprecated
560 public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
561 try {
562 m.setGroupsBlocked(List.of(getGroupId(groupId)), blocked);
563 } catch (NotPrimaryDeviceException e) {
564 throw new Error.Failure("This command doesn't work on linked devices.");
565 } catch (GroupNotFoundException e) {
566 throw new Error.GroupNotFound(e.getMessage());
567 } catch (IOException e) {
568 throw new Error.Failure(e.getMessage());
569 }
570 }
571
572 @Override
573 @Deprecated
574 public List<byte[]> getGroupIds() {
575 var groups = m.getGroups();
576 return groups.stream().map(g -> g.groupId().serialize()).toList();
577 }
578
579 @Override
580 public DBusPath getGroup(final byte[] groupId) {
581 updateGroups();
582 final var groupOptional = groups.stream().filter(g -> Arrays.equals(g.getId(), groupId)).findFirst();
583 if (groupOptional.isEmpty()) {
584 throw new Error.GroupNotFound("Group not found");
585 }
586 return groupOptional.get().getObjectPath();
587 }
588
589 @Override
590 public List<StructGroup> listGroups() {
591 updateGroups();
592 return groups;
593 }
594
595 @Override
596 @Deprecated
597 public String getGroupName(final byte[] groupId) {
598 var group = m.getGroup(getGroupId(groupId));
599 if (group == null || group.title() == null) {
600 return "";
601 } else {
602 return group.title();
603 }
604 }
605
606 @Override
607 @Deprecated
608 public List<String> getGroupMembers(final byte[] groupId) {
609 var group = m.getGroup(getGroupId(groupId));
610 if (group == null) {
611 return List.of();
612 } else {
613 final var members = group.members();
614 return getRecipientStrings(members);
615 }
616 }
617
618 @Override
619 public byte[] createGroup(
620 final String name, final List<String> members, final String avatar
621 ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber {
622 return updateGroupInternal(new byte[0], name, members, avatar);
623 }
624
625 @Override
626 @Deprecated
627 public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
628 return updateGroupInternal(groupId, name, members, avatar);
629 }
630
631 public byte[] updateGroupInternal(byte[] groupId, String name, List<String> members, String avatar) {
632 try {
633 groupId = nullIfEmpty(groupId);
634 name = nullIfEmpty(name);
635 avatar = nullIfEmpty(avatar);
636 final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber());
637 if (groupId == null) {
638 final var results = m.createGroup(name, memberIdentifiers, avatar);
639 updateGroups();
640 checkGroupSendMessageResults(results.second().timestamp(), results.second().results());
641 return results.first().serialize();
642 } else {
643 final var results = m.updateGroup(getGroupId(groupId),
644 UpdateGroup.newBuilder()
645 .withName(name)
646 .withMembers(memberIdentifiers)
647 .withAvatarFile(avatar)
648 .build());
649 if (results != null) {
650 checkGroupSendMessageResults(results.timestamp(), results.results());
651 }
652 return groupId;
653 }
654 } catch (IOException e) {
655 throw new Error.Failure(e.getMessage());
656 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
657 throw new Error.GroupNotFound(e.getMessage());
658 } catch (AttachmentInvalidException e) {
659 throw new Error.AttachmentInvalid(e.getMessage());
660 } catch (UnregisteredRecipientException e) {
661 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
662 }
663 }
664
665 @Override
666 @Deprecated
667 public boolean isRegistered() {
668 return true;
669 }
670
671 @Override
672 public boolean isRegistered(String number) {
673 var result = isRegistered(List.of(number));
674 return result.getFirst();
675 }
676
677 @Override
678 public List<Boolean> isRegistered(List<String> numbers) {
679 if (numbers.isEmpty()) {
680 return List.of();
681 }
682
683 Map<String, UserStatus> registered;
684 try {
685 registered = m.getUserStatus(new HashSet<>(numbers));
686 } catch (IOException e) {
687 throw new Error.Failure(e.getMessage());
688 } catch (RateLimitException e) {
689 throw new Error.Failure(e.getMessage()
690 + ", retry at "
691 + DateUtils.formatTimestamp(e.getNextAttemptTimestamp()));
692 }
693
694 return numbers.stream().map(number -> registered.get(number).uuid() != null).toList();
695 }
696
697 @Override
698 public void updateProfile(
699 String givenName,
700 String familyName,
701 String about,
702 String aboutEmoji,
703 String avatarPath,
704 final boolean removeAvatar
705 ) {
706 try {
707 givenName = nullIfEmpty(givenName);
708 familyName = nullIfEmpty(familyName);
709 about = nullIfEmpty(about);
710 aboutEmoji = nullIfEmpty(aboutEmoji);
711 avatarPath = nullIfEmpty(avatarPath);
712 final var avatarFile = removeAvatar || avatarPath == null ? null : avatarPath;
713 m.updateProfile(UpdateProfile.newBuilder()
714 .withGivenName(givenName)
715 .withFamilyName(familyName)
716 .withAbout(about)
717 .withAboutEmoji(aboutEmoji)
718 .withAvatar(avatarFile)
719 .withDeleteAvatar(removeAvatar)
720 .build());
721 } catch (IOException e) {
722 throw new Error.Failure(e.getMessage());
723 }
724 }
725
726 @Override
727 public void updateProfile(
728 final String name,
729 final String about,
730 final String aboutEmoji,
731 String avatarPath,
732 final boolean removeAvatar
733 ) {
734 updateProfile(name, "", about, aboutEmoji, avatarPath, removeAvatar);
735 }
736
737 @Override
738 public void removePin() {
739 try {
740 m.setRegistrationLockPin(Optional.empty());
741 } catch (IOException e) {
742 throw new Error.Failure("Remove pin error: " + e.getMessage());
743 } catch (NotPrimaryDeviceException e) {
744 throw new Error.Failure("This command doesn't work on linked devices.");
745 }
746 }
747
748 @Override
749 public void setPin(String registrationLockPin) {
750 try {
751 m.setRegistrationLockPin(Optional.of(registrationLockPin));
752 } catch (IOException e) {
753 throw new Error.Failure("Set pin error: " + e.getMessage());
754 } catch (NotPrimaryDeviceException e) {
755 throw new Error.Failure("This command doesn't work on linked devices.");
756 }
757 }
758
759 // Provide option to query a version string in order to react on potential
760 // future interface changes
761 @Override
762 public String version() {
763 return BaseConfig.PROJECT_VERSION;
764 }
765
766 // Create a unique list of Numbers from Identities and Contacts to really get
767 // all numbers the system knows
768 @Override
769 public List<String> listNumbers() {
770 return m.getRecipients(false, Optional.empty(), Set.of(), Optional.empty())
771 .stream()
772 .map(r -> r.getAddress().number().orElse(null))
773 .filter(Objects::nonNull)
774 .distinct()
775 .toList();
776 }
777
778 @Override
779 public List<String> getContactNumber(final String name) {
780 return m.getRecipients(false, Optional.empty(), Set.of(), Optional.of(name))
781 .stream()
782 .map(r -> r.getAddress().getLegacyIdentifier())
783 .toList();
784 }
785
786 @Override
787 @Deprecated
788 public void quitGroup(final byte[] groupId) {
789 var group = getGroupId(groupId);
790 try {
791 m.quitGroup(group, Set.of());
792 } catch (GroupNotFoundException | NotAGroupMemberException e) {
793 throw new Error.GroupNotFound(e.getMessage());
794 } catch (IOException | LastGroupAdminException e) {
795 throw new Error.Failure(e.getMessage());
796 } catch (UnregisteredRecipientException e) {
797 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
798 }
799 }
800
801 @Override
802 public byte[] joinGroup(final String groupLink) {
803 try {
804 final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
805 if (linkUrl == null) {
806 throw new Error.Failure("Group link is invalid:");
807 }
808 final var result = m.joinGroup(linkUrl);
809 return result.first().serialize();
810 } catch (PendingAdminApprovalException e) {
811 throw new Error.Failure("Pending admin approval: " + e.getMessage());
812 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | InactiveGroupLinkException e) {
813 throw new Error.Failure("Group link is invalid: " + e.getMessage());
814 } catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
815 throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
816 } catch (IOException e) {
817 throw new Error.Failure(e.getMessage());
818 }
819 }
820
821 @Override
822 public boolean isContactBlocked(final String number) {
823 return m.isContactBlocked(getSingleRecipientIdentifier(number, m.getSelfNumber()));
824 }
825
826 @Override
827 @Deprecated
828 public boolean isGroupBlocked(final byte[] groupId) {
829 var group = m.getGroup(getGroupId(groupId));
830 if (group == null) {
831 return false;
832 } else {
833 return group.isBlocked();
834 }
835 }
836
837 @Override
838 @Deprecated
839 public boolean isMember(final byte[] groupId) {
840 var group = m.getGroup(getGroupId(groupId));
841 if (group == null) {
842 return false;
843 } else {
844 return group.isMember();
845 }
846 }
847
848 @Override
849 public String uploadStickerPack(String stickerPackPath) {
850 File path = new File(stickerPackPath);
851 try {
852 return m.uploadStickerPack(path).toString();
853 } catch (IOException e) {
854 throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage());
855 } catch (StickerPackInvalidException e) {
856 throw new Error.Failure("Invalid sticker pack: " + e.getMessage());
857 }
858 }
859
860 private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
861 var error = SendMessageResultUtils.getErrorMessageFromSendMessageResult(result);
862
863 if (error == null) {
864 return;
865 }
866
867 final var message = "\nFailed to send message:\n" + error + '\n' + timestamp;
868
869 if (result.isIdentityFailure()) {
870 throw new Error.UntrustedIdentity(message);
871 } else {
872 throw new Error.Failure(message);
873 }
874 }
875
876 private void checkSendMessageResults(final SendMessageResults results) {
877 final var sendMessageResults = results.results().values().stream().findFirst();
878 if (results.results().size() == 1 && sendMessageResults.get().size() == 1) {
879 checkSendMessageResult(results.timestamp(), sendMessageResults.get().stream().findFirst().get());
880 return;
881 }
882
883 if (results.hasSuccess()) {
884 return;
885 }
886
887 var message = new StringBuilder();
888 message.append("Failed to send messages:\n");
889 var errors = SendMessageResultUtils.getErrorMessagesFromSendMessageResults(results.results());
890 for (var error : errors) {
891 message.append(error).append('\n');
892 }
893 message.append(results.timestamp());
894
895 throw new Error.Failure(message.toString());
896 }
897
898 private static void checkGroupSendMessageResults(
899 long timestamp, Collection<SendMessageResult> results
900 ) throws DBusExecutionException {
901 if (results.size() == 1) {
902 checkSendMessageResult(timestamp, results.stream().findFirst().get());
903 return;
904 }
905
906 var errors = SendMessageResultUtils.getErrorMessagesFromSendMessageResults(results);
907 if (errors.isEmpty() || errors.size() < results.size()) {
908 return;
909 }
910
911 var message = new StringBuilder();
912 message.append("Failed to send message:\n");
913 for (var error : errors) {
914 message.append(error).append('\n');
915 }
916 message.append(timestamp);
917
918 throw new Error.Failure(message.toString());
919 }
920
921 private static List<String> getRecipientStrings(final Set<RecipientAddress> members) {
922 return members.stream().map(RecipientAddress::getLegacyIdentifier).toList();
923 }
924
925 private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
926 final Collection<String> recipientStrings, final String localNumber
927 ) throws DBusExecutionException {
928 final var identifiers = new HashSet<RecipientIdentifier.Single>();
929 for (var recipientString : recipientStrings) {
930 identifiers.add(getSingleRecipientIdentifier(recipientString, localNumber));
931 }
932 return identifiers;
933 }
934
935 private static RecipientIdentifier.Single getSingleRecipientIdentifier(
936 final String recipientString, final String localNumber
937 ) throws DBusExecutionException {
938 try {
939 return RecipientIdentifier.Single.fromString(recipientString, localNumber);
940 } catch (InvalidNumberException e) {
941 throw new Error.InvalidNumber(e.getMessage());
942 }
943 }
944
945 private RecipientIdentifier.Group getGroupRecipientIdentifier(final byte[] groupId) {
946 return new RecipientIdentifier.Group(getGroupId(groupId));
947 }
948
949 private static GroupId getGroupId(byte[] groupId) throws DBusExecutionException {
950 try {
951 return GroupId.unknownVersion(groupId);
952 } catch (Throwable e) {
953 throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage());
954 }
955 }
956
957 private byte[] nullIfEmpty(final byte[] array) {
958 return array.length == 0 ? null : array;
959 }
960
961 private String nullIfEmpty(final String name) {
962 return name.isEmpty() ? null : name;
963 }
964
965 private String emptyIfNull(final String string) {
966 return string == null ? "" : string;
967 }
968
969 private static String getDeviceObjectPath(String basePath, long deviceId) {
970 return basePath + "/Devices/" + deviceId;
971 }
972
973 private void updateDevices() {
974 List<org.asamk.signal.manager.api.Device> linkedDevices;
975 try {
976 linkedDevices = m.getLinkedDevices();
977 } catch (IOException e) {
978 throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
979 }
980
981 unExportDevices();
982
983 linkedDevices.forEach(d -> {
984 final var object = new DbusSignalDeviceImpl(d);
985 final var deviceObjectPath = object.getObjectPath();
986 exportObject(object);
987 if (d.isThisDevice()) {
988 thisDevice = new DBusPath(deviceObjectPath);
989 }
990 this.devices.add(new StructDevice(new DBusPath(deviceObjectPath), (long) d.id(), emptyIfNull(d.name())));
991 });
992 }
993
994 private void unExportDevices() {
995 this.devices.stream()
996 .map(StructDevice::getObjectPath)
997 .map(DBusPath::getPath)
998 .forEach(connection::unExportObject);
999 this.devices.clear();
1000 }
1001
1002 private static String getGroupObjectPath(String basePath, byte[] groupId) {
1003 return basePath + "/Groups/" + makeValidObjectPathElement(Base64.getEncoder().encodeToString(groupId));
1004 }
1005
1006 private void updateGroups() {
1007 List<org.asamk.signal.manager.api.Group> groups;
1008 groups = m.getGroups();
1009
1010 unExportGroups();
1011
1012 groups.forEach(g -> {
1013 final var object = new DbusSignalGroupImpl(g.groupId());
1014 exportObject(object);
1015 this.groups.add(new StructGroup(new DBusPath(object.getObjectPath()),
1016 g.groupId().serialize(),
1017 emptyIfNull(g.title())));
1018 });
1019 }
1020
1021 private void unExportGroups() {
1022 this.groups.stream().map(StructGroup::getObjectPath).map(DBusPath::getPath).forEach(connection::unExportObject);
1023 this.groups.clear();
1024 }
1025
1026 private static String getConfigurationObjectPath(String basePath) {
1027 return basePath + "/Configuration";
1028 }
1029
1030 private void updateConfiguration() {
1031 unExportConfiguration();
1032 final var object = new DbusSignalConfigurationImpl();
1033 exportObject(object);
1034 }
1035
1036 private void unExportConfiguration() {
1037 final var objectPath = getConfigurationObjectPath(this.objectPath);
1038 connection.unExportObject(objectPath);
1039 }
1040
1041 private void exportObject(final DBusInterface object) {
1042 try {
1043 connection.exportObject(object);
1044 logger.debug("Exported dbus object: " + object.getObjectPath());
1045 } catch (DBusException e) {
1046 logger.warn("Failed to export dbus object (" + object.getObjectPath() + "): " + e.getMessage());
1047 }
1048 }
1049
1050 private void updateIdentities() {
1051 List<org.asamk.signal.manager.api.Identity> identities;
1052 identities = m.getIdentities();
1053
1054 unExportIdentities();
1055
1056 identities.forEach(i -> {
1057 final var object = new DbusSignalIdentityImpl(i);
1058 exportObject(object);
1059 this.identities.add(new StructIdentity(new DBusPath(object.getObjectPath()),
1060 i.recipient().uuid().map(UUID::toString).orElse(""),
1061 i.recipient().number().orElse("")));
1062 });
1063 }
1064
1065 private static String getIdentityObjectPath(String basePath, String id) {
1066 return basePath + "/Identities/" + makeValidObjectPathElement(id);
1067 }
1068
1069 private void unExportIdentities() {
1070 this.identities.stream()
1071 .map(StructIdentity::getObjectPath)
1072 .map(DBusPath::getPath)
1073 .forEach(connection::unExportObject);
1074 this.identities.clear();
1075 }
1076
1077 @Override
1078 public DBusPath getIdentity(String number) throws Error.Failure {
1079 final var found = identities.stream()
1080 .filter(identity -> identity.getNumber().equals(number) || identity.getUuid().equals(number))
1081 .findFirst();
1082
1083 if (found.isEmpty()) {
1084 throw new Error.Failure("Identity for " + number + " unknown");
1085 }
1086 return found.get().getObjectPath();
1087 }
1088
1089 @Override
1090 public List<StructIdentity> listIdentities() {
1091 updateIdentities();
1092 return this.identities;
1093 }
1094
1095 public class DbusSignalIdentityImpl extends DbusProperties implements Signal.Identity {
1096
1097 private final org.asamk.signal.manager.api.Identity identity;
1098
1099 public DbusSignalIdentityImpl(final org.asamk.signal.manager.api.Identity identity) {
1100 this.identity = identity;
1101 super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Identity",
1102 List.of(new DbusProperty<>("Number", () -> identity.recipient().number().orElse("")),
1103 new DbusProperty<>("Uuid",
1104 () -> identity.recipient().uuid().map(UUID::toString).orElse("")),
1105 new DbusProperty<>("Fingerprint", identity::getFingerprint),
1106 new DbusProperty<>("SafetyNumber", identity::safetyNumber),
1107 new DbusProperty<>("ScannableSafetyNumber", identity::scannableSafetyNumber),
1108 new DbusProperty<>("TrustLevel", identity::trustLevel),
1109 new DbusProperty<>("AddedDate", identity::dateAddedTimestamp))));
1110 }
1111
1112 @Override
1113 public String getObjectPath() {
1114 return getIdentityObjectPath(objectPath,
1115 identity.recipient().getLegacyIdentifier() + "_" + identity.recipient().getIdentifier());
1116 }
1117
1118 @Override
1119 public void trust() throws Error.Failure {
1120 var recipient = RecipientIdentifier.Single.fromAddress(identity.recipient());
1121 try {
1122 m.trustIdentityAllKeys(recipient);
1123 } catch (UnregisteredRecipientException e) {
1124 throw new Error.Failure("The user " + e.getSender().getIdentifier() + " is not registered.");
1125 }
1126 updateIdentities();
1127 }
1128
1129 @Override
1130 public void trustVerified(String safetyNumber) throws Error.Failure {
1131 var recipient = RecipientIdentifier.Single.fromAddress(identity.recipient());
1132
1133 if (safetyNumber == null) {
1134 throw new Error.Failure("You need to specify a fingerprint/safety number");
1135 }
1136 final IdentityVerificationCode verificationCode;
1137 try {
1138 verificationCode = IdentityVerificationCode.parse(safetyNumber);
1139 } catch (Exception e) {
1140 throw new Error.Failure(
1141 "Safety number has invalid format, either specify the old hex fingerprint or the new safety number");
1142 }
1143
1144 try {
1145 final var res = m.trustIdentityVerified(recipient, verificationCode);
1146 if (!res) {
1147 throw new Error.Failure(
1148 "Failed to set the trust for this number, make sure the number and the fingerprint/safety number are correct.");
1149 }
1150 } catch (UnregisteredRecipientException e) {
1151 throw new Error.Failure("The user " + e.getSender().getIdentifier() + " is not registered.");
1152 }
1153 updateIdentities();
1154 }
1155 }
1156
1157 public class DbusSignalDeviceImpl extends DbusProperties implements Signal.Device {
1158
1159 private final org.asamk.signal.manager.api.Device device;
1160
1161 public DbusSignalDeviceImpl(final org.asamk.signal.manager.api.Device device) {
1162 super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Device",
1163 List.of(new DbusProperty<>("Id", device::id),
1164 new DbusProperty<>("Name", () -> emptyIfNull(device.name()), this::setDeviceName),
1165 new DbusProperty<>("Created", device::created),
1166 new DbusProperty<>("LastSeen", device::lastSeen))));
1167 this.device = device;
1168 }
1169
1170 @Override
1171 public String getObjectPath() {
1172 return getDeviceObjectPath(objectPath, device.id());
1173 }
1174
1175 @Override
1176 public void removeDevice() throws Error.Failure {
1177 try {
1178 m.removeLinkedDevices(device.id());
1179 updateDevices();
1180 } catch (IOException e) {
1181 throw new Error.Failure(e.getMessage());
1182 }
1183 }
1184
1185 private void setDeviceName(String name) {
1186 if (!device.isThisDevice()) {
1187 throw new Error.Failure("Only the name of this device can be changed");
1188 }
1189 try {
1190 m.updateAccountAttributes(name);
1191 // update device list
1192 updateDevices();
1193 } catch (IOException e) {
1194 throw new Error.Failure(e.getMessage());
1195 }
1196 }
1197 }
1198
1199 public class DbusSignalConfigurationImpl extends DbusProperties implements Signal.Configuration {
1200
1201 public DbusSignalConfigurationImpl() {
1202 super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Configuration",
1203 List.of(new DbusProperty<>("ReadReceipts", this::getReadReceipts, this::setReadReceipts),
1204 new DbusProperty<>("UnidentifiedDeliveryIndicators",
1205 this::getUnidentifiedDeliveryIndicators,
1206 this::setUnidentifiedDeliveryIndicators),
1207 new DbusProperty<>("TypingIndicators",
1208 this::getTypingIndicators,
1209 this::setTypingIndicators),
1210 new DbusProperty<>("LinkPreviews", this::getLinkPreviews, this::setLinkPreviews))));
1211
1212 }
1213
1214 @Override
1215 public String getObjectPath() {
1216 return getConfigurationObjectPath(objectPath);
1217 }
1218
1219 public void setReadReceipts(Boolean readReceipts) {
1220 setConfiguration(readReceipts, null, null, null);
1221 }
1222
1223 public void setUnidentifiedDeliveryIndicators(Boolean unidentifiedDeliveryIndicators) {
1224 setConfiguration(null, unidentifiedDeliveryIndicators, null, null);
1225 }
1226
1227 public void setTypingIndicators(Boolean typingIndicators) {
1228 setConfiguration(null, null, typingIndicators, null);
1229 }
1230
1231 public void setLinkPreviews(Boolean linkPreviews) {
1232 setConfiguration(null, null, null, linkPreviews);
1233 }
1234
1235 private void setConfiguration(
1236 Boolean readReceipts,
1237 Boolean unidentifiedDeliveryIndicators,
1238 Boolean typingIndicators,
1239 Boolean linkPreviews
1240 ) {
1241 try {
1242 m.updateConfiguration(new org.asamk.signal.manager.api.Configuration(Optional.ofNullable(readReceipts),
1243 Optional.ofNullable(unidentifiedDeliveryIndicators),
1244 Optional.ofNullable(typingIndicators),
1245 Optional.ofNullable(linkPreviews)));
1246 } catch (NotPrimaryDeviceException e) {
1247 throw new Error.Failure("This command doesn't work on linked devices.");
1248 }
1249 }
1250
1251 private boolean getReadReceipts() {
1252 return m.getConfiguration().readReceipts().orElse(false);
1253 }
1254
1255 private boolean getUnidentifiedDeliveryIndicators() {
1256 return m.getConfiguration().unidentifiedDeliveryIndicators().orElse(false);
1257 }
1258
1259 private boolean getTypingIndicators() {
1260 return m.getConfiguration().typingIndicators().orElse(false);
1261 }
1262
1263 private boolean getLinkPreviews() {
1264 return m.getConfiguration().linkPreviews().orElse(false);
1265 }
1266 }
1267
1268 public class DbusSignalGroupImpl extends DbusProperties implements Signal.Group {
1269
1270 private final GroupId groupId;
1271
1272 public DbusSignalGroupImpl(final GroupId groupId) {
1273 this.groupId = groupId;
1274 super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Group",
1275 List.of(new DbusProperty<>("Id", groupId::serialize),
1276 new DbusProperty<>("Name", () -> emptyIfNull(getGroup().title()), this::setGroupName),
1277 new DbusProperty<>("Description",
1278 () -> emptyIfNull(getGroup().description()),
1279 this::setGroupDescription),
1280 new DbusProperty<>("Avatar", this::setGroupAvatar),
1281 new DbusProperty<>("IsBlocked", () -> getGroup().isBlocked(), this::setIsBlocked),
1282 new DbusProperty<>("IsMember", () -> getGroup().isMember()),
1283 new DbusProperty<>("IsAdmin", () -> getGroup().isAdmin()),
1284 new DbusProperty<>("MessageExpirationTimer",
1285 () -> getGroup().messageExpirationTimer(),
1286 this::setMessageExpirationTime),
1287 new DbusProperty<>("Members",
1288 () -> new Variant<>(getRecipientStrings(getGroup().members()), "as")),
1289 new DbusProperty<>("PendingMembers",
1290 () -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")),
1291 new DbusProperty<>("RequestingMembers",
1292 () -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")),
1293 new DbusProperty<>("Admins",
1294 () -> new Variant<>(getRecipientStrings(getGroup().adminMembers()), "as")),
1295 new DbusProperty<>("Banned",
1296 () -> new Variant<>(getRecipientStrings(getGroup().bannedMembers()), "as")),
1297 new DbusProperty<>("PermissionAddMember",
1298 () -> getGroup().permissionAddMember().name(),
1299 this::setGroupPermissionAddMember),
1300 new DbusProperty<>("PermissionEditDetails",
1301 () -> getGroup().permissionEditDetails().name(),
1302 this::setGroupPermissionEditDetails),
1303 new DbusProperty<>("PermissionSendMessage",
1304 () -> getGroup().permissionSendMessage().name(),
1305 this::setGroupPermissionSendMessage),
1306 new DbusProperty<>("GroupInviteLink", () -> {
1307 final var groupInviteLinkUrl = getGroup().groupInviteLinkUrl();
1308 return groupInviteLinkUrl == null ? "" : groupInviteLinkUrl.getUrl();
1309 }))));
1310 }
1311
1312 @Override
1313 public String getObjectPath() {
1314 return getGroupObjectPath(objectPath, groupId.serialize());
1315 }
1316
1317 @Override
1318 public void quitGroup() throws Error.Failure {
1319 try {
1320 m.quitGroup(groupId, Set.of());
1321 } catch (GroupNotFoundException e) {
1322 throw new Error.GroupNotFound(e.getMessage());
1323 } catch (NotAGroupMemberException e) {
1324 throw new Error.NotAGroupMember(e.getMessage());
1325 } catch (IOException e) {
1326 throw new Error.Failure(e.getMessage());
1327 } catch (LastGroupAdminException e) {
1328 throw new Error.LastGroupAdmin(e.getMessage());
1329 } catch (UnregisteredRecipientException e) {
1330 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
1331 }
1332 }
1333
1334 @Override
1335 public void deleteGroup() throws Error.Failure, Error.LastGroupAdmin {
1336 try {
1337 m.deleteGroup(groupId);
1338 } catch (IOException e) {
1339 throw new Error.Failure(e.getMessage());
1340 }
1341 updateGroups();
1342 }
1343
1344 @Override
1345 public void addMembers(final List<String> recipients) throws Error.Failure {
1346 final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
1347 updateGroup(UpdateGroup.newBuilder().withMembers(memberIdentifiers).build());
1348 }
1349
1350 @Override
1351 public void removeMembers(final List<String> recipients) throws Error.Failure {
1352 final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
1353 updateGroup(UpdateGroup.newBuilder().withRemoveMembers(memberIdentifiers).build());
1354 }
1355
1356 @Override
1357 public void addAdmins(final List<String> recipients) throws Error.Failure {
1358 final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
1359 updateGroup(UpdateGroup.newBuilder().withAdmins(memberIdentifiers).build());
1360 }
1361
1362 @Override
1363 public void removeAdmins(final List<String> recipients) throws Error.Failure {
1364 final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
1365 updateGroup(UpdateGroup.newBuilder().withRemoveAdmins(memberIdentifiers).build());
1366 }
1367
1368 @Override
1369 public void resetLink() throws Error.Failure {
1370 updateGroup(UpdateGroup.newBuilder().withResetGroupLink(true).build());
1371 }
1372
1373 @Override
1374 public void disableLink() throws Error.Failure {
1375 updateGroup(UpdateGroup.newBuilder().withGroupLinkState(GroupLinkState.DISABLED).build());
1376 }
1377
1378 @Override
1379 public void enableLink(final boolean requiresApproval) throws Error.Failure {
1380 updateGroup(UpdateGroup.newBuilder()
1381 .withGroupLinkState(requiresApproval
1382 ? GroupLinkState.ENABLED_WITH_APPROVAL
1383 : GroupLinkState.ENABLED)
1384 .build());
1385 }
1386
1387 private org.asamk.signal.manager.api.Group getGroup() {
1388 return m.getGroup(groupId);
1389 }
1390
1391 private void setGroupName(final String name) {
1392 updateGroup(UpdateGroup.newBuilder().withName(name).build());
1393 }
1394
1395 private void setGroupDescription(final String description) {
1396 updateGroup(UpdateGroup.newBuilder().withDescription(description).build());
1397 }
1398
1399 private void setGroupAvatar(final String avatar) {
1400 updateGroup(UpdateGroup.newBuilder().withAvatarFile(avatar).build());
1401 }
1402
1403 private void setMessageExpirationTime(final int expirationTime) {
1404 updateGroup(UpdateGroup.newBuilder().withExpirationTimer(expirationTime).build());
1405 }
1406
1407 private void setGroupPermissionAddMember(final String permission) {
1408 updateGroup(UpdateGroup.newBuilder().withAddMemberPermission(GroupPermission.valueOf(permission)).build());
1409 }
1410
1411 private void setGroupPermissionEditDetails(final String permission) {
1412 updateGroup(UpdateGroup.newBuilder()
1413 .withEditDetailsPermission(GroupPermission.valueOf(permission))
1414 .build());
1415 }
1416
1417 private void setGroupPermissionSendMessage(final String permission) {
1418 updateGroup(UpdateGroup.newBuilder()
1419 .withIsAnnouncementGroup(GroupPermission.valueOf(permission) == GroupPermission.ONLY_ADMINS)
1420 .build());
1421 }
1422
1423 private void setIsBlocked(final boolean isBlocked) {
1424 try {
1425 m.setGroupsBlocked(List.of(groupId), isBlocked);
1426 } catch (NotPrimaryDeviceException e) {
1427 throw new Error.Failure("This command doesn't work on linked devices.");
1428 } catch (GroupNotFoundException e) {
1429 throw new Error.GroupNotFound(e.getMessage());
1430 } catch (IOException e) {
1431 throw new Error.Failure(e.getMessage());
1432 }
1433 }
1434
1435 private void updateGroup(final UpdateGroup updateGroup) {
1436 try {
1437 m.updateGroup(groupId, updateGroup);
1438 } catch (IOException e) {
1439 throw new Error.Failure(e.getMessage());
1440 } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
1441 throw new Error.GroupNotFound(e.getMessage());
1442 } catch (AttachmentInvalidException e) {
1443 throw new Error.AttachmentInvalid(e.getMessage());
1444 } catch (UnregisteredRecipientException e) {
1445 throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
1446 }
1447 }
1448 }
1449 }