From d4b3816c5dd6792cc9e055de827830b07b224538 Mon Sep 17 00:00:00 2001 From: Kai Kozlov <37962720+kaikozlov@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:42:51 -0600 Subject: [PATCH] Add --no-urgent flag to send command (#1933) * Add --no-push flag to send command Expose the server's `urgent` parameter so callers can skip sending a push notification (FCM/APNs) to the recipient. The message is still delivered in real-time over WebSocket if the recipient's app is active. The flag is added to the Message record (following the same pattern as viewOnce) and threaded through ManagerImpl and SendHelper, keeping the Manager interface unchanged. * Rename --no-push flag to --no-urgent Align with the protocol naming as suggested by the maintainer. The flag controls the 'urgent' parameter on the server request. --- .../org/asamk/signal/manager/api/Message.java | 3 ++- .../signal/manager/helper/GroupHelper.java | 2 +- .../signal/manager/helper/SendHelper.java | 25 +++++++++++-------- .../signal/manager/internal/ManagerImpl.java | 15 +++++------ .../asamk/signal/commands/SendCommand.java | 8 +++++- .../org/asamk/signal/dbus/DbusSignalImpl.java | 9 ++++--- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Message.java b/lib/src/main/java/org/asamk/signal/manager/api/Message.java index 9b372451..09ac0d18 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Message.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Message.java @@ -12,7 +12,8 @@ public record Message( Optional sticker, List previews, Optional storyReply, - List textStyles + List textStyles, + boolean noUrgent ) { public record Mention(RecipientIdentifier.Single recipient, int start, int length) {} diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index 0bd1d280..516ff1e1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -682,7 +682,7 @@ public class GroupHelper { private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); - context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, false, Optional.empty()); + context.getSendHelper().sendAsGroupMessage(messageBuilder, groupId, false, Optional.empty(), true); } private SendGroupMessageResults updateGroupV2( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index bb571a03..10259350 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -84,7 +84,8 @@ public class SendHelper { public SendMessageResult sendMessage( final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId, - Optional editTargetTimestamp + Optional editTargetTimestamp, + boolean urgent ) { var contact = account.getContactStore().getContact(recipientId); if (contact == null || !contact.isProfileSharingEnabled() || contact.isHidden()) { @@ -102,7 +103,7 @@ public class SendHelper { } final var message = messageBuilder.build(); - return sendMessage(message, recipientId, editTargetTimestamp); + return sendMessage(message, recipientId, editTargetTimestamp, urgent); } /** @@ -113,10 +114,11 @@ public class SendHelper { final SignalServiceDataMessage.Builder messageBuilder, final GroupId groupId, final boolean includeSelf, - final Optional editTargetTimestamp + final Optional editTargetTimestamp, + boolean urgent ) throws IOException, GroupNotFoundException, NotAGroupMemberException, GroupSendingNotAllowedException { final var g = getGroupForSending(groupId); - return sendAsGroupMessage(messageBuilder, g, includeSelf, editTargetTimestamp); + return sendAsGroupMessage(messageBuilder, g, includeSelf, editTargetTimestamp, urgent); } /** @@ -128,7 +130,7 @@ public class SendHelper { final Set recipientIds, final GroupInfo groupInfo ) throws IOException { - return sendGroupMessage(message, recipientIds, groupInfo, ContentHint.IMPLICIT, Optional.empty()); + return sendGroupMessage(message, recipientIds, groupInfo, ContentHint.IMPLICIT, Optional.empty(), true); } public SendMessageResult sendReceiptMessage( @@ -311,7 +313,8 @@ public class SendHelper { final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g, final boolean includeSelf, - final Optional editTargetTimestamp + final Optional editTargetTimestamp, + boolean urgent ) throws IOException, GroupSendingNotAllowedException { GroupUtils.setGroupContext(messageBuilder, g); messageBuilder.withExpiration(g.getMessageExpirationTimer()); @@ -330,7 +333,7 @@ public class SendHelper { } } - return sendGroupMessage(message, recipients, g, ContentHint.RESENDABLE, editTargetTimestamp); + return sendGroupMessage(message, recipients, g, ContentHint.RESENDABLE, editTargetTimestamp, urgent); } private List sendGroupMessage( @@ -338,13 +341,13 @@ public class SendHelper { final Set recipientIds, final GroupInfo groupInfo, final ContentHint contentHint, - final Optional editTargetTimestamp + final Optional editTargetTimestamp, + boolean urgent ) throws IOException { final var messageSender = dependencies.getMessageSender(); final var messageSendLogStore = account.getMessageSendLogStore(); final AtomicLong entryId = new AtomicLong(-1); - final var urgent = true; final PartialSendCompleteListener partialSendCompleteListener = sendResult -> { logger.trace("Partial message send result: {}", sendResult.isSuccess()); synchronized (entryId) { @@ -712,10 +715,10 @@ public class SendHelper { private SendMessageResult sendMessage( SignalServiceDataMessage message, RecipientId recipientId, - Optional editTargetTimestamp + Optional editTargetTimestamp, + boolean urgent ) { final var messageSendLogStore = account.getMessageSendLogStore(); - final var urgent = true; final var result = handleSendMessage(recipientId, editTargetTimestamp.isEmpty() ? (messageSender, address, unidentifiedAccess, includePniSignature) -> messageSender.sendDataMessage( diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 4c34d965..29060403 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -666,14 +666,15 @@ public class ManagerImpl implements Manager { Set recipients, boolean notifySelf ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { - return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty()); + return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty(), true); } private SendMessageResults sendMessage( SignalServiceDataMessage.Builder messageBuilder, Set recipients, boolean notifySelf, - Optional editTargetTimestamp + Optional editTargetTimestamp, + boolean urgent ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); long timestamp = getNextMessageTimestamp(); @@ -685,14 +686,14 @@ public class ManagerImpl implements Manager { )) { final var result = notifySelf ? context.getSendHelper() - .sendMessage(messageBuilder, account.getSelfRecipientId(), editTargetTimestamp) + .sendMessage(messageBuilder, account.getSelfRecipientId(), editTargetTimestamp, urgent) : context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp); results.put(recipient, List.of(toSendMessageResult(result))); } else if (recipient instanceof RecipientIdentifier.Single single) { try { final var recipientId = context.getRecipientHelper().resolveRecipient(single); final var result = context.getSendHelper() - .sendMessage(messageBuilder, recipientId, editTargetTimestamp); + .sendMessage(messageBuilder, recipientId, editTargetTimestamp, urgent); results.put(recipient, List.of(toSendMessageResult(result))); } catch (UnregisteredRecipientException e) { results.put(recipient, @@ -700,7 +701,7 @@ public class ManagerImpl implements Manager { } } else if (recipient instanceof RecipientIdentifier.Group group) { final var result = context.getSendHelper() - .sendAsGroupMessage(messageBuilder, group.groupId(), notifySelf, editTargetTimestamp); + .sendAsGroupMessage(messageBuilder, group.groupId(), notifySelf, editTargetTimestamp, urgent); results.put(recipient, result.stream().map(this::toSendMessageResult).toList()); } } @@ -799,7 +800,7 @@ public class ManagerImpl implements Manager { } final var messageBuilder = SignalServiceDataMessage.newBuilder(); applyMessage(messageBuilder, message); - return sendMessage(messageBuilder, recipients, notifySelf); + return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty(), !message.noUrgent()); } @Override @@ -810,7 +811,7 @@ public class ManagerImpl implements Manager { ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { final var messageBuilder = SignalServiceDataMessage.newBuilder(); applyMessage(messageBuilder, message); - return sendMessage(messageBuilder, recipients, false, Optional.of(editTargetTimestamp)); + return sendMessage(messageBuilder, recipients, false, Optional.of(editTargetTimestamp), !message.noUrgent()); } private void applyMessage( diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index a957a161..81d14d05 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -105,6 +105,10 @@ public class SendCommand implements JsonRpcLocalCommand { subparser.addArgument("--edit-timestamp") .type(long.class) .help("Specify the timestamp of a previous message with the recipient or group to send an edited message."); + subparser.addArgument("--no-urgent") + .action(Arguments.storeTrue()) + .help("Send the message without the urgent flag, so no push notification is triggered for the recipient. " + + "The message will still be delivered in real-time if the recipient's app is active."); } @Override @@ -115,6 +119,7 @@ public class SendCommand implements JsonRpcLocalCommand { ) throws CommandException { final var notifySelf = Boolean.TRUE.equals(ns.getBoolean("notify-self")); final var isNoteToSelf = Boolean.TRUE.equals(ns.getBoolean("note-to-self")); + final var noUrgent = Boolean.TRUE.equals(ns.getBoolean("no-urgent")); final var recipientStrings = ns.getList("recipient"); final var groupIdStrings = ns.getList("group-id"); final var usernameStrings = ns.getList("username"); @@ -247,7 +252,8 @@ public class SendCommand implements JsonRpcLocalCommand { Optional.ofNullable(sticker), previews, Optional.ofNullable((storyReply)), - textStyles); + textStyles, + noUrgent); var results = editTimestamp != null ? m.sendEditMessage(message, recipientIdentifiers, editTimestamp) : m.sendMessage(message, recipientIdentifiers, notifySelf); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 98720728..3da66439 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -242,7 +242,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { Optional.empty(), List.of(), Optional.empty(), - List.of()); + List.of(), + false); final var recipientIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() .map(RecipientIdentifier.class::cast) .collect(Collectors.toSet()); @@ -407,7 +408,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { Optional.empty(), List.of(), Optional.empty(), - List.of()); + List.of(), + false); final var results = m.sendMessage(message, Set.of(RecipientIdentifier.NoteToSelf.INSTANCE), false); checkSendMessageResults(results); return results.timestamp(); @@ -453,7 +455,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { Optional.empty(), List.of(), Optional.empty(), - List.of()); + List.of(), + false); var results = m.sendMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false); checkSendMessageResults(results); return results.timestamp();