]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java
Implement socket/tcp for daemon command
[signal-cli] / src / main / java / org / asamk / signal / dbus / DbusManagerImpl.java
index 59422e6922354ad01b454e2976dcbbb00ea6f11c..cc346f7af7ff7ab4b8fd434dfb7c1297b0c0eeb4 100644 (file)
@@ -7,10 +7,15 @@ import org.asamk.signal.manager.Manager;
 import org.asamk.signal.manager.NotMasterDeviceException;
 import org.asamk.signal.manager.StickerPackInvalidException;
 import org.asamk.signal.manager.UntrustedIdentityException;
+import org.asamk.signal.manager.api.Configuration;
 import org.asamk.signal.manager.api.Device;
 import org.asamk.signal.manager.api.Group;
 import org.asamk.signal.manager.api.Identity;
+import org.asamk.signal.manager.api.InactiveGroupLinkException;
+import org.asamk.signal.manager.api.InvalidDeviceLinkException;
 import org.asamk.signal.manager.api.Message;
+import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.Pair;
 import org.asamk.signal.manager.api.RecipientIdentifier;
 import org.asamk.signal.manager.api.SendGroupMessageResults;
 import org.asamk.signal.manager.api.SendMessageResults;
@@ -26,19 +31,13 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException;
 import org.asamk.signal.manager.storage.recipients.Contact;
 import org.asamk.signal.manager.storage.recipients.Profile;
 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.freedesktop.dbus.DBusMap;
 import org.freedesktop.dbus.DBusPath;
 import org.freedesktop.dbus.connections.impl.DBusConnection;
 import org.freedesktop.dbus.exceptions.DBusException;
 import org.freedesktop.dbus.interfaces.DBusInterface;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.util.Pair;
-import org.whispersystems.libsignal.util.guava.Optional;
-import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
-import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
-import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
-import org.whispersystems.signalservice.api.util.UuidUtil;
-import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
+import org.freedesktop.dbus.interfaces.DBusSigHandler;
+import org.freedesktop.dbus.types.Variant;
 
 import java.io.File;
 import java.io.IOException;
