package org.asamk.signal.dbus;
import org.asamk.Signal;
-import org.asamk.signal.DbusConfig;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.AlreadyReceivingException;
import org.asamk.signal.manager.api.AttachmentInvalidException;
+import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.IdentityVerificationCode;
import org.asamk.signal.manager.api.InactiveGroupLinkException;
+import org.asamk.signal.manager.api.IncorrectPinException;
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.InvalidUsernameException;
import org.asamk.signal.manager.api.LastGroupAdminException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.NotAGroupMemberException;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.PinLockedException;
+import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.ReceiveConfig;
import org.asamk.signal.manager.api.Recipient;
import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.StickerPack;
+import org.asamk.signal.manager.api.StickerPackId;
import org.asamk.signal.manager.api.StickerPackInvalidException;
import org.asamk.signal.manager.api.StickerPackUrl;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.api.UpdateProfile;
import org.asamk.signal.manager.api.UserStatus;
+import org.asamk.signal.manager.api.UsernameLinkUrl;
+import org.asamk.signal.manager.api.UsernameStatus;
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.exceptions.DBusExecutionException;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.DBusSigHandler;
import org.freedesktop.dbus.types.Variant;
private final Set<ReceiveMessageHandler> weakHandlers = new HashSet<>();
private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
private final List<Runnable> closedListeners = new ArrayList<>();
+ private final String busname;
private DBusSigHandler<Signal.MessageReceivedV2> dbusMsgHandler;
private DBusSigHandler<Signal.EditMessageReceived> dbusEditMsgHandler;
private DBusSigHandler<Signal.ReceiptReceivedV2> dbusRcptHandler;
private DBusSigHandler<Signal.SyncMessageReceivedV2> dbusSyncHandler;
- public DbusManagerImpl(final Signal signal, DBusConnection connection) {
+ public DbusManagerImpl(final Signal signal, DBusConnection connection, final String busname) {
this.signal = signal;
this.connection = connection;
+ this.busname = busname;
}
@Override
}
@Override
- public void updateAccountAttributes(final String deviceName) throws IOException {
+ public Map<String, UsernameStatus> getUsernameStatus(final Set<String> usernames) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateAccountAttributes(
+ final String deviceName,
+ final Boolean unrestrictedUnidentifiedSender,
+ final Boolean discoverableByNumber,
+ final Boolean numberSharing
+ ) throws IOException {
if (deviceName != null) {
final var devicePath = signal.getThisDevice();
getRemoteObject(devicePath, Signal.Device.class).Set("org.asamk.Signal.Device", "Name", deviceName);
+ } else {
+ throw new UnsupportedOperationException();
}
}
}
@Override
- public void updateConfiguration(Configuration newConfiguration) throws IOException {
+ public void updateConfiguration(Configuration newConfiguration) {
final var configuration = getRemoteObject(new DBusPath(signal.getObjectPath() + "/Configuration"),
Signal.Configuration.class);
newConfiguration.readReceipts()
}
@Override
- public String setUsername(final String username) throws IOException, InvalidUsernameException {
+ public String getUsername() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UsernameLinkUrl getUsernameLink() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setUsername(final String username) throws IOException, InvalidUsernameException {
throw new UnsupportedOperationException();
}
throw new UnsupportedOperationException();
}
+ @Override
+ public void startChangeNumber(
+ final String newNumber, final boolean voiceVerification, final String captcha
+ ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void finishChangeNumber(
+ final String newNumber, final String verificationCode, final String pin
+ ) throws IncorrectPinException, PinLockedException, IOException {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void unregister() throws IOException {
signal.unregister();
public SendGroupMessageResults quitGroup(
final GroupId groupId, final Set<RecipientIdentifier.Single> groupAdmins
) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException {
- if (groupAdmins.size() > 0) {
+ if (!groupAdmins.isEmpty()) {
throw new UnsupportedOperationException();
}
final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
@Override
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()));
+ try {
+ final var newGroupId = signal.joinGroup(inviteLinkUrl.getUrl());
+ return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
+ } catch (DBusExecutionException e) {
+ throw new IOException("Failed to join group: " + e.getMessage() + " (" + e.getClass().getSimpleName() + ")",
+ e);
+ }
}
@Override
@Override
public SendMessageResults sendMessage(
- final Message message, final Set<RecipientIdentifier> recipients
+ final Message message, final Set<RecipientIdentifier> recipients, final boolean notifySelf
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
return handleMessage(recipients,
numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers),
return new SendMessageResults(0, Map.of());
}
+ @Override
+ public SendMessageResults sendMessageRequestResponse(
+ final MessageEnvelope.Sync.MessageRequestResponse.Type type,
+ final Set<RecipientIdentifier> recipientIdentifiers
+ ) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void hideRecipient(final RecipientIdentifier.Single recipient) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void deleteRecipient(final RecipientIdentifier.Single recipient) {
signal.deleteRecipient(recipient.getIdentifier());
if (isWeakListener) {
weakHandlers.add(handler);
} else {
- if (messageHandlers.size() == 0) {
+ if (messageHandlers.isEmpty()) {
installMessageHandlers();
}
messageHandlers.add(handler);
synchronized (messageHandlers) {
weakHandlers.remove(handler);
messageHandlers.remove(handler);
- if (messageHandlers.size() == 0) {
+ if (messageHandlers.isEmpty()) {
uninstallMessageHandlers();
}
}
@Override
public boolean isReceiving() {
synchronized (messageHandlers) {
- return messageHandlers.size() > 0;
+ return !messageHandlers.isEmpty();
}
}
+ private Thread receiveThread;
+
@Override
public void receiveMessages(
Optional<Duration> timeout, Optional<Integer> maxMessages, ReceiveMessageHandler handler
- ) throws IOException {
+ ) throws IOException, AlreadyReceivingException {
+ if (receiveThread != null) {
+ throw new AlreadyReceivingException("Already receiving message.");
+ }
+ receiveThread = Thread.currentThread();
+
final var remainingMessages = new AtomicInteger(maxMessages.orElse(-1));
final var lastMessage = new AtomicLong(System.currentTimeMillis());
final var thread = Thread.currentThread();
}
Thread.sleep(sleepTimeRemaining);
} catch (InterruptedException ignored) {
+ break;
}
}
} else {
}
removeReceiveHandler(receiveHandler);
+ receiveThread = null;
+ }
+
+ @Override
+ public void stopReceiveMessages() {
+ if (receiveThread != null) {
+ receiveThread.interrupt();
+ }
}
@Override
return null;
}
final var contactName = signal.getContactName(n);
- if (onlyContacts && contactName.length() == 0) {
+ if (onlyContacts && contactName.isEmpty()) {
return null;
}
if (name.isPresent() && !name.get().equals(contactName)) {
}
return Recipient.newBuilder()
.withAddress(new RecipientAddress(null, n))
- .withContact(new Contact(contactName, null, null, 0, contactBlocked, false, false))
+ .withContact(new Contact(contactName,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ false,
+ contactBlocked,
+ false,
+ false,
+ false,
+ null))
.build();
}).filter(Objects::nonNull).toList();
}
this.notify();
}
synchronized (messageHandlers) {
- if (messageHandlers.size() > 0) {
+ if (!messageHandlers.isEmpty()) {
uninstallMessageHandlers();
}
weakHandlers.clear();
.map(RecipientIdentifier.Single.class::cast)
.map(RecipientIdentifier.Single::getIdentifier)
.toList();
- if (singleRecipients.size() > 0) {
+ if (!singleRecipients.isEmpty()) {
timestamp = recipientsHandler.apply(singleRecipients);
}
private <T extends DBusInterface> T getRemoteObject(final DBusPath path, final Class<T> type) {
try {
- return connection.getRemoteObject(DbusConfig.getBusname(), path.getPath(), type);
+ return connection.getRemoteObject(busname, path.getPath(), type);
} catch (DBusException e) {
throw new AssertionError(e);
}
Optional.empty(),
Optional.empty(),
List.of(),
- List.of(),
+ getMentions(extras),
List.of(),
List.of())),
Optional.empty(),
Optional.empty(),
Optional.empty(),
List.of(),
- List.of(),
+ getMentions(extras),
List.of(),
List.of()))),
Optional.empty(),
Optional.empty(),
Optional.empty(),
List.of(),
- List.of(),
+ getMentions(extras),
List.of(),
List.of())),
Optional.empty(),
};
connection.addSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
} catch (DBusException e) {
- e.printStackTrace();
+ throw new RuntimeException(e);
}
signal.subscribeReceive();
}
connection.removeSigHandler(Signal.ReceiptReceivedV2.class, signal, this.dbusRcptHandler);
connection.removeSigHandler(Signal.SyncMessageReceivedV2.class, signal, this.dbusSyncHandler);
} catch (DBusException e) {
- e.printStackTrace();
+ throw new RuntimeException(e);
}
}
}).toList();
}
+ private List<MessageEnvelope.Data.Mention> getMentions(final Map<String, Variant<?>> extras) {
+ if (!extras.containsKey("mentions")) {
+ return List.of();
+ }
+
+ final List<DBusMap<String, Variant<?>>> mentions = getValue(extras, "mentions");
+ return mentions.stream()
+ .map(a -> new MessageEnvelope.Data.Mention(new RecipientAddress(null, getValue(a, "recipient")),
+ getValue(a, "start"),
+ getValue(a, "length")))
+ .toList();
+ }
+
@Override
public InputStream retrieveAttachment(final String id) throws IOException {
throw new UnsupportedOperationException();
}
+ @Override
+ public InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InputStream retrieveProfileAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InputStream retrieveGroupAvatar(final GroupId groupId) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InputStream retrieveSticker(final StickerPackId stickerPackId, final int stickerId) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
@SuppressWarnings("unchecked")
private <T> T getValue(
final Map<String, Variant<?>> stringVariantMap, final String field