]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
Add exportObject helper method
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusManagerImpl.java
1 package org.asamk.signal.dbus;
2
3 import org.asamk.Signal;
4 import org.asamk.signal.DbusConfig;
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.Configuration;
11 import org.asamk.signal.manager.api.Device;
12 import org.asamk.signal.manager.api.Group;
13 import org.asamk.signal.manager.api.Identity;
14 import org.asamk.signal.manager.api.InactiveGroupLinkException;
15 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
16 import org.asamk.signal.manager.api.Message;
17 import org.asamk.signal.manager.api.MessageEnvelope;
18 import org.asamk.signal.manager.api.Pair;
19 import org.asamk.signal.manager.api.RecipientIdentifier;
20 import org.asamk.signal.manager.api.SendGroupMessageResults;
21 import org.asamk.signal.manager.api.SendMessageResults;
22 import org.asamk.signal.manager.api.TypingAction;
23 import org.asamk.signal.manager.api.UpdateGroup;
24 import org.asamk.signal.manager.groups.GroupId;
25 import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
26 import org.asamk.signal.manager.groups.GroupNotFoundException;
27 import org.asamk.signal.manager.groups.GroupPermission;
28 import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
29 import org.asamk.signal.manager.groups.LastGroupAdminException;
30 import org.asamk.signal.manager.groups.NotAGroupMemberException;
31 import org.asamk.signal.manager.storage.recipients.Contact;
32 import org.asamk.signal.manager.storage.recipients.Profile;
33 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
34 import org.freedesktop.dbus.DBusMap;
35 import org.freedesktop.dbus.DBusPath;
36 import org.freedesktop.dbus.connections.impl.DBusConnection;
37 import org.freedesktop.dbus.exceptions.DBusException;
38 import org.freedesktop.dbus.interfaces.DBusInterface;
39 import org.freedesktop.dbus.interfaces.DBusSigHandler;
40 import org.freedesktop.dbus.types.Variant;
41
42 import java.io.File;
43 import java.io.IOException;
44 import java.net.URI;
45 import java.net.URISyntaxException;
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Optional;
52 import java.util.Set;
53 import java.util.UUID;
54 import java.util.concurrent.TimeUnit;
55 import java.util.function.Function;
56 import java.util.function.Supplier;
57 import java.util.stream.Collectors;
58
59 /**
60 * This class implements the Manager interface using the DBus Signal interface, where possible.
61 * It's used for the signal-cli dbus client mode (--dbus, --dbus-system)
62 */
63 public class DbusManagerImpl implements Manager {
64
65 private final Signal signal;
66 private final DBusConnection connection;
67
68 private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
69 private DBusSigHandler<Signal.MessageReceivedV2> dbusMsgHandler;
70 private DBusSigHandler<Signal.ReceiptReceivedV2> dbusRcptHandler;
71 private DBusSigHandler<Signal.SyncMessageReceivedV2> dbusSyncHandler;
72
73 public DbusManagerImpl(final Signal signal, DBusConnection connection) {
74 this.signal = signal;
75 this.connection = connection;
76 }
77
78 @Override
79 public String getSelfNumber() {
80 return signal.getSelfNumber();
81 }
82
83 @Override
84 public void checkAccountState() throws IOException {
85 throw new UnsupportedOperationException();
86 }
87
88 @Override
89 public Map<String, Pair<String, UUID>> areUsersRegistered(final Set<String> numbers) throws IOException {
90 final var numbersList = new ArrayList<>(numbers);
91 final var registered = signal.isRegistered(numbersList);
92
93 final var result = new HashMap<String, Pair<String, UUID>>();
94 for (var i = 0; i < numbersList.size(); i++) {
95 result.put(numbersList.get(i),
96 new Pair<>(numbersList.get(i), registered.get(i) ? RecipientAddress.UNKNOWN_UUID : null));
97 }
98 return result;
99 }
100
101 @Override
102 public void updateAccountAttributes(final String deviceName) throws IOException {
103 if (deviceName != null) {
104 final var devicePath = signal.getThisDevice();
105 getRemoteObject(devicePath, Signal.Device.class).Set("org.asamk.Signal.Device", "Name", deviceName);
106 }
107 }
108
109 @Override
110 public Configuration getConfiguration() {
111 throw new UnsupportedOperationException();
112 }
113
114 @Override
115 public void updateConfiguration(Configuration configuration) throws IOException {
116 throw new UnsupportedOperationException();
117 }
118
119 @Override
120 public void setProfile(
121 final String givenName,
122 final String familyName,
123 final String about,
124 final String aboutEmoji,
125 final Optional<File> avatar
126 ) throws IOException {
127 signal.updateProfile(emptyIfNull(givenName),
128 emptyIfNull(familyName),
129 emptyIfNull(about),
130 emptyIfNull(aboutEmoji),
131 avatar == null ? "" : avatar.map(File::getPath).orElse(""),
132 avatar != null && !avatar.isPresent());
133 }
134
135 @Override
136 public void unregister() throws IOException {
137 throw new UnsupportedOperationException();
138 }
139
140 @Override
141 public void deleteAccount() throws IOException {
142 throw new UnsupportedOperationException();
143 }
144
145 @Override
146 public void submitRateLimitRecaptchaChallenge(final String challenge, final String captcha) throws IOException {
147 signal.submitRateLimitChallenge(challenge, captcha);
148 }
149
150 @Override
151 public List<Device> getLinkedDevices() throws IOException {
152 final var thisDevice = signal.getThisDevice();
153 return signal.listDevices().stream().map(d -> {
154 final var device = getRemoteObject(d.getObjectPath(),
155 Signal.Device.class).GetAll("org.asamk.Signal.Device");
156 return new Device((long) device.get("Id").getValue(),
157 (String) device.get("Name").getValue(),
158 (long) device.get("Created").getValue(),
159 (long) device.get("LastSeen").getValue(),
160 thisDevice.equals(d.getObjectPath()));
161 }).collect(Collectors.toList());
162 }
163
164 @Override
165 public void removeLinkedDevices(final long deviceId) throws IOException {
166 final var devicePath = signal.getDevice(deviceId);
167 getRemoteObject(devicePath, Signal.Device.class).removeDevice();
168 }
169
170 @Override
171 public void addDeviceLink(final URI linkUri) throws IOException, InvalidDeviceLinkException {
172 signal.addDevice(linkUri.toString());
173 }
174
175 @Override
176 public void setRegistrationLockPin(final Optional<String> pin) throws IOException {
177 if (pin.isPresent()) {
178 signal.setPin(pin.get());
179 } else {
180 signal.removePin();
181 }
182 }
183
184 @Override
185 public Profile getRecipientProfile(final RecipientIdentifier.Single recipient) {
186 throw new UnsupportedOperationException();
187 }
188
189 @Override
190 public List<Group> getGroups() {
191 final var groups = signal.listGroups();
192 return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).collect(Collectors.toList());
193 }
194
195 @Override
196 public SendGroupMessageResults quitGroup(
197 final GroupId groupId, final Set<RecipientIdentifier.Single> groupAdmins
198 ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException {
199 if (groupAdmins.size() > 0) {
200 throw new UnsupportedOperationException();
201 }
202 final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
203 group.quitGroup();
204 return new SendGroupMessageResults(0, List.of());
205 }
206
207 @Override
208 public void deleteGroup(final GroupId groupId) throws IOException {
209 throw new UnsupportedOperationException();
210 }
211
212 @Override
213 public Pair<GroupId, SendGroupMessageResults> createGroup(
214 final String name, final Set<RecipientIdentifier.Single> members, final File avatarFile
215 ) throws IOException, AttachmentInvalidException {
216 final var newGroupId = signal.createGroup(emptyIfNull(name),
217 members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
218 avatarFile == null ? "" : avatarFile.getPath());
219 return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
220 }
221
222 @Override
223 public SendGroupMessageResults updateGroup(
224 final GroupId groupId, final UpdateGroup updateGroup
225 ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
226 final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
227 if (updateGroup.getName() != null) {
228 group.Set("org.asamk.Signal.Group", "Name", updateGroup.getName());
229 }
230 if (updateGroup.getDescription() != null) {
231 group.Set("org.asamk.Signal.Group", "Description", updateGroup.getDescription());
232 }
233 if (updateGroup.getAvatarFile() != null) {
234 group.Set("org.asamk.Signal.Group",
235 "Avatar",
236 updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile().getPath());
237 }
238 if (updateGroup.getExpirationTimer() != null) {
239 group.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup.getExpirationTimer());
240 }
241 if (updateGroup.getAddMemberPermission() != null) {
242 group.Set("org.asamk.Signal.Group", "PermissionAddMember", updateGroup.getAddMemberPermission().name());
243 }
244 if (updateGroup.getEditDetailsPermission() != null) {
245 group.Set("org.asamk.Signal.Group", "PermissionEditDetails", updateGroup.getEditDetailsPermission().name());
246 }
247 if (updateGroup.getIsAnnouncementGroup() != null) {
248 group.Set("org.asamk.Signal.Group",
249 "PermissionSendMessage",
250 updateGroup.getIsAnnouncementGroup()
251 ? GroupPermission.ONLY_ADMINS.name()
252 : GroupPermission.EVERY_MEMBER.name());
253 }
254 if (updateGroup.getMembers() != null) {
255 group.addMembers(updateGroup.getMembers()
256 .stream()
257 .map(RecipientIdentifier.Single::getIdentifier)
258 .collect(Collectors.toList()));
259 }
260 if (updateGroup.getRemoveMembers() != null) {
261 group.removeMembers(updateGroup.getRemoveMembers()
262 .stream()
263 .map(RecipientIdentifier.Single::getIdentifier)
264 .collect(Collectors.toList()));
265 }
266 if (updateGroup.getAdmins() != null) {
267 group.addAdmins(updateGroup.getAdmins()
268 .stream()
269 .map(RecipientIdentifier.Single::getIdentifier)
270 .collect(Collectors.toList()));
271 }
272 if (updateGroup.getRemoveAdmins() != null) {
273 group.removeAdmins(updateGroup.getRemoveAdmins()
274 .stream()
275 .map(RecipientIdentifier.Single::getIdentifier)
276 .collect(Collectors.toList()));
277 }
278 if (updateGroup.isResetGroupLink()) {
279 group.resetLink();
280 }
281 if (updateGroup.getGroupLinkState() != null) {
282 switch (updateGroup.getGroupLinkState()) {
283 case DISABLED -> group.disableLink();
284 case ENABLED -> group.enableLink(false);
285 case ENABLED_WITH_APPROVAL -> group.enableLink(true);
286 }
287 }
288 return new SendGroupMessageResults(0, List.of());
289 }
290
291 @Override
292 public Pair<GroupId, SendGroupMessageResults> joinGroup(final GroupInviteLinkUrl inviteLinkUrl) throws IOException, InactiveGroupLinkException {
293 final var newGroupId = signal.joinGroup(inviteLinkUrl.getUrl());
294 return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
295 }
296
297 @Override
298 public void sendTypingMessage(
299 final TypingAction action, final Set<RecipientIdentifier> recipients
300 ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
301 for (final var recipient : recipients) {
302 if (recipient instanceof RecipientIdentifier.Single) {
303 signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(),
304 action == TypingAction.STOP);
305 } else if (recipient instanceof RecipientIdentifier.Group) {
306 throw new UnsupportedOperationException();
307 }
308 }
309 }
310
311 @Override
312 public void sendReadReceipt(
313 final RecipientIdentifier.Single sender, final List<Long> messageIds
314 ) throws IOException, UntrustedIdentityException {
315 signal.sendReadReceipt(sender.getIdentifier(), messageIds);
316 }
317
318 @Override
319 public void sendViewedReceipt(
320 final RecipientIdentifier.Single sender, final List<Long> messageIds
321 ) throws IOException, UntrustedIdentityException {
322 signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
323 }
324
325 @Override
326 public SendMessageResults sendMessage(
327 final Message message, final Set<RecipientIdentifier> recipients
328 ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
329 return handleMessage(recipients,
330 numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers),
331 () -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()),
332 groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
333 }
334
335 @Override
336 public SendMessageResults sendRemoteDeleteMessage(
337 final long targetSentTimestamp, final Set<RecipientIdentifier> recipients
338 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
339 return handleMessage(recipients,
340 numbers -> signal.sendRemoteDeleteMessage(targetSentTimestamp, numbers),
341 () -> signal.sendRemoteDeleteMessage(targetSentTimestamp, signal.getSelfNumber()),
342 groupId -> signal.sendGroupRemoteDeleteMessage(targetSentTimestamp, groupId));
343 }
344
345 @Override
346 public SendMessageResults sendMessageReaction(
347 final String emoji,
348 final boolean remove,
349 final RecipientIdentifier.Single targetAuthor,
350 final long targetSentTimestamp,
351 final Set<RecipientIdentifier> recipients
352 ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
353 return handleMessage(recipients,
354 numbers -> signal.sendMessageReaction(emoji,
355 remove,
356 targetAuthor.getIdentifier(),
357 targetSentTimestamp,
358 numbers),
359 () -> signal.sendMessageReaction(emoji,
360 remove,
361 targetAuthor.getIdentifier(),
362 targetSentTimestamp,
363 signal.getSelfNumber()),
364 groupId -> signal.sendGroupMessageReaction(emoji,
365 remove,
366 targetAuthor.getIdentifier(),
367 targetSentTimestamp,
368 groupId));
369 }
370
371 @Override
372 public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
373 signal.sendEndSessionMessage(recipients.stream()
374 .map(RecipientIdentifier.Single::getIdentifier)
375 .collect(Collectors.toList()));
376 return new SendMessageResults(0, Map.of());
377 }
378
379 @Override
380 public void setContactName(
381 final RecipientIdentifier.Single recipient, final String name
382 ) throws NotMasterDeviceException {
383 signal.setContactName(recipient.getIdentifier(), name);
384 }
385
386 @Override
387 public void setContactBlocked(
388 final RecipientIdentifier.Single recipient, final boolean blocked
389 ) throws NotMasterDeviceException, IOException {
390 signal.setContactBlocked(recipient.getIdentifier(), blocked);
391 }
392
393 @Override
394 public void setGroupBlocked(
395 final GroupId groupId, final boolean blocked
396 ) throws GroupNotFoundException, IOException {
397 setGroupProperty(groupId, "IsBlocked", blocked);
398 }
399
400 private void setGroupProperty(final GroupId groupId, final String propertyName, final boolean blocked) {
401 final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
402 group.Set("org.asamk.Signal.Group", propertyName, blocked);
403 }
404
405 @Override
406 public void setExpirationTimer(
407 final RecipientIdentifier.Single recipient, final int messageExpirationTimer
408 ) throws IOException {
409 signal.setExpirationTimer(recipient.getIdentifier(), messageExpirationTimer);
410 }
411
412 @Override
413 public URI uploadStickerPack(final File path) throws IOException, StickerPackInvalidException {
414 try {
415 return new URI(signal.uploadStickerPack(path.getPath()));
416 } catch (URISyntaxException e) {
417 throw new AssertionError(e);
418 }
419 }
420
421 @Override
422 public void requestAllSyncData() throws IOException {
423 signal.sendSyncRequest();
424 }
425
426 @Override
427 public void addReceiveHandler(final ReceiveMessageHandler handler) {
428 synchronized (messageHandlers) {
429 if (messageHandlers.size() == 0) {
430 installMessageHandlers();
431 }
432 messageHandlers.add(handler);
433 }
434 }
435
436 @Override
437 public void removeReceiveHandler(final ReceiveMessageHandler handler) {
438 synchronized (messageHandlers) {
439 messageHandlers.remove(handler);
440 if (messageHandlers.size() == 0) {
441 uninstallMessageHandlers();
442 }
443 }
444 }
445
446 @Override
447 public boolean isReceiving() {
448 synchronized (messageHandlers) {
449 return messageHandlers.size() > 0;
450 }
451 }
452
453 @Override
454 public void receiveMessages(final ReceiveMessageHandler handler) throws IOException {
455 addReceiveHandler(handler);
456 try {
457 synchronized (this) {
458 this.wait();
459 }
460 } catch (InterruptedException ignored) {
461 }
462 removeReceiveHandler(handler);
463 }
464
465 @Override
466 public void receiveMessages(
467 final long timeout, final TimeUnit unit, final ReceiveMessageHandler handler
468 ) throws IOException {
469 addReceiveHandler(handler);
470 try {
471 Thread.sleep(unit.toMillis(timeout));
472 } catch (InterruptedException ignored) {
473 }
474 removeReceiveHandler(handler);
475 }
476
477 @Override
478 public void setIgnoreAttachments(final boolean ignoreAttachments) {
479 }
480
481 @Override
482 public boolean hasCaughtUpWithOldMessages() {
483 return true;
484 }
485
486 @Override
487 public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
488 return signal.isContactBlocked(recipient.getIdentifier());
489 }
490
491 @Override
492 public void sendContacts() throws IOException {
493 signal.sendContacts();
494 }
495
496 @Override
497 public List<Pair<RecipientAddress, Contact>> getContacts() {
498 throw new UnsupportedOperationException();
499 }
500
501 @Override
502 public String getContactOrProfileName(final RecipientIdentifier.Single recipient) {
503 return signal.getContactName(recipient.getIdentifier());
504 }
505
506 @Override
507 public Group getGroup(final GroupId groupId) {
508 final var groupPath = signal.getGroup(groupId.serialize());
509 return getGroup(groupPath);
510 }
511
512 @SuppressWarnings("unchecked")
513 private Group getGroup(final DBusPath groupPath) {
514 final var group = getRemoteObject(groupPath, Signal.Group.class).GetAll("org.asamk.Signal.Group");
515 final var id = (byte[]) group.get("Id").getValue();
516 try {
517 return new Group(GroupId.unknownVersion(id),
518 (String) group.get("Name").getValue(),
519 (String) group.get("Description").getValue(),
520 GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()),
521 ((List<String>) group.get("Members").getValue()).stream()
522 .map(m -> new RecipientAddress(null, m))
523 .collect(Collectors.toSet()),
524 ((List<String>) group.get("PendingMembers").getValue()).stream()
525 .map(m -> new RecipientAddress(null, m))
526 .collect(Collectors.toSet()),
527 ((List<String>) group.get("RequestingMembers").getValue()).stream()
528 .map(m -> new RecipientAddress(null, m))
529 .collect(Collectors.toSet()),
530 ((List<String>) group.get("Admins").getValue()).stream()
531 .map(m -> new RecipientAddress(null, m))
532 .collect(Collectors.toSet()),
533 (boolean) group.get("IsBlocked").getValue(),
534 (int) group.get("MessageExpirationTimer").getValue(),
535 GroupPermission.valueOf((String) group.get("PermissionAddMember").getValue()),
536 GroupPermission.valueOf((String) group.get("PermissionEditDetails").getValue()),
537 GroupPermission.valueOf((String) group.get("PermissionSendMessage").getValue()),
538 (boolean) group.get("IsMember").getValue(),
539 (boolean) group.get("IsAdmin").getValue());
540 } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
541 throw new AssertionError(e);
542 }
543 }
544
545 @Override
546 public List<Identity> getIdentities() {
547 throw new UnsupportedOperationException();
548 }
549
550 @Override
551 public List<Identity> getIdentities(final RecipientIdentifier.Single recipient) {
552 throw new UnsupportedOperationException();
553 }
554
555 @Override
556 public boolean trustIdentityVerified(final RecipientIdentifier.Single recipient, final byte[] fingerprint) {
557 throw new UnsupportedOperationException();
558 }
559
560 @Override
561 public boolean trustIdentityVerifiedSafetyNumber(
562 final RecipientIdentifier.Single recipient, final String safetyNumber
563 ) {
564 throw new UnsupportedOperationException();
565 }
566
567 @Override
568 public boolean trustIdentityVerifiedSafetyNumber(
569 final RecipientIdentifier.Single recipient, final byte[] safetyNumber
570 ) {
571 throw new UnsupportedOperationException();
572 }
573
574 @Override
575 public boolean trustIdentityAllKeys(final RecipientIdentifier.Single recipient) {
576 throw new UnsupportedOperationException();
577 }
578
579 @Override
580 public void close() throws IOException {
581 synchronized (this) {
582 this.notify();
583 }
584 synchronized (messageHandlers) {
585 messageHandlers.clear();
586 uninstallMessageHandlers();
587 }
588 }
589
590 private SendMessageResults handleMessage(
591 Set<RecipientIdentifier> recipients,
592 Function<List<String>, Long> recipientsHandler,
593 Supplier<Long> noteToSelfHandler,
594 Function<byte[], Long> groupHandler
595 ) {
596 long timestamp = 0;
597 final var singleRecipients = recipients.stream()
598 .filter(r -> r instanceof RecipientIdentifier.Single)
599 .map(RecipientIdentifier.Single.class::cast)
600 .map(RecipientIdentifier.Single::getIdentifier)
601 .collect(Collectors.toList());
602 if (singleRecipients.size() > 0) {
603 timestamp = recipientsHandler.apply(singleRecipients);
604 }
605
606 if (recipients.contains(RecipientIdentifier.NoteToSelf.INSTANCE)) {
607 timestamp = noteToSelfHandler.get();
608 }
609 final var groupRecipients = recipients.stream()
610 .filter(r -> r instanceof RecipientIdentifier.Group)
611 .map(RecipientIdentifier.Group.class::cast)
612 .map(RecipientIdentifier.Group::groupId)
613 .collect(Collectors.toList());
614 for (final var groupId : groupRecipients) {
615 timestamp = groupHandler.apply(groupId.serialize());
616 }
617 return new SendMessageResults(timestamp, Map.of());
618 }
619
620 private String emptyIfNull(final String string) {
621 return string == null ? "" : string;
622 }
623
624 private <T extends DBusInterface> T getRemoteObject(final DBusPath devicePath, final Class<T> type) {
625 try {
626 return connection.getRemoteObject(DbusConfig.getBusname(), devicePath.getPath(), type);
627 } catch (DBusException e) {
628 throw new AssertionError(e);
629 }
630 }
631
632 private void installMessageHandlers() {
633 try {
634 this.dbusMsgHandler = messageReceived -> {
635 final var extras = messageReceived.getExtras();
636 final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
637 messageReceived.getSender())),
638 0,
639 messageReceived.getTimestamp(),
640 0,
641 0,
642 false,
643 Optional.empty(),
644 Optional.empty(),
645 Optional.of(new MessageEnvelope.Data(messageReceived.getTimestamp(),
646 messageReceived.getGroupId().length > 0
647 ? Optional.of(new MessageEnvelope.Data.GroupContext(GroupId.unknownVersion(
648 messageReceived.getGroupId()), false, 0))
649 : Optional.empty(),
650 Optional.empty(),
651 Optional.of(messageReceived.getMessage()),
652 0,
653 false,
654 false,
655 false,
656 false,
657 Optional.empty(),
658 Optional.empty(),
659 getAttachments(extras),
660 Optional.empty(),
661 Optional.empty(),
662 List.of(),
663 List.of(),
664 List.of())),
665 Optional.empty(),
666 Optional.empty());
667 synchronized (messageHandlers) {
668 for (final var messageHandler : messageHandlers) {
669 messageHandler.handleMessage(envelope, null);
670 }
671 }
672 };
673 connection.addSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
674
675 this.dbusRcptHandler = receiptReceived -> {
676 final var type = switch (receiptReceived.getReceiptType()) {
677 case "read" -> MessageEnvelope.Receipt.Type.READ;
678 case "viewed" -> MessageEnvelope.Receipt.Type.VIEWED;
679 case "delivery" -> MessageEnvelope.Receipt.Type.DELIVERY;
680 default -> MessageEnvelope.Receipt.Type.UNKNOWN;
681 };
682 final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
683 receiptReceived.getSender())),
684 0,
685 receiptReceived.getTimestamp(),
686 0,
687 0,
688 false,
689 Optional.of(new MessageEnvelope.Receipt(receiptReceived.getTimestamp(),
690 type,
691 List.of(receiptReceived.getTimestamp()))),
692 Optional.empty(),
693 Optional.empty(),
694 Optional.empty(),
695 Optional.empty());
696 synchronized (messageHandlers) {
697 for (final var messageHandler : messageHandlers) {
698 messageHandler.handleMessage(envelope, null);
699 }
700 }
701 };
702 connection.addSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
703
704 this.dbusSyncHandler = syncReceived -> {
705 final var extras = syncReceived.getExtras();
706 final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
707 syncReceived.getSource())),
708 0,
709 syncReceived.getTimestamp(),
710 0,
711 0,
712 false,
713 Optional.empty(),
714 Optional.empty(),
715 Optional.empty(),
716 Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(),
717 syncReceived.getTimestamp(),
718 syncReceived.getDestination().isEmpty()
719 ? Optional.empty()
720 : Optional.of(new RecipientAddress(null, syncReceived.getDestination())),
721 Set.of(),
722 new MessageEnvelope.Data(syncReceived.getTimestamp(),
723 syncReceived.getGroupId().length > 0
724 ? Optional.of(new MessageEnvelope.Data.GroupContext(GroupId.unknownVersion(
725 syncReceived.getGroupId()), false, 0))
726 : Optional.empty(),
727 Optional.empty(),
728 Optional.of(syncReceived.getMessage()),
729 0,
730 false,
731 false,
732 false,
733 false,
734 Optional.empty(),
735 Optional.empty(),
736 getAttachments(extras),
737 Optional.empty(),
738 Optional.empty(),
739 List.of(),
740 List.of(),
741 List.of()))),
742 Optional.empty(),
743 List.of(),
744 List.of(),
745 Optional.empty(),
746 Optional.empty(),
747 Optional.empty(),
748 Optional.empty())),
749 Optional.empty());
750 synchronized (messageHandlers) {
751 for (final var messageHandler : messageHandlers) {
752 messageHandler.handleMessage(envelope, null);
753 }
754 }
755 };
756 connection.addSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
757 } catch (DBusException e) {
758 e.printStackTrace();
759 }
760 }
761
762 private void uninstallMessageHandlers() {
763 try {
764 connection.removeSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
765 connection.removeSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
766 connection.removeSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
767 } catch (DBusException e) {
768 e.printStackTrace();
769 }
770 }
771
772 private List<MessageEnvelope.Data.Attachment> getAttachments(final Map<String, Variant<?>> extras) {
773 if (!extras.containsKey("attachments")) {
774 return List.of();
775 }
776
777 final List<DBusMap<String, Variant<?>>> attachments = getValue(extras, "attachments");
778 return attachments.stream().map(a -> {
779 final String file = a.containsKey("file") ? getValue(a, "file") : null;
780 return new MessageEnvelope.Data.Attachment(a.containsKey("remoteId")
781 ? Optional.of(getValue(a, "remoteId"))
782 : Optional.empty(),
783 file != null ? Optional.of(new File(file)) : Optional.empty(),
784 Optional.empty(),
785 getValue(a, "contentType"),
786 Optional.empty(),
787 Optional.empty(),
788 Optional.empty(),
789 Optional.empty(),
790 Optional.empty(),
791 Optional.empty(),
792 Optional.empty(),
793 getValue(a, "isVoiceNote"),
794 getValue(a, "isGif"),
795 getValue(a, "isBorderless"));
796 }).collect(Collectors.toList());
797 }
798
799 @SuppressWarnings("unchecked")
800 private <T> T getValue(
801 final Map<String, Variant<?>> stringVariantMap, final String field
802 ) {
803 return (T) stringVariantMap.get(field).getValue();
804 }
805 }