From 593cd7d8ca6e8e0ab654accfd7e3c9d2ee01b001 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 28 Sep 2021 18:51:44 +0200 Subject: [PATCH] Refactor dbus client mode to improve maintainability --- src/main/java/org/asamk/Signal.java | 2 +- src/main/java/org/asamk/signal/App.java | 13 +- .../asamk/signal/commands/DbusCommand.java | 20 - .../signal/commands/RemoteDeleteCommand.java | 46 +- .../asamk/signal/commands/SendCommand.java | 97 +--- .../signal/commands/SendReactionCommand.java | 53 +- .../signal/commands/UpdateGroupCommand.java | 43 +- .../exceptions/UserErrorException.java | 4 + .../asamk/signal/dbus/DbusManagerImpl.java | 487 ++++++++++++++++++ .../org/asamk/signal/dbus/DbusSignalImpl.java | 57 +- 10 files changed, 531 insertions(+), 291 deletions(-) delete mode 100644 src/main/java/org/asamk/signal/commands/DbusCommand.java create mode 100644 src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 2105ca76..cc521f6d 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -13,7 +13,7 @@ import java.util.List; */ public interface Signal extends DBusInterface { - String getNumber(); + String getSelfNumber(); long sendMessage( String message, List attachments, String recipient diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index e81b7018..bffbded5 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -8,7 +8,6 @@ import net.sourceforge.argparse4j.inf.Namespace; import org.asamk.Signal; import org.asamk.signal.commands.Command; import org.asamk.signal.commands.Commands; -import org.asamk.signal.commands.DbusCommand; import org.asamk.signal.commands.ExtendedDbusCommand; import org.asamk.signal.commands.LocalCommand; import org.asamk.signal.commands.MultiLocalCommand; @@ -19,6 +18,7 @@ import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; +import org.asamk.signal.dbus.DbusManagerImpl; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotRegisteredException; import org.asamk.signal.manager.ProvisioningManager; @@ -29,6 +29,7 @@ import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.util.IOUtils; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -346,8 +347,14 @@ public class App { ) throws CommandException { if (command instanceof ExtendedDbusCommand) { ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn, outputWriter); - } else if (command instanceof DbusCommand) { - ((DbusCommand) command).handleCommand(ns, ts, outputWriter); + } else if (command instanceof LocalCommand) { + try { + ((LocalCommand) command).handleCommand(ns, new DbusManagerImpl(ts), outputWriter); + } catch (UnsupportedOperationException e) { + throw new UserErrorException("Command is not yet implemented via dbus", e); + } catch (DBusExecutionException e) { + throw new UnexpectedErrorException(e.getMessage(), e); + } } else { throw new UserErrorException("Command is not yet implemented via dbus"); } diff --git a/src/main/java/org/asamk/signal/commands/DbusCommand.java b/src/main/java/org/asamk/signal/commands/DbusCommand.java deleted file mode 100644 index 9f676a39..00000000 --- a/src/main/java/org/asamk/signal/commands/DbusCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.asamk.signal.commands; - -import net.sourceforge.argparse4j.inf.Namespace; - -import org.asamk.Signal; -import org.asamk.signal.OutputWriter; -import org.asamk.signal.commands.exceptions.CommandException; -import org.asamk.signal.dbus.DbusSignalImpl; -import org.asamk.signal.manager.Manager; - -public interface DbusCommand extends LocalCommand { - - void handleCommand(Namespace ns, Signal signal, OutputWriter outputWriter) throws CommandException; - - default void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter - ) throws CommandException { - handleCommand(ns, new DbusSignalImpl(m, null), outputWriter); - } -} diff --git a/src/main/java/org/asamk/signal/commands/RemoteDeleteCommand.java b/src/main/java/org/asamk/signal/commands/RemoteDeleteCommand.java index 7d7067c4..e515defe 100644 --- a/src/main/java/org/asamk/signal/commands/RemoteDeleteCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemoteDeleteCommand.java @@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.Signal; import org.asamk.signal.JsonWriter; import org.asamk.signal.OutputWriter; import org.asamk.signal.PlainTextWriter; @@ -17,13 +16,11 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.ErrorUtils; -import org.freedesktop.dbus.errors.UnknownObject; -import org.freedesktop.dbus.exceptions.DBusExecutionException; import java.io.IOException; import java.util.Map; -public class RemoteDeleteCommand implements DbusCommand, JsonRpcLocalCommand { +public class RemoteDeleteCommand implements JsonRpcLocalCommand { @Override public String getName() { @@ -69,47 +66,6 @@ public class RemoteDeleteCommand implements DbusCommand, JsonRpcLocalCommand { } } - @Override - public void handleCommand( - final Namespace ns, final Signal signal, final OutputWriter outputWriter - ) throws CommandException { - final var recipients = ns.getList("recipient"); - final var groupIdStrings = ns.getList("group-id"); - - final var noRecipients = recipients == null || recipients.isEmpty(); - final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty(); - if (noRecipients && noGroups) { - throw new UserErrorException("No recipients given"); - } - if (!noRecipients && !noGroups) { - throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time"); - } - - final long targetTimestamp = ns.getLong("target-timestamp"); - - try { - long timestamp = 0; - if (!noGroups) { - final var groupIds = CommandUtil.getGroupIds(groupIdStrings); - for (final var groupId : groupIds) { - timestamp = signal.sendGroupRemoteDeleteMessage(targetTimestamp, groupId.serialize()); - } - } else { - timestamp = signal.sendRemoteDeleteMessage(targetTimestamp, recipients); - } - outputResult(outputWriter, timestamp); - } catch (UnknownObject e) { - throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); - } catch (Signal.Error.InvalidNumber e) { - throw new UserErrorException("Invalid number: " + e.getMessage()); - } catch (Signal.Error.GroupNotFound e) { - throw new UserErrorException("Failed to send to group: " + e.getMessage()); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); - } - } - private void outputResult(final OutputWriter outputWriter, final long timestamp) { if (outputWriter instanceof PlainTextWriter) { final var writer = (PlainTextWriter) outputWriter; diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 1973b1a1..1cd2e674 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -4,13 +4,11 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.Signal; import org.asamk.signal.JsonWriter; import org.asamk.signal.OutputWriter; import org.asamk.signal.PlainTextWriter; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; -import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.Manager; @@ -22,8 +20,6 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.ErrorUtils; import org.asamk.signal.util.IOUtils; -import org.freedesktop.dbus.errors.UnknownObject; -import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +29,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -public class SendCommand implements DbusCommand, JsonRpcLocalCommand { +public class SendCommand implements JsonRpcLocalCommand { private final static Logger logger = LoggerFactory.getLogger(SendCommand.class); @@ -116,97 +112,6 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { } } - @Override - public void handleCommand( - final Namespace ns, final Signal signal, final OutputWriter outputWriter - ) throws CommandException { - final var recipients = ns.getList("recipient"); - final var isEndSession = ns.getBoolean("end-session"); - final var groupIdStrings = ns.getList("group-id"); - final var isNoteToSelf = ns.getBoolean("note-to-self"); - - final var noRecipients = recipients == null || recipients.isEmpty(); - final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty(); - if ((noRecipients && isEndSession) || (noRecipients && noGroups && !isNoteToSelf)) { - throw new UserErrorException("No recipients given"); - } - if (!noRecipients && !noGroups) { - throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time"); - } - if (!noRecipients && isNoteToSelf) { - throw new UserErrorException( - "You cannot specify recipients by phone number and note to self at the same time"); - } - - if (isEndSession) { - try { - signal.sendEndSessionMessage(recipients); - return; - } catch (Signal.Error.UntrustedIdentity e) { - throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")"); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); - } - } - - var messageText = ns.getString("message"); - if (messageText == null) { - try { - messageText = IOUtils.readAll(System.in, Charset.defaultCharset()); - } catch (IOException e) { - throw new UserErrorException("Failed to read message from stdin: " + e.getMessage()); - } - } - - List attachments = ns.getList("attachment"); - if (attachments == null) { - attachments = List.of(); - } - - if (!noGroups) { - final var groupIds = CommandUtil.getGroupIds(groupIdStrings); - - try { - long timestamp = 0; - for (final var groupId : groupIds) { - timestamp = signal.sendGroupMessage(messageText, attachments, groupId.serialize()); - } - outputResult(outputWriter, timestamp); - return; - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send group message: " + e.getMessage(), e); - } - } - - if (isNoteToSelf) { - try { - var timestamp = signal.sendNoteToSelfMessage(messageText, attachments); - outputResult(outputWriter, timestamp); - return; - } catch (Signal.Error.UntrustedIdentity e) { - throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")"); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage(), e); - } - } - - try { - var timestamp = signal.sendMessage(messageText, attachments, recipients); - outputResult(outputWriter, timestamp); - } catch (UnknownObject e) { - throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); - } catch (Signal.Error.UntrustedIdentity e) { - throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")"); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); - } - } - private void outputResult(final OutputWriter outputWriter, final long timestamp) { if (outputWriter instanceof PlainTextWriter) { final var writer = (PlainTextWriter) outputWriter; diff --git a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java index f8c3c358..a1c6c319 100644 --- a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java @@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.Signal; import org.asamk.signal.JsonWriter; import org.asamk.signal.OutputWriter; import org.asamk.signal.PlainTextWriter; @@ -17,13 +16,11 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.ErrorUtils; -import org.freedesktop.dbus.errors.UnknownObject; -import org.freedesktop.dbus.exceptions.DBusExecutionException; import java.io.IOException; import java.util.Map; -public class SendReactionCommand implements DbusCommand, JsonRpcLocalCommand { +public class SendReactionCommand implements JsonRpcLocalCommand { @Override public String getName() { @@ -85,54 +82,6 @@ public class SendReactionCommand implements DbusCommand, JsonRpcLocalCommand { } } - @Override - public void handleCommand( - final Namespace ns, final Signal signal, final OutputWriter outputWriter - ) throws CommandException { - final var recipients = ns.getList("recipient"); - final var groupIdStrings = ns.getList("group-id"); - - final var noRecipients = recipients == null || recipients.isEmpty(); - final var noGroups = groupIdStrings == null || groupIdStrings.isEmpty(); - if (noRecipients && noGroups) { - throw new UserErrorException("No recipients given"); - } - if (!noRecipients && !noGroups) { - throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time"); - } - - final var emoji = ns.getString("emoji"); - final var isRemove = ns.getBoolean("remove"); - final var targetAuthor = ns.getString("target-author"); - final var targetTimestamp = ns.getLong("target-timestamp"); - - try { - long timestamp = 0; - if (!noGroups) { - final var groupIds = CommandUtil.getGroupIds(groupIdStrings); - for (final var groupId : groupIds) { - timestamp = signal.sendGroupMessageReaction(emoji, - isRemove, - targetAuthor, - targetTimestamp, - groupId.serialize()); - } - } else { - timestamp = signal.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, recipients); - } - outputResult(outputWriter, timestamp); - } catch (UnknownObject e) { - throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); - } catch (Signal.Error.InvalidNumber e) { - throw new UserErrorException("Invalid number: " + e.getMessage()); - } catch (Signal.Error.GroupNotFound e) { - throw new UserErrorException("Failed to send to group: " + e.getMessage()); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); - } - } - private void outputResult(final OutputWriter outputWriter, final long timestamp) { if (outputWriter instanceof PlainTextWriter) { final var writer = (PlainTextWriter) outputWriter; diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 49cd4719..4bbaa992 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -4,7 +4,6 @@ import net.sourceforge.argparse4j.impl.Arguments; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; -import org.asamk.Signal; import org.asamk.signal.JsonWriter; import org.asamk.signal.OutputWriter; import org.asamk.signal.PlainTextWriter; @@ -21,17 +20,14 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.ErrorUtils; -import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; -public class UpdateGroupCommand implements DbusCommand, JsonRpcLocalCommand { +public class UpdateGroupCommand implements JsonRpcLocalCommand { private final static Logger logger = LoggerFactory.getLogger(UpdateGroupCommand.class); @@ -179,43 +175,6 @@ public class UpdateGroupCommand implements DbusCommand, JsonRpcLocalCommand { } } - @Override - public void handleCommand( - final Namespace ns, final Signal signal, final OutputWriter outputWriter - ) throws CommandException { - var groupId = CommandUtil.getGroupId(ns.getString("group-id")); - - var groupName = ns.getString("name"); - if (groupName == null) { - groupName = ""; - } - - List groupMembers = ns.getList("member"); - if (groupMembers == null) { - groupMembers = new ArrayList<>(); - } - - var groupAvatar = ns.getString("avatar"); - if (groupAvatar == null) { - groupAvatar = ""; - } - - try { - var newGroupId = signal.updateGroup(groupId == null ? new byte[0] : groupId.serialize(), - groupName, - groupMembers, - groupAvatar); - if (groupId == null) { - outputResult(outputWriter, null, GroupId.unknownVersion(newGroupId)); - } - } catch (Signal.Error.AttachmentInvalid e) { - throw new UserErrorException("Failed to add avatar attachment for group\": " + e.getMessage()); - } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); - } - } - private void outputResult(final OutputWriter outputWriter, final Long timestamp, final GroupId groupId) { if (outputWriter instanceof PlainTextWriter) { final var writer = (PlainTextWriter) outputWriter; diff --git a/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java b/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java index 84e957cc..819ce495 100644 --- a/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java +++ b/src/main/java/org/asamk/signal/commands/exceptions/UserErrorException.java @@ -5,4 +5,8 @@ public final class UserErrorException extends CommandException { public UserErrorException(final String message) { super(message); } + + public UserErrorException(final String message, final Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java new file mode 100644 index 00000000..b9f5ae11 --- /dev/null +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -0,0 +1,487 @@ +package org.asamk.signal.dbus; + +import org.asamk.Signal; +import org.asamk.signal.manager.AttachmentInvalidException; +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.Device; +import org.asamk.signal.manager.api.Group; +import org.asamk.signal.manager.api.Identity; +import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.RecipientIdentifier; +import org.asamk.signal.manager.api.SendGroupMessageResults; +import org.asamk.signal.manager.api.SendMessageResults; +import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupInviteLinkUrl; +import org.asamk.signal.manager.groups.GroupLinkState; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.GroupPermission; +import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; +import org.asamk.signal.manager.groups.LastGroupAdminException; +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.whispersystems.libsignal.IdentityKey; +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 java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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; + +/** + * This class implements the Manager interface using the DBus Signal interface, where possible. + * It's used for the signal-cli dbus client mode (--dbus, --dbus-system) + */ +public class DbusManagerImpl implements Manager { + + private final Signal signal; + + public DbusManagerImpl(final Signal signal) { + this.signal = signal; + } + + @Override + public String getSelfNumber() { + return signal.getSelfNumber(); + } + + @Override + public void checkAccountState() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Map> areUsersRegistered(final Set numbers) throws IOException { + final var numbersList = new ArrayList<>(numbers); + final var registered = signal.isRegistered(numbersList); + + final var result = new HashMap>(); + 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)); + } + return result; + } + + @Override + public void updateAccountAttributes(final String deviceName) throws IOException { + if (deviceName != null) { + signal.updateDeviceName(deviceName); + } + } + + @Override + public void setProfile( + final String givenName, + final String familyName, + final String about, + final String aboutEmoji, + final Optional avatar + ) throws IOException { + signal.updateProfile(emptyIfNull(givenName), + emptyIfNull(familyName), + emptyIfNull(about), + emptyIfNull(aboutEmoji), + avatar == null ? "" : avatar.transform(File::getPath).or(""), + avatar != null && !avatar.isPresent()); + } + + @Override + public void unregister() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteAccount() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void submitRateLimitRecaptchaChallenge(final String challenge, final String captcha) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public List getLinkedDevices() throws IOException { + return signal.listDevices() + .stream() + .map(name -> new Device(-1, name, 0, 0, false)) + .collect(Collectors.toList()); + } + + @Override + public void removeLinkedDevices(final int deviceId) throws IOException { + signal.removeDevice(deviceId); + } + + @Override + public void addDeviceLink(final URI linkUri) throws IOException, InvalidKeyException { + signal.addDevice(linkUri.toString()); + } + + @Override + public void setRegistrationLockPin(final Optional pin) throws IOException, UnauthenticatedResponseException { + if (pin.isPresent()) { + signal.setPin(pin.get()); + } else { + signal.removePin(); + } + } + + @Override + public Profile getRecipientProfile(final RecipientIdentifier.Single recipient) throws UnregisteredUserException { + throw new UnsupportedOperationException(); + } + + @Override + public List getGroups() { + final var groupIds = signal.getGroupIds(); + return groupIds.stream().map(id -> getGroup(GroupId.unknownVersion(id))).collect(Collectors.toList()); + } + + @Override + public SendGroupMessageResults quitGroup( + final GroupId groupId, final Set groupAdmins + ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException { + if (groupAdmins.size() > 0) { + throw new UnsupportedOperationException(); + } + signal.quitGroup(groupId.serialize()); + return new SendGroupMessageResults(0, List.of()); + } + + @Override + public void deleteGroup(final GroupId groupId) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Pair createGroup( + final String name, final Set members, final File avatarFile + ) throws IOException, AttachmentInvalidException { + final var newGroupId = signal.updateGroup(new byte[0], + emptyIfNull(name), + members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()), + avatarFile == null ? "" : avatarFile.getPath()); + return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of())); + } + + @Override + public SendGroupMessageResults updateGroup( + final GroupId groupId, + final String name, + final String description, + final Set members, + final Set removeMembers, + final Set admins, + final Set removeAdmins, + final boolean resetGroupLink, + final GroupLinkState groupLinkState, + final GroupPermission addMemberPermission, + final GroupPermission editDetailsPermission, + final File avatarFile, + final Integer expirationTimer, + final Boolean isAnnouncementGroup + ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { + signal.updateGroup(groupId.serialize(), + emptyIfNull(name), + members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()), + avatarFile == null ? "" : avatarFile.getPath()); + return new SendGroupMessageResults(0, List.of()); + } + + @Override + public Pair joinGroup(final GroupInviteLinkUrl inviteLinkUrl) throws IOException, GroupLinkNotActiveException { + final var newGroupId = signal.joinGroup(inviteLinkUrl.getUrl()); + return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of())); + } + + @Override + public void sendTypingMessage( + final TypingAction action, final Set recipients + ) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { + for (final var recipient : recipients) { + if (recipient instanceof RecipientIdentifier.Single) { + signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(), + action == TypingAction.STOP); + } else if (recipient instanceof RecipientIdentifier.Group) { + throw new UnsupportedOperationException(); + } + } + } + + @Override + public void sendReadReceipt( + final RecipientIdentifier.Single sender, final List messageIds + ) throws IOException, UntrustedIdentityException { + signal.sendReadReceipt(sender.getIdentifier(), messageIds); + } + + @Override + public void sendViewedReceipt( + final RecipientIdentifier.Single sender, final List messageIds + ) throws IOException, UntrustedIdentityException { + throw new UnsupportedOperationException(); + } + + @Override + public SendMessageResults sendMessage( + final Message message, final Set 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)); + } + + @Override + public SendMessageResults sendRemoteDeleteMessage( + final long targetSentTimestamp, final Set recipients + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { + return handleMessage(recipients, + numbers -> signal.sendRemoteDeleteMessage(targetSentTimestamp, numbers), + () -> signal.sendRemoteDeleteMessage(targetSentTimestamp, signal.getSelfNumber()), + groupId -> signal.sendGroupRemoteDeleteMessage(targetSentTimestamp, groupId)); + } + + @Override + public SendMessageResults sendMessageReaction( + final String emoji, + final boolean remove, + final RecipientIdentifier.Single targetAuthor, + final long targetSentTimestamp, + final Set recipients + ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { + return handleMessage(recipients, + numbers -> signal.sendMessageReaction(emoji, + remove, + targetAuthor.getIdentifier(), + targetSentTimestamp, + numbers), + () -> signal.sendMessageReaction(emoji, + remove, + targetAuthor.getIdentifier(), + targetSentTimestamp, + signal.getSelfNumber()), + groupId -> signal.sendGroupMessageReaction(emoji, + remove, + targetAuthor.getIdentifier(), + targetSentTimestamp, + groupId)); + } + + @Override + public SendMessageResults sendEndSessionMessage(final Set recipients) throws IOException { + signal.sendEndSessionMessage(recipients.stream() + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList())); + return new SendMessageResults(0, Map.of()); + } + + @Override + public void setContactName( + final RecipientIdentifier.Single recipient, final String name + ) throws NotMasterDeviceException, UnregisteredUserException { + signal.setContactName(recipient.getIdentifier(), name); + } + + @Override + public void setContactBlocked( + final RecipientIdentifier.Single recipient, final boolean blocked + ) throws NotMasterDeviceException, IOException { + signal.setContactBlocked(recipient.getIdentifier(), blocked); + } + + @Override + public void setGroupBlocked( + final GroupId groupId, final boolean blocked + ) throws GroupNotFoundException, IOException { + signal.setGroupBlocked(groupId.serialize(), blocked); + } + + @Override + public void setExpirationTimer( + final RecipientIdentifier.Single recipient, final int messageExpirationTimer + ) throws IOException { + signal.setExpirationTimer(recipient.getIdentifier(), messageExpirationTimer); + } + + @Override + public URI uploadStickerPack(final File path) throws IOException, StickerPackInvalidException { + try { + return new URI(signal.uploadStickerPack(path.getPath())); + } catch (URISyntaxException e) { + throw new AssertionError(e); + } + } + + @Override + public void requestAllSyncData() throws IOException { + signal.sendSyncRequest(); + } + + @Override + public void receiveMessages( + final long timeout, + final TimeUnit unit, + final boolean returnOnTimeout, + final boolean ignoreAttachments, + final ReceiveMessageHandler handler + ) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasCaughtUpWithOldMessages() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isContactBlocked(final RecipientIdentifier.Single recipient) { + return signal.isContactBlocked(recipient.getIdentifier()); + } + + @Override + public File getAttachmentFile(final SignalServiceAttachmentRemoteId attachmentId) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendContacts() throws IOException { + signal.sendContacts(); + } + + @Override + public List> getContacts() { + throw new UnsupportedOperationException(); + } + + @Override + public String getContactOrProfileName(final RecipientIdentifier.Single recipient) { + return signal.getContactName(recipient.getIdentifier()); + } + + @Override + public Group getGroup(final GroupId groupId) { + final var id = groupId.serialize(); + return new Group(groupId, + signal.getGroupName(id), + null, + null, + signal.getGroupMembers(id).stream().map(m -> new RecipientAddress(null, m)).collect(Collectors.toSet()), + Set.of(), + Set.of(), + Set.of(), + signal.isGroupBlocked(id), + 0, + false, + signal.isMember(id)); + } + + @Override + public List getIdentities() { + throw new UnsupportedOperationException(); + } + + @Override + public List getIdentities(final RecipientIdentifier.Single recipient) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean trustIdentityVerified(final RecipientIdentifier.Single recipient, final byte[] fingerprint) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean trustIdentityVerifiedSafetyNumber( + final RecipientIdentifier.Single recipient, final String safetyNumber + ) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean trustIdentityVerifiedSafetyNumber( + final RecipientIdentifier.Single recipient, final byte[] safetyNumber + ) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean trustIdentityAllKeys(final RecipientIdentifier.Single recipient) { + throw new UnsupportedOperationException(); + } + + @Override + public String computeSafetyNumber( + final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey + ) { + throw new UnsupportedOperationException(); + } + + @Override + public SignalServiceAddress resolveSignalServiceAddress(final SignalServiceAddress address) { + return address; + } + + @Override + public void close() throws IOException { + } + + private SendMessageResults handleMessage( + Set recipients, + Function, Long> recipientsHandler, + Supplier noteToSelfHandler, + Function groupHandler + ) { + long timestamp = 0; + final var singleRecipients = recipients.stream() + .filter(r -> r instanceof RecipientIdentifier.Single) + .map(RecipientIdentifier.Single.class::cast) + .map(RecipientIdentifier.Single::getIdentifier) + .collect(Collectors.toList()); + if (singleRecipients.size() > 0) { + timestamp = recipientsHandler.apply(singleRecipients); + } + + if (recipients.contains(RecipientIdentifier.NoteToSelf.INSTANCE)) { + timestamp = noteToSelfHandler.get(); + } + final var groupRecipients = recipients.stream() + .filter(r -> r instanceof RecipientIdentifier.Group) + .map(RecipientIdentifier.Group.class::cast) + .map(g -> g.groupId) + .collect(Collectors.toList()); + for (final var groupId : groupRecipients) { + timestamp = groupHandler.apply(groupId.serialize()); + } + return new SendMessageResults(timestamp, Map.of()); + } + + private String emptyIfNull(final String string) { + return string == null ? "" : string; + } +} diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index c8208774..1a4fdc10 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -67,7 +67,7 @@ public class DbusSignalImpl implements Signal { } @Override - public String getNumber() { + public String getSelfNumber() { return m.getSelfNumber(); } @@ -96,8 +96,6 @@ public class DbusSignalImpl implements Signal { @Override public List listDevices() { List devices; - List results = new ArrayList(); - try { devices = m.getLinkedDevices(); } catch (IOException | Error.Failure e) { @@ -345,7 +343,8 @@ public class DbusSignalImpl implements Signal { // the profile name @Override public String getContactName(final String number) { - return m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber())); + final var name = m.getContactOrProfileName(getSingleRecipientIdentifier(number, m.getSelfNumber())); + return name == null ? "" : name; } @Override @@ -403,7 +402,7 @@ public class DbusSignalImpl implements Signal { @Override public String getGroupName(final byte[] groupId) { var group = m.getGroup(getGroupId(groupId)); - if (group == null) { + if (group == null || group.getTitle() == null) { return ""; } else { return group.getTitle(); @@ -423,15 +422,9 @@ public class DbusSignalImpl implements Signal { @Override public byte[] updateGroup(byte[] groupId, String name, List members, String avatar) { try { - if (groupId.length == 0) { - groupId = null; - } - if (name.isEmpty()) { - name = null; - } - if (avatar.isEmpty()) { - avatar = null; - } + groupId = nullIfEmpty(groupId); + name = nullIfEmpty(name); + avatar = nullIfEmpty(avatar); final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber()); if (groupId == null) { final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar)); @@ -499,17 +492,19 @@ public class DbusSignalImpl implements Signal { @Override public void updateProfile( - final String givenName, - final String familyName, - final String about, - final String aboutEmoji, + String givenName, + String familyName, + String about, + String aboutEmoji, String avatarPath, final boolean removeAvatar ) { try { - if (avatarPath.isEmpty()) { - avatarPath = null; - } + givenName = nullIfEmpty(givenName); + familyName = nullIfEmpty(familyName); + about = nullIfEmpty(about); + aboutEmoji = nullIfEmpty(aboutEmoji); + avatarPath = nullIfEmpty(avatarPath); Optional avatarFile = removeAvatar ? Optional.absent() : avatarPath == null ? null : Optional.of(new File(avatarPath)); @@ -527,17 +522,7 @@ public class DbusSignalImpl implements Signal { String avatarPath, final boolean removeAvatar ) { - try { - if (avatarPath.isEmpty()) { - avatarPath = null; - } - Optional avatarFile = removeAvatar - ? Optional.absent() - : avatarPath == null ? null : Optional.of(new File(avatarPath)); - m.setProfile(name, null, about, aboutEmoji, avatarFile); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } + updateProfile(name, "", about, aboutEmoji, avatarPath, removeAvatar); } @Override @@ -766,4 +751,12 @@ public class DbusSignalImpl implements Signal { throw new Error.InvalidGroupId("Invalid group id: " + e.getMessage()); } } + + private byte[] nullIfEmpty(final byte[] array) { + return array.length == 0 ? null : array; + } + + private String nullIfEmpty(final String name) { + return name.isEmpty() ? null : name; + } } -- 2.50.1