Add --ignore-avatars and --ignore-stickers CLI flags

Implement two new CLI flags to disable downloading avatars and sticker
packs during message reception, following the existing pattern of
--ignore-attachments and --ignore-stories flags.

Changes:
- Add --ignore-avatars and --ignore-stickers flags to ReceiveCommand,
  DaemonCommand, and JsonRpcDispatcherCommand
- Extend ReceiveConfig record with ignoreAvatars and ignoreStickers
  fields
- Add ReceiveHelper.getReceiveConfig() getter to expose config to
  other helpers
- Gate avatar downloads in ProfileHelper (profile avatars), SyncHelper
  (contact avatars), and GroupHelper (group avatars for V1 and V2)
- Gate sticker pack downloads in IncomingMessageHandler for both
  direct sticker messages and sync sticker pack operations
- Update handleSignalServiceDataMessage and handleSyncMessage to pass
  full ReceiveConfig instead of individual boolean flags
- Update man page (signal-cli.1.adoc) with flag documentation
- Add entries to CHANGELOG.md

When these flags are set, the respective content is not downloaded
during message reception. Metadata (avatar paths, sticker pack IDs)
is still stored, and existing FileNotFoundException handling will
surface if content is later requested but wasn't downloaded.

Fixes #1903
This commit is contained in:
Brian (bex) Exelbierd 2026-01-18 22:58:17 +01:00
parent 3af9dff0ed
commit 6a12e3d0af
12 changed files with 75 additions and 16 deletions

View File

@ -2,6 +2,11 @@
## [Unreleased]
### Added
- Add --ignore-avatars flag to prevent downloading avatars
- Add --ignore-stickers flag to prevent downloading sticker packs
## [0.13.22] - 2025-11-14
Requires libsignal-client version 0.86.1.

View File

@ -1,3 +1,3 @@
package org.asamk.signal.manager.api;
public record ReceiveConfig(boolean ignoreAttachments, boolean ignoreStories, boolean sendReadReceipts) {}
public record ReceiveConfig(boolean ignoreAttachments, boolean ignoreStories, boolean ignoreAvatars, boolean ignoreStickers, boolean sendReadReceipts) {}

View File

