From: AsamK Date: Sat, 12 Jun 2021 15:57:15 +0000 (+0200) Subject: Add sendTyping command X-Git-Tag: v0.8.4~5 X-Git-Url: https://git.nmode.ca/signal-cli/commitdiff_plain/7e223dc228b81edd2f9b42845d42ce26f1d88e13 Add sendTyping command Fixes #602 --- diff --git a/CHANGELOG.md b/CHANGELOG.md index a4667d60..9c6a9805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ **Attention**: Now requires native libsignal-client version 0.8.1 ### Added -- Added new parameters to `updateGroup` for group v2 features: +- New parameters to `updateGroup` for group v2 features: `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration` -- Added new `--delete` parameter to `quitGroup`, to delete the local group data +- New `--delete` parameter for `quitGroup`, to delete the local group data +- New 'sendTyping' command to send typing indicators ### Fixed - Prevent last admin of a group from leaving the group diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 994d1e61..2500371f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -17,6 +17,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.Device; +import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; @@ -106,6 +107,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; +import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact; @@ -1597,6 +1599,40 @@ public class Manager implements Closeable { } } + public void sendTypingMessage( + TypingAction action, Set recipients + ) throws IOException, UntrustedIdentityException, InvalidNumberException { + sendTypingMessageInternal(action, getSignalServiceAddresses(recipients)); + } + + private void sendTypingMessageInternal( + TypingAction action, Set recipientIds + ) throws IOException, UntrustedIdentityException { + final var timestamp = System.currentTimeMillis(); + var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent()); + var messageSender = createMessageSender(); + for (var recipientId : recipientIds) { + final var address = resolveSignalServiceAddress(recipientId); + messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message); + } + } + + public void sendGroupTypingMessage( + TypingAction action, GroupId groupId + ) throws IOException, NotAGroupMemberException, GroupNotFoundException { + final var timestamp = System.currentTimeMillis(); + final var g = getGroupForSending(groupId); + final var message = new SignalServiceTypingMessage(action.toSignalService(), + timestamp, + Optional.of(groupId.serialize())); + final var messageSender = createMessageSender(); + final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId())); + final var addresses = recipientIdList.stream() + .map(this::resolveSignalServiceAddress) + .collect(Collectors.toList()); + messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null); + } + private Pair> sendMessage( SignalServiceDataMessage.Builder messageBuilder, Set recipientIds ) throws IOException { diff --git a/lib/src/main/java/org/asamk/signal/manager/api/TypingAction.java b/lib/src/main/java/org/asamk/signal/manager/api/TypingAction.java new file mode 100644 index 00000000..228990c1 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/TypingAction.java @@ -0,0 +1,19 @@ +package org.asamk.signal.manager.api; + +import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; + +public enum TypingAction { + START, + STOP; + + public SignalServiceTypingMessage.Action toSignalService() { + switch (this) { + case START: + return SignalServiceTypingMessage.Action.STARTED; + case STOP: + return SignalServiceTypingMessage.Action.STOPPED; + default: + throw new IllegalStateException("Invalid typing action " + this); + } + } +} diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 8e011c90..e30a1965 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -199,6 +199,20 @@ Specify the timestamp of the message to which to react. *-r*, *--remove*:: Remove a reaction. +=== sendTyping + +Send typing message to trigger a typing indicator for the recipient. +Indicator will be shown for 15seconds unless a typing STOP message is sent first. + +RECIPIENT:: +Specify the recipients’ phone number. + +*-g* GROUP, *--group* GROUP:: +Specify the recipient group ID in base64 encoding. + +*-s*, *--stop*:: +Send a typing STOP message. + === remoteDelete Remotely delete a previously sent message. diff --git a/src/main/java/org/asamk/signal/commands/Commands.java b/src/main/java/org/asamk/signal/commands/Commands.java index a068405a..06e1fa1e 100644 --- a/src/main/java/org/asamk/signal/commands/Commands.java +++ b/src/main/java/org/asamk/signal/commands/Commands.java @@ -28,6 +28,7 @@ public class Commands { addCommand("sendContacts", new SendContactsCommand()); addCommand("sendReaction", new SendReactionCommand()); addCommand("sendSyncRequest", new SendSyncRequestCommand()); + addCommand("sendTyping", new SendTypingCommand()); addCommand("setPin", new SetPinCommand()); addCommand("trust", new TrustCommand()); addCommand("unblock", new UnblockCommand()); diff --git a/src/main/java/org/asamk/signal/commands/SendTypingCommand.java b/src/main/java/org/asamk/signal/commands/SendTypingCommand.java new file mode 100644 index 00000000..80637dfb --- /dev/null +++ b/src/main/java/org/asamk/signal/commands/SendTypingCommand.java @@ -0,0 +1,71 @@ +package org.asamk.signal.commands; + +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; +import net.sourceforge.argparse4j.inf.Subparser; + +import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.UserErrorException; +import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.TypingAction; +import org.asamk.signal.manager.groups.GroupId; +import org.asamk.signal.manager.groups.GroupIdFormatException; +import org.asamk.signal.manager.groups.GroupNotFoundException; +import org.asamk.signal.manager.groups.NotAGroupMemberException; +import org.asamk.signal.util.Util; +import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.util.InvalidNumberException; + +import java.io.IOException; +import java.util.HashSet; + +public class SendTypingCommand implements LocalCommand { + + @Override + public void attachToSubparser(final Subparser subparser) { + subparser.help( + "Send typing message to trigger a typing indicator for the recipient. Indicator will be shown for 15seconds unless a typing STOP message is sent first."); + subparser.addArgument("-g", "--group").help("Specify the recipient group ID."); + subparser.addArgument("recipient").help("Specify the recipients' phone number.").nargs("*"); + subparser.addArgument("-s", "--stop").help("Send a typing STOP message.").action(Arguments.storeTrue()); + } + + @Override + public void handleCommand(final Namespace ns, final Manager m) throws CommandException { + final var recipients = ns.getList("recipient"); + final var groupIdString = ns.getString("group"); + + final var noRecipients = recipients == null || recipients.isEmpty(); + if (noRecipients && groupIdString == null) { + throw new UserErrorException("No recipients given"); + } + if (!noRecipients && groupIdString != null) { + throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time"); + } + + final var action = ns.getBoolean("stop") ? TypingAction.STOP : TypingAction.START; + + GroupId groupId = null; + if (groupIdString != null) { + try { + groupId = Util.decodeGroupId(groupIdString); + } catch (GroupIdFormatException e) { + throw new UserErrorException("Invalid group id: " + e.getMessage()); + } + } + + try { + if (groupId != null) { + m.sendGroupTypingMessage(action, groupId); + } else { + m.sendTypingMessage(action, new HashSet<>(recipients)); + } + } catch (IOException | UntrustedIdentityException e) { + throw new UserErrorException("Failed to send message: " + e.getMessage()); + } catch (GroupNotFoundException | NotAGroupMemberException e) { + throw new UserErrorException("Failed to send to group: " + e.getMessage()); + } catch (InvalidNumberException e) { + throw new UserErrorException("Invalid number: " + e.getMessage()); + } + } +}