Compare commits

...

8 Commits

Author SHA1 Message Date
Brian Exelbierd
63c13adb99
Merge ab7f34b4460aaa4339ad65112b8d315821618943 into 14297986f28ea09166c33b167a98042ab5e90716 2026-01-24 21:31:49 +00:00
Brian (bex) Exelbierd
ab7f34b446 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
- Pass ignoreAvatars as explicit boolean parameter to ProfileHelper,
  SyncHelper, and GroupHelper methods (per maintainer feedback)
- 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
2026-01-24 22:31:29 +01:00
AsamK
14297986f2 Use SequencedCollection instead of List 2026-01-24 17:24:27 +01:00
AsamK
0bd4d554d8 Use virtual threads 2026-01-24 17:24:27 +01:00
AsamK
32c8d4f801 Update to java 25 2026-01-24 15:38:02 +01:00
AsamK
82abc20871 Remove deprecated functionality 2026-01-24 15:37:00 +01:00
AsamK
e8ab01f665 Fix use of deprecated API in plugin 2026-01-24 15:20:55 +01:00
AsamK
dee557a9ad Prepare next release 2026-01-24 15:09:23 +01:00
41 changed files with 344 additions and 340 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '21', '25' ]
java: [ '25' ]
steps:
- uses: actions/checkout@v4
@ -58,9 +58,8 @@ jobs:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
distribution: 'graalvm-community'
version: '21.0.2'
java-version: '21'
distribution: 'graalvm'
java-version: '25'
cache: 'gradle'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 21
java-version: 25
- name: Checkout repository
uses: actions/checkout@v4

View File

@ -1,5 +1,15 @@
# Changelog
## [Unreleased]
**Attention**: Now requires Java 25
### Breaking changes
- Remove isRegistered method without parameters from Signal dbus interface, which always returned `true`
- Remove `sandbox` value for --service-environment parameter, use `staging` instead
- The `daemon` command now requires at least one channel parameter (`--socket`, `--dbus`, ...) and no longer defaults to dbus
## [0.13.23] - 2026-01-24
Requires libsignal-client version 0.86.12.
@ -8,6 +18,8 @@ Requires libsignal-client version 0.86.12.
- Add sendPollCreate, sendPollVote, sendPollTerminate commands for polls
- Add updateDevice command to set device name of linked devices
- Add --ignore-avatars flag to prevent downloading avatars
- Add --ignore-stickers flag to prevent downloading sticker packs
### Changed

View File

@ -1,4 +1,4 @@
FROM docker.io/azul/zulu-openjdk:21-jre-headless
FROM docker.io/azul/zulu-openjdk:25-jre-headless
LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli
LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger."

View File

@ -23,7 +23,7 @@ Windows. There's also a [docker image and some Linux packages](https://github.co
System requirements:
- at least Java Runtime Environment (JRE) 21
- at least Java Runtime Environment (JRE) 25
- native library: libsignal-client
The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other

View File