@ -108,6 +108,9 @@ public class GroupHelper {
}
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar) {
if (context.getReceiveHelper().getReceiveConfig().ignoreAvatars()) {
return;
}
try {
context.getAvatarStore()
.storeGroupAvatar(groupId,
@ -505,7 +508,7 @@ public class GroupHelper {
}
storeProfileKeysFromMembers(decryptedGroup);
final var avatar = decryptedGroup.avatar;
if (!avatar.isEmpty()) {
if (!avatar.isEmpty() && !context.getReceiveHelper().getReceiveConfig().ignoreAvatars()) {
downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar);
}
groupInfoV2.setGroup(decryptedGroup);

View File

@ -368,7 +368,7 @@ public final class IncomingMessageHandler {
false,
senderDeviceAddress,
destination,
receiveConfig.ignoreAttachments()));
receiveConfig));
}
if (content.getStoryMessage().isPresent()) {
@ -381,7 +381,7 @@ public final class IncomingMessageHandler {
actions.addAll(handleSyncMessage(envelope,
syncMessage,
senderDeviceAddress,
receiveConfig.ignoreAttachments()));
receiveConfig));
}
return actions;
@ -474,7 +474,7 @@ public final class IncomingMessageHandler {
final SignalServiceEnvelope envelope,
final SignalServiceSyncMessage syncMessage,
final DeviceAddress sender,
final boolean ignoreAttachments
final ReceiveConfig receiveConfig
) {
var actions = new ArrayList<HandleAction>();
account.setMultiDevice(true);
@ -490,12 +490,12 @@ public final class IncomingMessageHandler {
: new DeviceAddress(account.getRecipientResolver().resolveRecipient(destination),
destination.getServiceId(),
0),
ignoreAttachments));
receiveConfig));
}
if (message.getStoryMessage().isPresent()) {
actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
sender.recipientId(),
ignoreAttachments));
receiveConfig.ignoreAttachments()));
}
}
if (syncMessage.getRequest().isPresent() && account.isPrimaryDevice()) {
@ -575,7 +575,7 @@ public final class IncomingMessageHandler {
final var sticker = context.getStickerHelper()
.addOrUpdateStickerPack(stickerPackId, stickerPackKey, installed);
if (sticker != null && installed) {
if (sticker != null && installed && !receiveConfig.ignoreStickers()) {
context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, sticker.packKey()));
}
}
@ -731,7 +731,7 @@ public final class IncomingMessageHandler {
boolean isSync,
DeviceAddress source,
DeviceAddress destination,
boolean ignoreAttachments
ReceiveConfig receiveConfig
) {
var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) {
@ -823,7 +823,7 @@ public final class IncomingMessageHandler {
message.getExpireTimerVersion());
}
}
if (!ignoreAttachments) {
if (!receiveConfig.ignoreAttachments()) {
if (message.getAttachments().isPresent()) {
for (var attachment : message.getAttachments().get()) {
context.getAttachmentHelper().downloadAttachment(attachment);
@ -871,7 +871,9 @@ public final class IncomingMessageHandler {
sticker = new StickerPack(stickerPackId, messageSticker.getPackKey());
account.getStickerStore().addStickerPack(sticker);
}
context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
if (!receiveConfig.ignoreStickers()) {
context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
}
}
return actions;
}

View File

@ -278,7 +278,9 @@ public final class ProfileHelper {
final SignalServiceProfile encryptedProfile
) {
final var avatarPath = encryptedProfile.getAvatar();
downloadProfileAvatar(recipientId, avatarPath, profileKey);
if (!context.getReceiveHelper().getReceiveConfig().ignoreAvatars()) {
downloadProfileAvatar(recipientId, avatarPath, profileKey);
}
return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
}
@ -288,6 +290,9 @@ public final class ProfileHelper {
final String avatarPath,
final ProfileKey profileKey
) {
if (context.getReceiveHelper().getReceiveConfig().ignoreAvatars()) {
return;
}
var profile = account.getProfileStore().getProfile(recipientId);
if (profile == null || !Objects.equals(avatarPath, profile.getAvatarUrlPath())) {
logger.trace("Downloading profile avatar for {}", recipientId);

View File

@ -40,7 +40,7 @@ public class ReceiveHelper {
private final SignalDependencies dependencies;
private final Context context;
private ReceiveConfig receiveConfig = new ReceiveConfig(false, false, false);
private ReceiveConfig receiveConfig = new ReceiveConfig(false, false, false, false, false);
private boolean hasCaughtUpWithOldMessages = false;
private boolean isWaitingForMessage = false;
private boolean shouldStop = false;
@ -58,6 +58,10 @@ public class ReceiveHelper {
dependencies.setAllowStories(!receiveConfig.ignoreStories());
}
public ReceiveConfig getReceiveConfig() {
return receiveConfig;
}
public void setAuthenticationFailureListener(final Callable authenticationFailureListener) {
this.authenticationFailureListener = authenticationFailureListener;
}

View File

@ -378,7 +378,7 @@ public class SyncHelper {
}
account.getContactStore().storeContact(recipientId, builder.build());
if (c.getAvatar().isPresent()) {
if (c.getAvatar().isPresent() && !context.getReceiveHelper().getReceiveConfig().ignoreAvatars()) {
storeContactAvatar(c.getAvatar().get(), address);
}
}

View File

@ -587,6 +587,12 @@ Dont download attachments of received messages.
*--ignore-stories*::
Dont receive story messages from the server.
*--ignore-avatars*::
Don't download avatars of received messages.
*--ignore-stickers*::
Don't download sticker packs of received messages.
*--send-read-receipts*::
Send read receipts for all incoming data messages (in addition to the default delivery receipts)
@ -950,6 +956,12 @@ Dont download attachments of received messages.
*--ignore-stories*::
Dont receive story messages from the server.
*--ignore-avatars*::
Don't download avatars of received messages.
*--ignore-stickers*::
Don't download sticker packs of received messages.
*--send-read-receipts*::
Send read receipts for all incoming data messages (in addition to the default delivery receipts)
@ -971,6 +983,12 @@ Dont download attachments of received messages.
*--ignore-stories*::
Dont receive story messages from the server.
*--ignore-avatars*::
Don't download avatars of received messages.
*--ignore-stickers*::
Don't download sticker packs of received messages.
*--send-read-receipts*::
Send read receipts for all incoming data messages (in addition to the default delivery receipts)

View File

@ -80,6 +80,12 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
subparser.addArgument("--ignore-stories")
.help("Dont receive story messages from the server.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-avatars")
.help("Don't download avatars of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-stickers")
.help("Don't download sticker packs of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--send-read-receipts")
.help("Send read receipts for all incoming data messages (in addition to the default delivery receipts)")
.action(Arguments.storeTrue());

View File

@ -43,6 +43,12 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand
subparser.addArgument("--ignore-stories")
.help("Dont receive story messages from the server.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-avatars")
.help("Don't download avatars of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-stickers")
.help("Don't download sticker packs of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--send-read-receipts")
.help("Send read receipts for all incoming data messages (in addition to the default delivery receipts)")
.action(Arguments.storeTrue());

View File

@ -54,6 +54,12 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<Receiv
subparser.addArgument("--ignore-stories")
.help("Dont receive story messages from the server.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-avatars")
.help("Don't download avatars of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--ignore-stickers")
.help("Don't download sticker packs of received messages.")
.action(Arguments.storeTrue());
subparser.addArgument("--send-read-receipts")
.help("Send read receipts for all incoming data messages (in addition to the default delivery receipts)")
.action(Arguments.storeTrue());
@ -75,8 +81,10 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<Receiv
final var maxMessagesRaw = ns.getInt("max-messages");
final var ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
final var ignoreStories = Boolean.TRUE.equals(ns.getBoolean("ignore-stories"));
final var ignoreAvatars = Boolean.TRUE.equals(ns.getBoolean("ignore-avatars"));
final var ignoreStickers = Boolean.TRUE.equals(ns.getBoolean("ignore-stickers"));
final var sendReadReceipts = Boolean.TRUE.equals(ns.getBoolean("send-read-receipts"));
m.setReceiveConfig(new ReceiveConfig(ignoreAttachments, ignoreStories, sendReadReceipts));
m.setReceiveConfig(new ReceiveConfig(ignoreAttachments, ignoreStories, ignoreAvatars, ignoreStickers, sendReadReceipts));
try {
final var handler = switch (outputWriter) {
case JsonWriter writer -> new JsonReceiveMessageHandler(m, writer);

View File

@ -146,8 +146,10 @@ public class CommandUtil {
public static ReceiveConfig getReceiveConfig(final Namespace ns) {
final var ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
final var ignoreStories = Boolean.TRUE.equals(ns.getBoolean("ignore-stories"));
final var ignoreAvatars = Boolean.TRUE.equals(ns.getBoolean("ignore-avatars"));
final var ignoreStickers = Boolean.TRUE.equals(ns.getBoolean("ignore-stickers"));
final var sendReadReceipts = Boolean.TRUE.equals(ns.getBoolean("send-read-receipts"));
return new ReceiveConfig(ignoreAttachments, ignoreStories, sendReadReceipts);
return new ReceiveConfig(ignoreAttachments, ignoreStories, ignoreAvatars, ignoreStickers, sendReadReceipts);
}
}