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