@ -8,12 +8,12 @@ plugins {
allprojects {
group = "org.asamk"
version = "0.13.23"
version = "0.14.0-SNAPSHOT"
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {
@ -39,7 +39,7 @@ graalvmNative {
if (System.getenv("GRAALVM_HOME") == null) {
toolchainDetection.set(true)
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(25))
})
} else {
toolchainDetection.set(false)

View File

@ -7,11 +7,11 @@ plugins {
}
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
compilerOptions.jvmTarget.set(JvmTarget.JVM_24)
}
java {
targetCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_24
}
repositories {

View File

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
@ -8,7 +6,7 @@ import javax.xml.parsers.DocumentBuilderFactory
class CheckLibVersionsPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("checkLibVersions") {
project.tasks.register("checkLibVersions") {
description =
"Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded."
doLast {
@ -28,7 +26,7 @@ class CheckLibVersionsPlugin : Plugin<Project> {
try {
val dbf = DocumentBuilderFactory.newInstance()
val db = dbf.newDocumentBuilder()
val doc = db.parse(metaDataUrl);
val doc = db.parse(metaDataUrl)
val newest = doc.getElementsByTagName("latest").item(0).textContent
if (version != newest.toString()) {
println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}")

View File

@ -1,26 +1 @@
[
{
"interfaces":["java.sql.Connection"]
},
{
"interfaces":["org.asamk.Signal"]
},
{
"interfaces":["org.asamk.Signal$Configuration"]
},
{
"interfaces":["org.asamk.Signal$Device"]
},
{
"interfaces":["org.asamk.Signal$Group"]
},
{
"interfaces":["org.asamk.Signal$Identity"]
},
{
"interfaces":["org.asamk.SignalControl"]
},
{
"interfaces":["org.freedesktop.dbus.interfaces.DBus"]
}
]
[{"interfaces":["java.sql.Connection"]},{"interfaces":["org.asamk.Signal"]},{"interfaces":["org.asamk.Signal$Configuration"]},{"interfaces":["org.asamk.Signal$Device"]},{"interfaces":["org.asamk.Signal$Group"]},{"interfaces":["org.asamk.Signal$Identity"]},{"interfaces":["org.asamk.SignalControl"]},{"interfaces":["org.freedesktop.dbus.interfaces.DBus"]}]

View File

@ -212,6 +212,17 @@
}
]
},
{
"type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"javax.crypto.KDFParameters"
]
}
]
},
{
"type": "com.sun.crypto.provider.HmacCore$HmacSHA256",
"methods": [
@ -307,6 +318,14 @@
"name": "getCredentials",
"parameterTypes": []
},
{
"name": "getCredentialsProvider",
"parameterTypes": []
},
{
"name": "getCredentialsProviderClassName",
"parameterTypes": []
},
{
"name": "getDataSource",
"parameterTypes": []
@ -495,6 +514,18 @@
"com.zaxxer.hikari.util.Credentials"
]
},
{
"name": "setCredentialsProvider",
"parameterTypes": [
"com.zaxxer.hikari.HikariCredentialsProvider"
]
},
{
"name": "setCredentialsProviderClassName",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setDataSource",
"parameterTypes": [
@ -811,7 +842,13 @@
{
"type": "java.lang.Enum",
"allDeclaredMethods": true,
"jniAccessible": true
"jniAccessible": true,
"methods": [
{
"name": "ordinal",
"parameterTypes": []
}
]
},
{
"type": "java.lang.Float",
@ -931,6 +968,10 @@
{
"name": "getStackTrace",
"parameterTypes": []
},
{
"name": "isVirtual",
"parameterTypes": []
}
]
},
@ -976,6 +1017,9 @@
}
]
},
{
"type": "java.lang.VirtualThread"
},
{
"type": "java.lang.annotation.Retention",
"methods": [
@ -1879,6 +1923,10 @@
"type": "org.asamk.signal.Main",
"jniAccessible": true,
"methods": [
{
"name": "<init>",
"parameterTypes": []
},
{
"name": "main",
"parameterTypes": [
@ -4516,6 +4564,33 @@
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.kdf.HKDF$Mappings",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.kdf.PBEPBKDF2$Mappings",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.kdf.SCRYPT$Mappings",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
"methods": [
@ -4708,6 +4783,15 @@
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.symmetric.HKDF$Mappings",
"methods": [
{
"name": "<init>",
"parameterTypes": []
}
]
},
{
"type": "org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
"methods": [
@ -5146,6 +5230,50 @@
}
]
},
{
"type": "org.signal.core.models.ServiceId",
"allDeclaredFields": true,
"methods": [
{
"name": "<init>",
"parameterTypes": [
"org.signal.libsignal.protocol.ServiceId"
]
},
{
"name": "equals",
"parameterTypes": [
"java.lang.Object"
]
},
{
"name": "hashCode",
"parameterTypes": []
},
{
"name": "logString",
"parameterTypes": []
},
{
"name": "toByteArray",
"parameterTypes": []
},
{
"name": "toByteString",
"parameterTypes": []
},
{
"name": "toProtocolAddress",
"parameterTypes": [
"int"
]
},
{
"name": "toString",
"parameterTypes": []
}
]
},
{
"type": "org.signal.libsignal.internal.CompletableFuture",
"jniAccessible": true,
@ -5214,6 +5342,12 @@
"type": "org.signal.libsignal.net.ChatConnection$ListenerBridge",
"jniAccessible": true,
"methods": [
{
"name": "connectionInterrupted",
"parameterTypes": [
"java.lang.Throwable"
]
},
{
"name": "onConnectionInterrupted",
"parameterTypes": [
@ -5237,6 +5371,24 @@
"parameterTypes": [
"java.lang.String[]"
]
},
{
"name": "receivedAlerts",
"parameterTypes": [
"java.lang.String[]"
]
},
{
"name": "receivedIncomingMessage",
"parameterTypes": [
"byte[]",
"long",
"long"
]
},
{
"name": "receivedQueueEmpty",
"parameterTypes": []
}
]
},
@ -5279,6 +5431,18 @@
}
]
},
{
"type": "org.signal.libsignal.net.ChatServiceInactiveException",
"jniAccessible": true,
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"type": "org.signal.libsignal.net.DeviceDeregisteredException",
"jniAccessible": true,
@ -5315,6 +5479,18 @@
}
]
},
{
"type": "org.signal.libsignal.net.TransportFailureException",
"jniAccessible": true,
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"type": "org.signal.libsignal.net.internal.BridgeChatListener",
"jniAccessible": true
@ -6831,6 +7007,22 @@
}
]
},
{
"type": "org.whispersystems.signalservice.api.link.SetDeviceNameRequest",
"allDeclaredFields": true,
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "getDeviceName",
"parameterTypes": []
}
]
},
{
"type": "org.whispersystems.signalservice.api.messages.calls.HangupMessage",
"allDeclaredFields": true,
@ -6894,7 +7086,7 @@
"allDeclaredFields": true
},
{
"type": "org.signal.core.models.ServiceId",
"type": "org.whispersystems.signalservice.api.push.ServiceId",
"allDeclaredFields": true,
"methods": [
{
@ -9234,4 +9426,4 @@
}
}
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,8 @@ plugins {
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {

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

@ -107,7 +107,10 @@ public class GroupHelper {
return group != null && group.isBlocked();
}
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar) {
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar, boolean ignoreAvatars) {
if (ignoreAvatars) {
return;
}
try {
context.getAvatarStore()
.storeGroupAvatar(groupId,
@ -167,7 +170,7 @@ public class GroupHelper {
storeProfileKeysFromMembers(group);
final var avatar = group.avatar;
if (!avatar.isEmpty()) {
downloadGroupAvatar(groupId, groupSecretParams, avatar);
downloadGroupAvatar(groupId, groupSecretParams, avatar, false);
}
}
groupInfoV2.setGroup(group);
@ -506,13 +509,16 @@ public class GroupHelper {
storeProfileKeysFromMembers(decryptedGroup);
final var avatar = decryptedGroup.avatar;
if (!avatar.isEmpty()) {
downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar);
downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar, false);
}
groupInfoV2.setGroup(decryptedGroup);
account.getGroupStore().updateGroup(group);
}
private void downloadGroupAvatar(GroupIdV2 groupId, GroupSecretParams groupSecretParams, String cdnKey) {
private void downloadGroupAvatar(GroupIdV2 groupId, GroupSecretParams groupSecretParams, String cdnKey, boolean ignoreAvatars) {
if (ignoreAvatars) {
return;
}
try {
context.getAvatarStore()
.storeGroupAvatar(groupId,

View File

@ -369,7 +369,7 @@ public final class IncomingMessageHandler {
false,
senderDeviceAddress,
destination,
receiveConfig.ignoreAttachments()));
receiveConfig));
}
if (content.getStoryMessage().isPresent()) {
@ -382,7 +382,7 @@ public final class IncomingMessageHandler {
actions.addAll(handleSyncMessage(envelope,
syncMessage,
senderDeviceAddress,
receiveConfig.ignoreAttachments()));
receiveConfig));
}
return actions;
@ -475,7 +475,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);
@ -491,12 +491,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()) {
@ -522,7 +522,7 @@ public final class IncomingMessageHandler {
try {
final var groupsMessage = syncMessage.getGroups().get();
context.getAttachmentHelper()
.retrieveAttachment(groupsMessage, context.getSyncHelper()::handleSyncDeviceGroups);
.retrieveAttachment(groupsMessage, input -> context.getSyncHelper().handleSyncDeviceGroups(input, receiveConfig.ignoreAvatars()));
} catch (Exception e) {
logger.warn("Failed to handle received sync groups, ignoring: {}", e.getMessage());
}
@ -550,7 +550,7 @@ public final class IncomingMessageHandler {
final var contactsMessage = syncMessage.getContacts().get();
context.getAttachmentHelper()
.retrieveAttachment(contactsMessage.getContactsStream(),
context.getSyncHelper()::handleSyncDeviceContacts);
input -> context.getSyncHelper().handleSyncDeviceContacts(input, receiveConfig.ignoreAvatars()));
} catch (Exception e) {
logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
}
@ -576,7 +576,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()));
}
}
@ -738,7 +738,7 @@ public final class IncomingMessageHandler {
boolean isSync,
DeviceAddress source,
DeviceAddress destination,
boolean ignoreAttachments
ReceiveConfig receiveConfig
) {
var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) {
@ -757,7 +757,7 @@ public final class IncomingMessageHandler {
if (groupInfo.getAvatar().isPresent()) {
var avatar = groupInfo.getAvatar().get();
context.getGroupHelper().downloadGroupAvatar(groupV1.getGroupId(), avatar);
context.getGroupHelper().downloadGroupAvatar(groupV1.getGroupId(), avatar, receiveConfig.ignoreAvatars());
}
if (groupInfo.getName().isPresent()) {
@ -830,7 +830,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);
@ -878,7 +878,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

@ -275,10 +275,13 @@ public final class ProfileHelper {
private Profile decryptProfileAndDownloadAvatar(
final RecipientId recipientId,
final ProfileKey profileKey,
final SignalServiceProfile encryptedProfile
final SignalServiceProfile encryptedProfile,
final boolean ignoreAvatars
) {
final var avatarPath = encryptedProfile.getAvatar();
downloadProfileAvatar(recipientId, avatarPath, profileKey);
if (!ignoreAvatars) {
downloadProfileAvatar(recipientId, avatarPath, profileKey, ignoreAvatars);
}
return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
}
@ -286,8 +289,12 @@ public final class ProfileHelper {
public void downloadProfileAvatar(
final RecipientId recipientId,
final String avatarPath,
final ProfileKey profileKey
final ProfileKey profileKey,
final boolean ignoreAvatars
) {
if (ignoreAvatars) {
return;
}
var profile = account.getProfileStore().getProfile(recipientId);
if (profile == null || !Objects.equals(avatarPath, profile.getAvatarUrlPath())) {
logger.trace("Downloading profile avatar for {}", recipientId);
@ -341,7 +348,7 @@ public final class ProfileHelper {
Profile newProfile = null;
if (profileKey.isPresent()) {
logger.trace("Decrypting profile");
newProfile = decryptProfileAndDownloadAvatar(recipientId, profileKey.get(), encryptedProfile);
newProfile = decryptProfileAndDownloadAvatar(recipientId, profileKey.get(), encryptedProfile, false);
}
if (newProfile == null) {

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;

View File

@ -293,7 +293,7 @@ public class SyncHelper {
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forConfiguration(configurationMessage));
}
public void handleSyncDeviceGroups(final InputStream input) {
public void handleSyncDeviceGroups(final InputStream input, final boolean ignoreAvatars) {
final var s = new DeviceGroupsInputStream(input);
DeviceGroup g;
while (true) {
@ -327,7 +327,7 @@ public class SyncHelper {
}
if (g.getAvatar().isPresent()) {
context.getGroupHelper().downloadGroupAvatar(syncGroup.getGroupId(), g.getAvatar().get());
context.getGroupHelper().downloadGroupAvatar(syncGroup.getGroupId(), g.getAvatar().get(), ignoreAvatars);
}
syncGroup.archived = g.isArchived();
account.getGroupStore().updateGroup(syncGroup);
@ -335,7 +335,7 @@ public class SyncHelper {
}
}
public void handleSyncDeviceContacts(final InputStream input) throws IOException {
public void handleSyncDeviceContacts(final InputStream input, final boolean ignoreAvatars) throws IOException {
final var s = new DeviceContactsInputStream(input);
DeviceContact c;
while (true) {
@ -381,7 +381,7 @@ public class SyncHelper {
account.getContactStore().storeContact(recipientId, builder.build());
if (c.getAvatar().isPresent()) {
storeContactAvatar(c.getAvatar().get(), address);
storeContactAvatar(c.getAvatar().get(), address, ignoreAvatars);
}
}
}
@ -430,7 +430,10 @@ public class SyncHelper {
streamDetails.getContentType()));
}
private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address) {
private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address, boolean ignoreAvatars) {
if (ignoreAvatars) {
return;
}
try {
context.getAvatarStore()
.storeContactAvatar(address,

View File

@ -20,7 +20,7 @@ public class JobExecutor implements AutoCloseable {
public JobExecutor(final Context context) {
this.context = context;
this.executorService = Executors.newCachedThreadPool();
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
}
public void enqueueJob(Job job) {

View File

@ -157,7 +157,7 @@ public class ManagerImpl implements Manager {
private final SignalDependencies dependencies;
private final Context context;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
private Thread receiveThread;
private boolean isReceivingSynchronous;

View File

@ -18,6 +18,6 @@ public class DownloadProfileAvatarJob implements Job {
logger.trace("Downloading profile avatar {}", avatarPath);
final var account = context.getAccount();
context.getProfileHelper()
.downloadProfileAvatar(account.getSelfRecipientId(), avatarPath, account.getProfileKey());
.downloadProfileAvatar(account.getSelfRecipientId(), avatarPath, account.getProfileKey(), false);
}
}

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

@ -32,7 +32,7 @@ fi
run() {
# To update graalvm config, set GRAALVM_HOME, e.g:
# export GRAALVM_HOME=/usr/lib/jvm/java-21-graalvm
# export GRAALVM_HOME=/usr/lib/jvm/java-25-graalvm
if [ ! -z "$GRAALVM_HOME" ]; then
export JAVA_HOME=$GRAALVM_HOME
export SIGNAL_CLI_OPTS="-agentlib:native-image-agent=config-merge-dir=graalvm-config-dir-${SIGNAL_CLI_AGENT_ID}/"

View File

@ -143,9 +143,6 @@ public interface Signal extends DBusInterface {
String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
@Deprecated
boolean isRegistered() throws Error.Failure, Error.InvalidNumber;
boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber;
List<Boolean> isRegistered(List<String> numbers) throws Error.Failure, Error.InvalidNumber;

View File

@ -13,10 +13,4 @@ public enum ServiceEnvironmentCli {
return "staging";
}
},
@Deprecated SANDBOX {
@Override
public String toString() {
return "sandbox";
}
},
}

View File

@ -3,12 +3,13 @@ package org.asamk.signal.commands;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface Command {
String getName();
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT);
}
}

View File

@ -10,6 +10,7 @@ import org.asamk.signal.ReceiveMessageHandler;
import org.asamk.signal.Shutdown;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusHandler;
import org.asamk.signal.http.HttpServerHandler;
import org.asamk.signal.json.JsonReceiveMessageHandler;
@ -31,6 +32,7 @@ import java.nio.channels.Channel;
import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.SequencedCollection;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -80,13 +82,19 @@ 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());
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
@ -201,9 +209,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
&& tcpAddress == null
&& httpAddress == null
&& inheritedChannel == null) {
logger.warn(
"Running daemon command without explicit mode is deprecated. Use 'daemon --dbus' to use the dbus interface.");
daemonHandler.runDbus(false, DbusConfig.getBusname());
throw new UserErrorException("At least one channel parameter is required, e.g. --socket or --dbus.");
}
}

View File

@ -16,6 +16,7 @@ import org.asamk.signal.output.JsonWriter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonRpcRegistrationCommand<Map<String, Object>> {
@ -54,7 +55,7 @@ public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonR
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface JsonRpcCommand<T> extends Command {
@ -12,7 +13,7 @@ public interface JsonRpcCommand<T> extends Command {
return null;
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}
}

View File

@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
import java.util.List;
import java.util.SequencedCollection;
import java.util.function.Supplier;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -43,6 +44,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());
@ -53,7 +60,7 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Object>>, LocalCommand {
@ -23,7 +24,7 @@ public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Ob
handleCommand(commandNamespace, m, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String, Object>>, MultiLocalCommand {
@ -27,7 +28,7 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String
handleCommand(commandNamespace, c, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -27,6 +27,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.SequencedCollection;
public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<ReceiveCommand.ReceiveParams> {
@ -54,13 +55,19 @@ 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());
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
@ -75,8 +82,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

@ -21,6 +21,7 @@ import org.asamk.signal.util.CommandUtil;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class RegisterCommand implements RegistrationCommand, JsonRpcRegistrationCommand<RegisterCommand.RegistrationParams> {
@ -57,7 +58,7 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCommand<VerifyCommand.VerifyParams> {
@ -50,7 +51,7 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -16,6 +16,7 @@ import org.asamk.signal.output.PlainTextWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public class VersionCommand implements JsonRpcLocalCommand, JsonRpcMultiLocalCommand {
@ -25,7 +26,7 @@ public class VersionCommand implements JsonRpcLocalCommand, JsonRpcMultiLocalCom
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -675,12 +675,6 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
}
}
@Override
@Deprecated
public boolean isRegistered() {
return true;
}
@Override
public boolean isRegistered(String number) {
var result = isRegistered(List.of(number));

View File

@ -59,7 +59,7 @@ public class HttpServerHandler implements AutoCloseable {
logger.debug("Starting HTTP server on {}", address);
server = HttpServer.create(address, 0);
server.setExecutor(Executors.newCachedThreadPool());
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/api/v1/rpc", this::handleRpcEndpoint);
server.createContext("/api/v1/events", this::handleEventsEndpoint);

View File

@ -55,7 +55,7 @@ public class JsonRpcReader {
return;
}
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
while (!Thread.interrupted()) {
final var input = lineSupplier.get();
if (input == null) {
@ -91,7 +91,7 @@ public class JsonRpcReader {
case JsonRpcBatchMessage jsonRpcBatchMessage -> {
final var messages = jsonRpcBatchMessage.getMessages();
final var responseList = new ArrayList<JsonRpcResponse>(messages.size());
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
final var lock = new ReentrantLock();
messages.forEach(jsonNode -> {
final JsonRpcRequest request;

View File

@ -63,7 +63,7 @@ public class SocketHandler implements AutoCloseable {
logger.debug("Starting JSON-RPC server on {}", address);
listenerThread = Thread.ofPlatform().name("daemon-listener").start(() -> {
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
logger.info("Started JSON-RPC server on {}", address);
while (true) {
final var connectionId = threadNumber.getAndIncrement();

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);
}
}