@@ -46,14 +45,17 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * This class implements the Manager interface using the DBus Signal interface, where possible.
@@ -64,6 +66,12 @@ public class DbusManagerImpl implements Manager {
     private final Signal signal;
     private final DBusConnection connection;
 
+    private final Set<ReceiveMessageHandler> weakHandlers = new HashSet<>();
+    private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
+    private DBusSigHandler<Signal.MessageReceivedV2> dbusMsgHandler;
+    private DBusSigHandler<Signal.ReceiptReceivedV2> dbusRcptHandler;
+    private DBusSigHandler<Signal.SyncMessageReceivedV2> dbusSyncHandler;
+
     public DbusManagerImpl(final Signal signal, DBusConnection connection) {
         this.signal = signal;
         this.connection = connection;
@@ -87,7 +95,7 @@ public class DbusManagerImpl implements Manager {
         final var result = new HashMap<String, Pair<String, UUID>>();
         for (var i = 0; i < numbersList.size(); i++) {
             result.put(numbersList.get(i),
-                    new Pair<>(numbersList.get(i), registered.get(i) ? UuidUtil.UNKNOWN_UUID : null));
+                    new Pair<>(numbersList.get(i), registered.get(i) ? RecipientAddress.UNKNOWN_UUID : null));
         }
         return result;
     }
@@ -101,12 +109,12 @@ public class DbusManagerImpl implements Manager {
     }
 
     @Override
-    public void updateConfiguration(
-            final Boolean readReceipts,
-            final Boolean unidentifiedDeliveryIndicators,
-            final Boolean typingIndicators,
-            final Boolean linkPreviews
-    ) throws IOException {
+    public Configuration getConfiguration() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void updateConfiguration(Configuration configuration) throws IOException {
         throw new UnsupportedOperationException();
     }
 
@@ -122,7 +130,7 @@ public class DbusManagerImpl implements Manager {
                 emptyIfNull(familyName),
                 emptyIfNull(about),
                 emptyIfNull(aboutEmoji),
-                avatar == null ? "" : avatar.transform(File::getPath).or(""),
+                avatar == null ? "" : avatar.map(File::getPath).orElse(""),
                 avatar != null && !avatar.isPresent());
     }
 
@@ -138,7 +146,7 @@ public class DbusManagerImpl implements Manager {
 
     @Override
     public void submitRateLimitRecaptchaChallenge(final String challenge, final String captcha) throws IOException {
-        throw new UnsupportedOperationException();
+        signal.submitRateLimitChallenge(challenge, captcha);
     }
 
     @Override
@@ -162,12 +170,12 @@ public class DbusManagerImpl implements Manager {
     }
 
     @Override
-    public void addDeviceLink(final URI linkUri) throws IOException, InvalidKeyException {
+    public void addDeviceLink(final URI linkUri) throws IOException, InvalidDeviceLinkException {
         signal.addDevice(linkUri.toString());
     }
 
     @Override
-    public void setRegistrationLockPin(final Optional<String> pin) throws IOException, UnauthenticatedResponseException {
+    public void setRegistrationLockPin(final Optional<String> pin) throws IOException {
         if (pin.isPresent()) {
             signal.setPin(pin.get());
         } else {
@@ -176,7 +184,7 @@ public class DbusManagerImpl implements Manager {
     }
 
     @Override
-    public Profile getRecipientProfile(final RecipientIdentifier.Single recipient) throws UnregisteredUserException {
+    public Profile getRecipientProfile(final RecipientIdentifier.Single recipient) {
         throw new UnsupportedOperationException();
     }
 
@@ -274,22 +282,16 @@ public class DbusManagerImpl implements Manager {
         }
         if (updateGroup.getGroupLinkState() != null) {
             switch (updateGroup.getGroupLinkState()) {
-                case DISABLED:
-                    group.disableLink();
-                    break;
-                case ENABLED:
-                    group.enableLink(false);
-                    break;
-                case ENABLED_WITH_APPROVAL:
-                    group.enableLink(true);
-                    break;
+                case DISABLED -> group.disableLink();
+                case ENABLED -> group.enableLink(false);
+                case ENABLED_WITH_APPROVAL -> group.enableLink(true);
             }
         }
         return new SendGroupMessageResults(0, List.of());
     }
 
     @Override
-    public Pair<GroupId, SendGroupMessageResults> joinGroup(final GroupInviteLinkUrl inviteLinkUrl) throws IOException, GroupLinkNotActiveException {
+    public Pair<GroupId, SendGroupMessageResults> joinGroup(final GroupInviteLinkUrl inviteLinkUrl) throws IOException, InactiveGroupLinkException {
         final var newGroupId = signal.joinGroup(inviteLinkUrl.getUrl());
         return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
     }
@@ -319,7 +321,7 @@ public class DbusManagerImpl implements Manager {
     public void sendViewedReceipt(
             final RecipientIdentifier.Single sender, final List<Long> messageIds
     ) throws IOException, UntrustedIdentityException {
-        throw new UnsupportedOperationException();
+        signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
     }
 
     @Override
@@ -327,9 +329,9 @@ public class DbusManagerImpl implements Manager {
             final Message message, final Set<RecipientIdentifier> recipients
     ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
         return handleMessage(recipients,
-                numbers -> signal.sendMessage(message.getMessageText(), message.getAttachments(), numbers),
-                () -> signal.sendNoteToSelfMessage(message.getMessageText(), message.getAttachments()),
-                groupId -> signal.sendGroupMessage(message.getMessageText(), message.getAttachments(), groupId));
+                numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers),
+                () -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()),
+                groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
     }
 
     @Override
@@ -379,7 +381,7 @@ public class DbusManagerImpl implements Manager {
     @Override
     public void setContactName(
             final RecipientIdentifier.Single recipient, final String name
-    ) throws NotMasterDeviceException, UnregisteredUserException {
+    ) throws NotMasterDeviceException {
         signal.setContactName(recipient.getIdentifier(), name);
     }
 
@@ -423,30 +425,74 @@ public class DbusManagerImpl implements Manager {
         signal.sendSyncRequest();
     }
 
+    @Override
+    public void addReceiveHandler(final ReceiveMessageHandler handler, final boolean isWeakListener) {
+        synchronized (messageHandlers) {
+            if (isWeakListener) {
+                weakHandlers.add(handler);
+            } else {
+                if (messageHandlers.size() == 0) {
+                    installMessageHandlers();
+                }
+                messageHandlers.add(handler);
+            }
+        }
+    }
+
+    @Override
+    public void removeReceiveHandler(final ReceiveMessageHandler handler) {
+        synchronized (messageHandlers) {
+            weakHandlers.remove(handler);
+            messageHandlers.remove(handler);
+            if (messageHandlers.size() == 0) {
+                uninstallMessageHandlers();
+            }
+        }
+    }
+
+    @Override
+    public boolean isReceiving() {
+        synchronized (messageHandlers) {
+            return messageHandlers.size() > 0;
+        }
+    }
+
+    @Override
+    public void receiveMessages(final ReceiveMessageHandler handler) throws IOException {
+        addReceiveHandler(handler);
+        try {
+            synchronized (this) {
+                this.wait();
+            }
+        } catch (InterruptedException ignored) {
+        }
+        removeReceiveHandler(handler);
+    }
+
     @Override
     public void receiveMessages(
-            final long timeout,
-            final TimeUnit unit,
-            final boolean returnOnTimeout,
-            final boolean ignoreAttachments,
-            final ReceiveMessageHandler handler
+            final long timeout, final TimeUnit unit, final ReceiveMessageHandler handler
     ) throws IOException {
-        throw new UnsupportedOperationException();
+        addReceiveHandler(handler);
+        try {
+            Thread.sleep(unit.toMillis(timeout));
+        } catch (InterruptedException ignored) {
+        }
+        removeReceiveHandler(handler);
     }
 
     @Override
-    public boolean hasCaughtUpWithOldMessages() {
-        throw new UnsupportedOperationException();
+    public void setIgnoreAttachments(final boolean ignoreAttachments) {
     }
 
     @Override
-    public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
-        return signal.isContactBlocked(recipient.getIdentifier());
+    public boolean hasCaughtUpWithOldMessages() {
+        return true;
     }
 
     @Override
-    public File getAttachmentFile(final SignalServiceAttachmentRemoteId attachmentId) {
-        throw new UnsupportedOperationException();
+    public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
+        return signal.isContactBlocked(recipient.getIdentifier());
     }
 
     @Override
@@ -537,13 +583,18 @@ public class DbusManagerImpl implements Manager {
         throw new UnsupportedOperationException();
     }
 
-    @Override
-    public SignalServiceAddress resolveSignalServiceAddress(final SignalServiceAddress address) {
-        return address;
-    }
-
     @Override
     public void close() throws IOException {
+        synchronized (this) {
+            this.notify();
+        }
+        synchronized (messageHandlers) {
+            if (messageHandlers.size() > 0) {
+                uninstallMessageHandlers();
+            }
+            weakHandlers.clear();
+            messageHandlers.clear();
+        }
     }
 
     private SendMessageResults handleMessage(
@@ -568,7 +619,7 @@ public class DbusManagerImpl implements Manager {
         final var groupRecipients = recipients.stream()
                 .filter(r -> r instanceof RecipientIdentifier.Group)
                 .map(RecipientIdentifier.Group.class::cast)
-                .map(g -> g.groupId)
+                .map(RecipientIdentifier.Group::groupId)
                 .collect(Collectors.toList());
         for (final var groupId : groupRecipients) {
             timestamp = groupHandler.apply(groupId.serialize());
@@ -587,4 +638,176 @@ public class DbusManagerImpl implements Manager {
             throw new AssertionError(e);
         }
     }
+
+    private void installMessageHandlers() {
+        try {
+            this.dbusMsgHandler = messageReceived -> {
+                final var extras = messageReceived.getExtras();
+                final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
+                        messageReceived.getSender())),
+                        0,
+                        messageReceived.getTimestamp(),
+                        0,
+                        0,
+                        false,
+                        Optional.empty(),
+                        Optional.empty(),
+                        Optional.of(new MessageEnvelope.Data(messageReceived.getTimestamp(),
+                                messageReceived.getGroupId().length > 0
+                                        ? Optional.of(new MessageEnvelope.Data.GroupContext(GroupId.unknownVersion(
+                                        messageReceived.getGroupId()), false, 0))
+                                        : Optional.empty(),
+                                Optional.empty(),
+                                Optional.of(messageReceived.getMessage()),
+                                0,
+                                false,
+                                false,
+                                false,
+                                false,
+                                Optional.empty(),
+                                Optional.empty(),
+                                getAttachments(extras),
+                                Optional.empty(),
+                                Optional.empty(),
+                                List.of(),
+                                List.of(),
+                                List.of())),
+                        Optional.empty(),
+                        Optional.empty());
+                notifyMessageHandlers(envelope);
+            };
+            connection.addSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
+
+            this.dbusRcptHandler = receiptReceived -> {
+                final var type = switch (receiptReceived.getReceiptType()) {
+                    case "read" -> MessageEnvelope.Receipt.Type.READ;
+                    case "viewed" -> MessageEnvelope.Receipt.Type.VIEWED;
+                    case "delivery" -> MessageEnvelope.Receipt.Type.DELIVERY;
+                    default -> MessageEnvelope.Receipt.Type.UNKNOWN;
+                };
+                final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
+                        receiptReceived.getSender())),
+                        0,
+                        receiptReceived.getTimestamp(),
+                        0,
+                        0,
+                        false,
+                        Optional.of(new MessageEnvelope.Receipt(receiptReceived.getTimestamp(),
+                                type,
+                                List.of(receiptReceived.getTimestamp()))),
+                        Optional.empty(),
+                        Optional.empty(),
+                        Optional.empty(),
+                        Optional.empty());
+                notifyMessageHandlers(envelope);
+            };
+            connection.addSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
+
+            this.dbusSyncHandler = syncReceived -> {
+                final var extras = syncReceived.getExtras();
+                final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null,
+                        syncReceived.getSource())),
+                        0,
+                        syncReceived.getTimestamp(),
+                        0,
+                        0,
+                        false,
+                        Optional.empty(),
+                        Optional.empty(),
+                        Optional.empty(),
+                        Optional.of(new MessageEnvelope.Sync(Optional.of(new MessageEnvelope.Sync.Sent(syncReceived.getTimestamp(),
+                                syncReceived.getTimestamp(),
+                                syncReceived.getDestination().isEmpty()
+                                        ? Optional.empty()
+                                        : Optional.of(new RecipientAddress(null, syncReceived.getDestination())),
+                                Set.of(),
+                                new MessageEnvelope.Data(syncReceived.getTimestamp(),
+                                        syncReceived.getGroupId().length > 0
+                                                ? Optional.of(new MessageEnvelope.Data.GroupContext(GroupId.unknownVersion(
+                                                syncReceived.getGroupId()), false, 0))
+                                                : Optional.empty(),
+                                        Optional.empty(),
+                                        Optional.of(syncReceived.getMessage()),
+                                        0,
+                                        false,
+                                        false,
+                                        false,
+                                        false,
+                                        Optional.empty(),
+                                        Optional.empty(),
+                                        getAttachments(extras),
+                                        Optional.empty(),
+                                        Optional.empty(),
+                                        List.of(),
+                                        List.of(),
+                                        List.of()))),
+                                Optional.empty(),
+                                List.of(),
+                                List.of(),
+                                Optional.empty(),
+                                Optional.empty(),
+                                Optional.empty(),
+                                Optional.empty())),
+                        Optional.empty());
+                notifyMessageHandlers(envelope);
+            };
+            connection.addSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
+        } catch (DBusException e) {
+            e.printStackTrace();
+        }
+        signal.subscribeReceive();
+    }
+
+    private void notifyMessageHandlers(final MessageEnvelope envelope) {
+        synchronized (messageHandlers) {
+            Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> {
+                h.handleMessage(envelope, null);
+            });
+        }
+    }
+
+    private void uninstallMessageHandlers() {
+        try {
+            signal.unsubscribeReceive();
+            connection.removeSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler);
+            connection.removeSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
+            connection.removeSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
+        } catch (DBusException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private List<MessageEnvelope.Data.Attachment> getAttachments(final Map<String, Variant<?>> extras) {
+        if (!extras.containsKey("attachments")) {
+            return List.of();
+        }
+
+        final List<DBusMap<String, Variant<?>>> attachments = getValue(extras, "attachments");
+        return attachments.stream().map(a -> {
+            final String file = a.containsKey("file") ? getValue(a, "file") : null;
+            return new MessageEnvelope.Data.Attachment(a.containsKey("remoteId")
+                    ? Optional.of(getValue(a, "remoteId"))
+                    : Optional.empty(),
+                    file != null ? Optional.of(new File(file)) : Optional.empty(),
+                    Optional.empty(),
+                    getValue(a, "contentType"),
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    getValue(a, "isVoiceNote"),
+                    getValue(a, "isGif"),
+                    getValue(a, "isBorderless"));
+        }).collect(Collectors.toList());
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T getValue(
+            final Map<String, Variant<?>> stringVariantMap, final String field
+    ) {
+        return (T) stringVariantMap.get(field).getValue();
+    }
 }