1 package org
.asamk
.signal
.dbus
;
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
;
43 import java
.io
.IOException
;
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
;
51 import java
.util
.Optional
;
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 import java
.util
.stream
.Stream
;
61 * This class implements the Manager interface using the DBus Signal interface, where possible.
62 * It's used for the signal-cli dbus client mode (--dbus, --dbus-system)
64 public class DbusManagerImpl
implements Manager
{
66 private final Signal signal
;
67 private final DBusConnection connection
;
69 private final Set
<ReceiveMessageHandler
> weakHandlers
= new HashSet
<>();
70 private final Set
<ReceiveMessageHandler
> messageHandlers
= new HashSet
<>();
71 private final List
<Runnable
> closedListeners
= new ArrayList
<>();
72 private DBusSigHandler
<Signal
.MessageReceivedV2
> dbusMsgHandler
;
73 private DBusSigHandler
<Signal
.ReceiptReceivedV2
> dbusRcptHandler
;
74 private DBusSigHandler
<Signal
.SyncMessageReceivedV2
> dbusSyncHandler
;
76 public DbusManagerImpl(final Signal signal
, DBusConnection connection
) {
78 this.connection
= connection
;
82 public String
getSelfNumber() {
83 return signal
.getSelfNumber();
87 public void checkAccountState() throws IOException
{
88 throw new UnsupportedOperationException();
92 public Map
<String
, Pair
<String
, UUID
>> areUsersRegistered(final Set
<String
> numbers
) throws IOException
{
93 final var numbersList
= new ArrayList
<>(numbers
);
94 final var registered
= signal
.isRegistered(numbersList
);
96 final var result
= new HashMap
<String
, Pair
<String
, UUID
>>();
97 for (var i
= 0; i
< numbersList
.size(); i
++) {
98 result
.put(numbersList
.get(i
),
99 new Pair
<>(numbersList
.get(i
), registered
.get(i
) ? RecipientAddress
.UNKNOWN_UUID
: null));
105 public void updateAccountAttributes(final String deviceName
) throws IOException
{
106 if (deviceName
!= null) {
107 final var devicePath
= signal
.getThisDevice();
108 getRemoteObject(devicePath
, Signal
.Device
.class).Set("org.asamk.Signal.Device", "Name", deviceName
);
113 public Configuration
getConfiguration() {
114 throw new UnsupportedOperationException();
118 public void updateConfiguration(Configuration configuration
) throws IOException
{
119 throw new UnsupportedOperationException();
123 public void setProfile(
124 final String givenName
,
125 final String familyName
,
127 final String aboutEmoji
,
128 final Optional
<File
> avatar
129 ) throws IOException
{
130 signal
.updateProfile(emptyIfNull(givenName
),
131 emptyIfNull(familyName
),
133 emptyIfNull(aboutEmoji
),
134 avatar
== null ?
"" : avatar
.map(File
::getPath
).orElse(""),
135 avatar
!= null && !avatar
.isPresent());
139 public void unregister() throws IOException
{
140 throw new UnsupportedOperationException();
144 public void deleteAccount() throws IOException
{
145 throw new UnsupportedOperationException();
149 public void submitRateLimitRecaptchaChallenge(final String challenge
, final String captcha
) throws IOException
{
150 signal
.submitRateLimitChallenge(challenge
, captcha
);
154 public List
<Device
> getLinkedDevices() throws IOException
{
155 final var thisDevice
= signal
.getThisDevice();
156 return signal
.listDevices().stream().map(d
-> {
157 final var device
= getRemoteObject(d
.getObjectPath(),
158 Signal
.Device
.class).GetAll("org.asamk.Signal.Device");
159 return new Device((long) device
.get("Id").getValue(),
160 (String
) device
.get("Name").getValue(),
161 (long) device
.get("Created").getValue(),
162 (long) device
.get("LastSeen").getValue(),
163 thisDevice
.equals(d
.getObjectPath()));
164 }).collect(Collectors
.toList());
168 public void removeLinkedDevices(final long deviceId
) throws IOException
{
169 final var devicePath
= signal
.getDevice(deviceId
);
170 getRemoteObject(devicePath
, Signal
.Device
.class).removeDevice();
174 public void addDeviceLink(final URI linkUri
) throws IOException
, InvalidDeviceLinkException
{
175 signal
.addDevice(linkUri
.toString());
179 public void setRegistrationLockPin(final Optional
<String
> pin
) throws IOException
{
180 if (pin
.isPresent()) {
181 signal
.setPin(pin
.get());
188 public Profile
getRecipientProfile(final RecipientIdentifier
.Single recipient
) {
189 throw new UnsupportedOperationException();
193 public List
<Group
> getGroups() {
194 final var groups
= signal
.listGroups();
195 return groups
.stream().map(Signal
.StructGroup
::getObjectPath
).map(this::getGroup
).collect(Collectors
.toList());
199 public SendGroupMessageResults
quitGroup(
200 final GroupId groupId
, final Set
<RecipientIdentifier
.Single
> groupAdmins
201 ) throws GroupNotFoundException
, IOException
, NotAGroupMemberException
, LastGroupAdminException
{
202 if (groupAdmins
.size() > 0) {
203 throw new UnsupportedOperationException();
205 final var group
= getRemoteObject(signal
.getGroup(groupId
.serialize()), Signal
.Group
.class);
207 return new SendGroupMessageResults(0, List
.of());
211 public void deleteGroup(final GroupId groupId
) throws IOException
{
212 throw new UnsupportedOperationException();
216 public Pair
<GroupId
, SendGroupMessageResults
> createGroup(
217 final String name
, final Set
<RecipientIdentifier
.Single
> members
, final File avatarFile
218 ) throws IOException
, AttachmentInvalidException
{
219 final var newGroupId
= signal
.createGroup(emptyIfNull(name
),
220 members
.stream().map(RecipientIdentifier
.Single
::getIdentifier
).collect(Collectors
.toList()),
221 avatarFile
== null ?
"" : avatarFile
.getPath());
222 return new Pair
<>(GroupId
.unknownVersion(newGroupId
), new SendGroupMessageResults(0, List
.of()));
226 public SendGroupMessageResults
updateGroup(
227 final GroupId groupId
, final UpdateGroup updateGroup
228 ) throws IOException
, GroupNotFoundException
, AttachmentInvalidException
, NotAGroupMemberException
, GroupSendingNotAllowedException
{
229 final var group
= getRemoteObject(signal
.getGroup(groupId
.serialize()), Signal
.Group
.class);
230 if (updateGroup
.getName() != null) {
231 group
.Set("org.asamk.Signal.Group", "Name", updateGroup
.getName());
233 if (updateGroup
.getDescription() != null) {
234 group
.Set("org.asamk.Signal.Group", "Description", updateGroup
.getDescription());
236 if (updateGroup
.getAvatarFile() != null) {
237 group
.Set("org.asamk.Signal.Group",
239 updateGroup
.getAvatarFile() == null ?
"" : updateGroup
.getAvatarFile().getPath());
241 if (updateGroup
.getExpirationTimer() != null) {
242 group
.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup
.getExpirationTimer());
244 if (updateGroup
.getAddMemberPermission() != null) {
245 group
.Set("org.asamk.Signal.Group", "PermissionAddMember", updateGroup
.getAddMemberPermission().name());
247 if (updateGroup
.getEditDetailsPermission() != null) {
248 group
.Set("org.asamk.Signal.Group", "PermissionEditDetails", updateGroup
.getEditDetailsPermission().name());
250 if (updateGroup
.getIsAnnouncementGroup() != null) {
251 group
.Set("org.asamk.Signal.Group",
252 "PermissionSendMessage",
253 updateGroup
.getIsAnnouncementGroup()
254 ? GroupPermission
.ONLY_ADMINS
.name()
255 : GroupPermission
.EVERY_MEMBER
.name());
257 if (updateGroup
.getMembers() != null) {
258 group
.addMembers(updateGroup
.getMembers()
260 .map(RecipientIdentifier
.Single
::getIdentifier
)
261 .collect(Collectors
.toList()));
263 if (updateGroup
.getRemoveMembers() != null) {
264 group
.removeMembers(updateGroup
.getRemoveMembers()
266 .map(RecipientIdentifier
.Single
::getIdentifier
)
267 .collect(Collectors
.toList()));
269 if (updateGroup
.getAdmins() != null) {
270 group
.addAdmins(updateGroup
.getAdmins()
272 .map(RecipientIdentifier
.Single
::getIdentifier
)
273 .collect(Collectors
.toList()));
275 if (updateGroup
.getRemoveAdmins() != null) {
276 group
.removeAdmins(updateGroup
.getRemoveAdmins()
278 .map(RecipientIdentifier
.Single
::getIdentifier
)
279 .collect(Collectors
.toList()));
281 if (updateGroup
.isResetGroupLink()) {
284 if (updateGroup
.getGroupLinkState() != null) {
285 switch (updateGroup
.getGroupLinkState()) {
286 case DISABLED
-> group
.disableLink();
287 case ENABLED
-> group
.enableLink(false);
288 case ENABLED_WITH_APPROVAL
-> group
.enableLink(true);
291 return new SendGroupMessageResults(0, List
.of());
295 public Pair
<GroupId
, SendGroupMessageResults
> joinGroup(final GroupInviteLinkUrl inviteLinkUrl
) throws IOException
, InactiveGroupLinkException
{
296 final var newGroupId
= signal
.joinGroup(inviteLinkUrl
.getUrl());
297 return new Pair
<>(GroupId
.unknownVersion(newGroupId
), new SendGroupMessageResults(0, List
.of()));
301 public void sendTypingMessage(
302 final TypingAction action
, final Set
<RecipientIdentifier
> recipients
303 ) throws IOException
, UntrustedIdentityException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
304 for (final var recipient
: recipients
) {
305 if (recipient
instanceof RecipientIdentifier
.Single
) {
306 signal
.sendTyping(((RecipientIdentifier
.Single
) recipient
).getIdentifier(),
307 action
== TypingAction
.STOP
);
308 } else if (recipient
instanceof RecipientIdentifier
.Group
) {
309 throw new UnsupportedOperationException();
315 public void sendReadReceipt(
316 final RecipientIdentifier
.Single sender
, final List
<Long
> messageIds
317 ) throws IOException
, UntrustedIdentityException
{
318 signal
.sendReadReceipt(sender
.getIdentifier(), messageIds
);
322 public void sendViewedReceipt(
323 final RecipientIdentifier
.Single sender
, final List
<Long
> messageIds
324 ) throws IOException
, UntrustedIdentityException
{
325 signal
.sendViewedReceipt(sender
.getIdentifier(), messageIds
);
329 public SendMessageResults
sendMessage(
330 final Message message
, final Set
<RecipientIdentifier
> recipients
331 ) throws IOException
, AttachmentInvalidException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
332 return handleMessage(recipients
,
333 numbers
-> signal
.sendMessage(message
.messageText(), message
.attachments(), numbers
),
334 () -> signal
.sendNoteToSelfMessage(message
.messageText(), message
.attachments()),
335 groupId
-> signal
.sendGroupMessage(message
.messageText(), message
.attachments(), groupId
));
339 public SendMessageResults
sendRemoteDeleteMessage(
340 final long targetSentTimestamp
, final Set
<RecipientIdentifier
> recipients
341 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
342 return handleMessage(recipients
,
343 numbers
-> signal
.sendRemoteDeleteMessage(targetSentTimestamp
, numbers
),
344 () -> signal
.sendRemoteDeleteMessage(targetSentTimestamp
, signal
.getSelfNumber()),
345 groupId
-> signal
.sendGroupRemoteDeleteMessage(targetSentTimestamp
, groupId
));
349 public SendMessageResults
sendMessageReaction(
351 final boolean remove
,
352 final RecipientIdentifier
.Single targetAuthor
,
353 final long targetSentTimestamp
,
354 final Set
<RecipientIdentifier
> recipients
355 ) throws IOException
, NotAGroupMemberException
, GroupNotFoundException
, GroupSendingNotAllowedException
{
356 return handleMessage(recipients
,
357 numbers
-> signal
.sendMessageReaction(emoji
,
359 targetAuthor
.getIdentifier(),
362 () -> signal
.sendMessageReaction(emoji
,
364 targetAuthor
.getIdentifier(),
366 signal
.getSelfNumber()),
367 groupId
-> signal
.sendGroupMessageReaction(emoji
,
369 targetAuthor
.getIdentifier(),
375 public SendMessageResults
sendEndSessionMessage(final Set
<RecipientIdentifier
.Single
> recipients
) throws IOException
{
376 signal
.sendEndSessionMessage(recipients
.stream()
377 .map(RecipientIdentifier
.Single
::getIdentifier
)
378 .collect(Collectors
.toList()));
379 return new SendMessageResults(0, Map
.of());
383 public void setContactName(
384 final RecipientIdentifier
.Single recipient
, final String name
385 ) throws NotMasterDeviceException
{
386 signal
.setContactName(recipient
.getIdentifier(), name
);
390 public void setContactBlocked(
391 final RecipientIdentifier
.Single recipient
, final boolean blocked
392 ) throws NotMasterDeviceException
, IOException
{
393 signal
.setContactBlocked(recipient
.getIdentifier(), blocked
);
397 public void setGroupBlocked(
398 final GroupId groupId
, final boolean blocked
399 ) throws GroupNotFoundException
, IOException
{
400 setGroupProperty(groupId
, "IsBlocked", blocked
);
403 private void setGroupProperty(final GroupId groupId
, final String propertyName
, final boolean blocked
) {
404 final var group
= getRemoteObject(signal
.getGroup(groupId
.serialize()), Signal
.Group
.class);
405 group
.Set("org.asamk.Signal.Group", propertyName
, blocked
);
409 public void setExpirationTimer(
410 final RecipientIdentifier
.Single recipient
, final int messageExpirationTimer
411 ) throws IOException
{
412 signal
.setExpirationTimer(recipient
.getIdentifier(), messageExpirationTimer
);
416 public URI
uploadStickerPack(final File path
) throws IOException
, StickerPackInvalidException
{
418 return new URI(signal
.uploadStickerPack(path
.getPath()));
419 } catch (URISyntaxException e
) {
420 throw new AssertionError(e
);
425 public void requestAllSyncData() throws IOException
{
426 signal
.sendSyncRequest();
430 public void addReceiveHandler(final ReceiveMessageHandler handler
, final boolean isWeakListener
) {
431 synchronized (messageHandlers
) {
432 if (isWeakListener
) {
433 weakHandlers
.add(handler
);
435 if (messageHandlers
.size() == 0) {
436 installMessageHandlers();
438 messageHandlers
.add(handler
);
444 public void removeReceiveHandler(final ReceiveMessageHandler handler
) {
445 synchronized (messageHandlers
) {
446 weakHandlers
.remove(handler
);
447 messageHandlers
.remove(handler
);
448 if (messageHandlers
.size() == 0) {
449 uninstallMessageHandlers();
455 public boolean isReceiving() {
456 synchronized (messageHandlers
) {
457 return messageHandlers
.size() > 0;
462 public void receiveMessages(final ReceiveMessageHandler handler
) throws IOException
{
463 addReceiveHandler(handler
);
465 synchronized (this) {
468 } catch (InterruptedException ignored
) {
470 removeReceiveHandler(handler
);
474 public void receiveMessages(
475 final long timeout
, final TimeUnit unit
, final ReceiveMessageHandler handler
476 ) throws IOException
{
477 addReceiveHandler(handler
);
479 Thread
.sleep(unit
.toMillis(timeout
));
480 } catch (InterruptedException ignored
) {
482 removeReceiveHandler(handler
);
486 public void setIgnoreAttachments(final boolean ignoreAttachments
) {
490 public boolean hasCaughtUpWithOldMessages() {
495 public boolean isContactBlocked(final RecipientIdentifier
.Single recipient
) {
496 return signal
.isContactBlocked(recipient
.getIdentifier());
500 public void sendContacts() throws IOException
{
501 signal
.sendContacts();
505 public List
<Pair
<RecipientAddress
, Contact
>> getContacts() {
506 throw new UnsupportedOperationException();
510 public String
getContactOrProfileName(final RecipientIdentifier
.Single recipient
) {
511 return signal
.getContactName(recipient
.getIdentifier());
515 public Group
getGroup(final GroupId groupId
) {
516 final var groupPath
= signal
.getGroup(groupId
.serialize());
517 return getGroup(groupPath
);
520 @SuppressWarnings("unchecked")
521 private Group
getGroup(final DBusPath groupPath
) {
522 final var group
= getRemoteObject(groupPath
, Signal
.Group
.class).GetAll("org.asamk.Signal.Group");
523 final var id
= (byte[]) group
.get("Id").getValue();
525 return new Group(GroupId
.unknownVersion(id
),
526 (String
) group
.get("Name").getValue(),
527 (String
) group
.get("Description").getValue(),
528 GroupInviteLinkUrl
.fromUri((String
) group
.get("GroupInviteLink").getValue()),
529 ((List
<String
>) group
.get("Members").getValue()).stream()
530 .map(m
-> new RecipientAddress(null, m
))
531 .collect(Collectors
.toSet()),
532 ((List
<String
>) group
.get("PendingMembers").getValue()).stream()
533 .map(m
-> new RecipientAddress(null, m
))
534 .collect(Collectors
.toSet()),
535 ((List
<String
>) group
.get("RequestingMembers").getValue()).stream()
536 .map(m
-> new RecipientAddress(null, m
))
537 .collect(Collectors
.toSet()),
538 ((List
<String
>) group
.get("Admins").getValue()).stream()
539 .map(m
-> new RecipientAddress(null, m
))
540 .collect(Collectors
.toSet()),
541 (boolean) group
.get("IsBlocked").getValue(),
542 (int) group
.get("MessageExpirationTimer").getValue(),
543 GroupPermission
.valueOf((String
) group
.get("PermissionAddMember").getValue()),
544 GroupPermission
.valueOf((String
) group
.get("PermissionEditDetails").getValue()),
545 GroupPermission
.valueOf((String
) group
.get("PermissionSendMessage").getValue()),
546 (boolean) group
.get("IsMember").getValue(),
547 (boolean) group
.get("IsAdmin").getValue());
548 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
549 throw new AssertionError(e
);
554 public List
<Identity
> getIdentities() {
555 throw new UnsupportedOperationException();
559 public List
<Identity
> getIdentities(final RecipientIdentifier
.Single recipient
) {
560 throw new UnsupportedOperationException();
564 public boolean trustIdentityVerified(final RecipientIdentifier
.Single recipient
, final byte[] fingerprint
) {
565 throw new UnsupportedOperationException();
569 public boolean trustIdentityVerifiedSafetyNumber(
570 final RecipientIdentifier
.Single recipient
, final String safetyNumber
572 throw new UnsupportedOperationException();
576 public boolean trustIdentityVerifiedSafetyNumber(
577 final RecipientIdentifier
.Single recipient
, final byte[] safetyNumber
579 throw new UnsupportedOperationException();
583 public boolean trustIdentityAllKeys(final RecipientIdentifier
.Single recipient
) {
584 throw new UnsupportedOperationException();
588 public void addClosedListener(final Runnable listener
) {
589 synchronized (closedListeners
) {
590 closedListeners
.add(listener
);
595 public void close() throws IOException
{
596 synchronized (this) {
599 synchronized (messageHandlers
) {
600 if (messageHandlers
.size() > 0) {
601 uninstallMessageHandlers();
603 weakHandlers
.clear();
604 messageHandlers
.clear();
606 synchronized (closedListeners
) {
607 closedListeners
.forEach(Runnable
::run
);
608 closedListeners
.clear();
612 private SendMessageResults
handleMessage(
613 Set
<RecipientIdentifier
> recipients
,
614 Function
<List
<String
>, Long
> recipientsHandler
,
615 Supplier
<Long
> noteToSelfHandler
,
616 Function
<byte[], Long
> groupHandler
619 final var singleRecipients
= recipients
.stream()
620 .filter(r
-> r
instanceof RecipientIdentifier
.Single
)
621 .map(RecipientIdentifier
.Single
.class::cast
)
622 .map(RecipientIdentifier
.Single
::getIdentifier
)
623 .collect(Collectors
.toList());
624 if (singleRecipients
.size() > 0) {
625 timestamp
= recipientsHandler
.apply(singleRecipients
);
628 if (recipients
.contains(RecipientIdentifier
.NoteToSelf
.INSTANCE
)) {
629 timestamp
= noteToSelfHandler
.get();
631 final var groupRecipients
= recipients
.stream()
632 .filter(r
-> r
instanceof RecipientIdentifier
.Group
)
633 .map(RecipientIdentifier
.Group
.class::cast
)
634 .map(RecipientIdentifier
.Group
::groupId
)
635 .collect(Collectors
.toList());
636 for (final var groupId
: groupRecipients
) {
637 timestamp
= groupHandler
.apply(groupId
.serialize());
639 return new SendMessageResults(timestamp
, Map
.of());
642 private String
emptyIfNull(final String string
) {
643 return string
== null ?
"" : string
;
646 private <T
extends DBusInterface
> T
getRemoteObject(final DBusPath devicePath
, final Class
<T
> type
) {
648 return connection
.getRemoteObject(DbusConfig
.getBusname(), devicePath
.getPath(), type
);
649 } catch (DBusException e
) {
650 throw new AssertionError(e
);
654 private void installMessageHandlers() {
656 this.dbusMsgHandler
= messageReceived
-> {
657 final var extras
= messageReceived
.getExtras();
658 final var envelope
= new MessageEnvelope(Optional
.of(new RecipientAddress(null,
659 messageReceived
.getSender())),
661 messageReceived
.getTimestamp(),
667 Optional
.of(new MessageEnvelope
.Data(messageReceived
.getTimestamp(),
668 messageReceived
.getGroupId().length
> 0
669 ? Optional
.of(new MessageEnvelope
.Data
.GroupContext(GroupId
.unknownVersion(
670 messageReceived
.getGroupId()), false, 0))
673 Optional
.of(messageReceived
.getMessage()),
681 getAttachments(extras
),
689 notifyMessageHandlers(envelope
);
691 connection
.addSigHandler(Signal
.MessageReceivedV2
.class, signal
, this.dbusMsgHandler
);
693 this.dbusRcptHandler
= receiptReceived
-> {
694 final var type
= switch (receiptReceived
.getReceiptType()) {
695 case "read" -> MessageEnvelope
.Receipt
.Type
.READ
;
696 case "viewed" -> MessageEnvelope
.Receipt
.Type
.VIEWED
;
697 case "delivery" -> MessageEnvelope
.Receipt
.Type
.DELIVERY
;
698 default -> MessageEnvelope
.Receipt
.Type
.UNKNOWN
;
700 final var envelope
= new MessageEnvelope(Optional
.of(new RecipientAddress(null,
701 receiptReceived
.getSender())),
703 receiptReceived
.getTimestamp(),
707 Optional
.of(new MessageEnvelope
.Receipt(receiptReceived
.getTimestamp(),
709 List
.of(receiptReceived
.getTimestamp()))),
714 notifyMessageHandlers(envelope
);
716 connection
.addSigHandler(Signal
.ReceiptReceivedV2
.class, signal
, this.dbusRcptHandler
);
718 this.dbusSyncHandler
= syncReceived
-> {
719 final var extras
= syncReceived
.getExtras();
720 final var envelope
= new MessageEnvelope(Optional
.of(new RecipientAddress(null,
721 syncReceived
.getSource())),
723 syncReceived
.getTimestamp(),
730 Optional
.of(new MessageEnvelope
.Sync(Optional
.of(new MessageEnvelope
.Sync
.Sent(syncReceived
.getTimestamp(),
731 syncReceived
.getTimestamp(),
732 syncReceived
.getDestination().isEmpty()
734 : Optional
.of(new RecipientAddress(null, syncReceived
.getDestination())),
736 new MessageEnvelope
.Data(syncReceived
.getTimestamp(),
737 syncReceived
.getGroupId().length
> 0
738 ? Optional
.of(new MessageEnvelope
.Data
.GroupContext(GroupId
.unknownVersion(
739 syncReceived
.getGroupId()), false, 0))
742 Optional
.of(syncReceived
.getMessage()),
750 getAttachments(extras
),
764 notifyMessageHandlers(envelope
);
766 connection
.addSigHandler(Signal
.SyncMessageReceivedV2
.class, signal
, this.dbusSyncHandler
);
767 } catch (DBusException e
) {
770 signal
.subscribeReceive();
773 private void notifyMessageHandlers(final MessageEnvelope envelope
) {
774 synchronized (messageHandlers
) {
775 Stream
.concat(messageHandlers
.stream(), weakHandlers
.stream()).forEach(h
-> {
776 h
.handleMessage(envelope
, null);
781 private void uninstallMessageHandlers() {
783 signal
.unsubscribeReceive();
784 connection
.removeSigHandler(Signal
.MessageReceivedV2
.class, signal
, this.dbusMsgHandler
);
785 connection
.removeSigHandler(Signal
.ReceiptReceivedV2
.class, signal
, this.dbusRcptHandler
);
786 connection
.removeSigHandler(Signal
.SyncMessageReceivedV2
.class, signal
, this.dbusSyncHandler
);
787 } catch (DBusException e
) {
792 private List
<MessageEnvelope
.Data
.Attachment
> getAttachments(final Map
<String
, Variant
<?
>> extras
) {
793 if (!extras
.containsKey("attachments")) {
797 final List
<DBusMap
<String
, Variant
<?
>>> attachments
= getValue(extras
, "attachments");
798 return attachments
.stream().map(a
-> {
799 final String file
= a
.containsKey("file") ?
getValue(a
, "file") : null;
800 return new MessageEnvelope
.Data
.Attachment(a
.containsKey("remoteId")
801 ? Optional
.of(getValue(a
, "remoteId"))
803 file
!= null ? Optional
.of(new File(file
)) : Optional
.empty(),
805 getValue(a
, "contentType"),
813 getValue(a
, "isVoiceNote"),
814 getValue(a
, "isGif"),
815 getValue(a
, "isBorderless"));
816 }).collect(Collectors
.toList());
819 @SuppressWarnings("unchecked")
820 private <T
> T
getValue(
821 final Map
<String
, Variant
<?
>> stringVariantMap
, final String field
823 return (T
) stringVariantMap
.get(field
).getValue();