mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-08-25 05:26:03 +00:00
Refactor message receive
This commit is contained in:
parent
fefca7d837
commit
4d1d28672d
14
CHANGELOG.md
14
CHANGELOG.md
@ -8,7 +8,13 @@
|
||||
|
||||
- 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
|
||||
- The `daemon` command now requires at least one channel parameter (`--socket`, `--dbus`, ...) and no longer defaults to
|
||||
dbus
|
||||
|
||||
### Added
|
||||
|
||||
- Add --ignore-avatars flag to prevent downloading avatars
|
||||
- Add --ignore-stickers flag to prevent downloading sticker packs
|
||||
|
||||
## [0.13.24] - 2026-02-05
|
||||
|
||||
@ -27,8 +33,6 @@ 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
|
||||
|
||||
@ -158,9 +162,11 @@ Requires libsignal-client version 0.68.1.
|
||||
Requires libsignal-client version 0.66.2.
|
||||
|
||||
### Added
|
||||
|
||||
- Allow setting nickname and note with `updateContact` command
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix syncing nickname, note and expiration timer
|
||||
- Fix check for registered users with a proxy
|
||||
- Improve handling of storage records not yet supported by signal-cli
|
||||
@ -186,6 +192,7 @@ Requires libsignal-client version 0.65.2.
|
||||
Requires libsignal-client version 0.64.0.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix issue with receiving messages that have an invalid destination
|
||||
|
||||
## [0.13.10] - 2024-11-30
|
||||
@ -198,6 +205,7 @@ Requires libsignal-client version 0.62.0.
|
||||
- Fix receiving expiration timer updates
|
||||
|
||||
### Improved
|
||||
|
||||
- Add support for new storage encryption scheme
|
||||
|
||||
## [0.13.9] - 2024-10-28
|
||||
|
||||
@ -107,10 +107,7 @@ public class GroupHelper {
|
||||
return group != null && group.isBlocked();
|
||||
}
|
||||
|
||||
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar, boolean ignoreAvatars) {
|
||||
if (ignoreAvatars) {
|
||||
return;
|
||||
}
|
||||
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar) {
|
||||
try {
|
||||
context.getAvatarStore()
|
||||
.storeGroupAvatar(groupId,
|
||||
@ -133,7 +130,8 @@ public class GroupHelper {
|
||||
public GroupInfoV2 getOrMigrateGroup(
|
||||
final GroupMasterKey groupMasterKey,
|
||||
final int revision,
|
||||
final byte[] signedGroupChange
|
||||
final byte[] signedGroupChange,
|
||||
final boolean ignoreAvatars
|
||||
) {
|
||||
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
|
||||
|
||||
@ -169,8 +167,8 @@ public class GroupHelper {
|
||||
if (group != null) {
|
||||
storeProfileKeysFromMembers(group);
|
||||
final var avatar = group.avatar;
|
||||
if (!avatar.isEmpty()) {
|
||||
downloadGroupAvatar(groupId, groupSecretParams, avatar, false);
|
||||
if (!avatar.isEmpty() && !ignoreAvatars) {
|
||||
downloadGroupAvatar(groupId, groupSecretParams, avatar);
|
||||
}
|
||||
}
|
||||
groupInfoV2.setGroup(group);
|
||||
@ -394,7 +392,8 @@ public class GroupHelper {
|
||||
.joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo);
|
||||
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
|
||||
groupJoinInfo.revision + 1,
|
||||
changeResponse.group_change == null ? null : changeResponse.group_change.encode());
|
||||
changeResponse.group_change == null ? null : changeResponse.group_change.encode(),
|
||||
false);
|
||||
|
||||
if (group.getGroup() == null) {
|
||||
// Only requested member, can't send update to group members
|
||||
@ -509,16 +508,13 @@ public class GroupHelper {
|
||||
storeProfileKeysFromMembers(decryptedGroup);
|
||||
final var avatar = decryptedGroup.avatar;
|
||||
if (!avatar.isEmpty()) {
|
||||
downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar, false);
|
||||
downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar);
|
||||
}
|
||||
groupInfoV2.setGroup(decryptedGroup);
|
||||
account.getGroupStore().updateGroup(group);
|
||||
}
|
||||
|
||||
private void downloadGroupAvatar(GroupIdV2 groupId, GroupSecretParams groupSecretParams, String cdnKey, boolean ignoreAvatars) {
|
||||
if (ignoreAvatars) {
|
||||
return;
|
||||
}
|
||||
private void downloadGroupAvatar(GroupIdV2 groupId, GroupSecretParams groupSecretParams, String cdnKey) {
|
||||
try {
|
||||
context.getAvatarStore()
|
||||
.storeGroupAvatar(groupId,
|
||||
|
||||
@ -257,7 +257,7 @@ public final class IncomingMessageHandler {
|
||||
var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content);
|
||||
final var groupContext = getGroupContext(content);
|
||||
if (groupContext != null && groupContext.getGroupV2().isPresent()) {
|
||||
handleGroupV2Context(groupContext.getGroupV2().get());
|
||||
handleGroupV2Context(groupContext.getGroupV2().get(), receiveConfig.ignoreAvatars());
|
||||
}
|
||||
// Check again in case the user just joined the group
|
||||
notAllowedToSendToGroup = notAllowedToSendToGroup && isNotAllowedToSendToGroup(envelope, content);
|
||||
@ -377,15 +377,12 @@ public final class IncomingMessageHandler {
|
||||
|
||||
if (content.getStoryMessage().isPresent()) {
|
||||
final var message = content.getStoryMessage().get();
|
||||
actions.addAll(handleSignalServiceStoryMessage(message, sender, receiveConfig.ignoreAttachments()));
|
||||
actions.addAll(handleSignalServiceStoryMessage(message, sender, receiveConfig));
|
||||
}
|
||||
|
||||
if (content.getSyncMessage().isPresent()) {
|
||||
var syncMessage = content.getSyncMessage().get();
|
||||
actions.addAll(handleSyncMessage(envelope,
|
||||
syncMessage,
|
||||
senderDeviceAddress,
|
||||
receiveConfig));
|
||||
actions.addAll(handleSyncMessage(envelope, syncMessage, senderDeviceAddress, receiveConfig));
|
||||
}
|
||||
|
||||
return actions;
|
||||
@ -499,7 +496,7 @@ public final class IncomingMessageHandler {
|
||||
if (message.getStoryMessage().isPresent()) {
|
||||
actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
|
||||
sender.recipientId(),
|
||||
receiveConfig.ignoreAttachments()));
|
||||
receiveConfig));
|
||||
}
|
||||
}
|
||||
if (syncMessage.getRequest().isPresent() && account.isPrimaryDevice()) {
|
||||
@ -525,7 +522,9 @@ public final class IncomingMessageHandler {
|
||||
try {
|
||||
final var groupsMessage = syncMessage.getGroups().get();
|
||||
context.getAttachmentHelper()
|
||||
.retrieveAttachment(groupsMessage, input -> context.getSyncHelper().handleSyncDeviceGroups(input, receiveConfig.ignoreAvatars()));
|
||||
.retrieveAttachment(groupsMessage,
|
||||
input -> context.getSyncHelper()
|
||||
.handleSyncDeviceGroups(input, receiveConfig.ignoreAvatars()));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to handle received sync groups, ignoring: {}", e.getMessage());
|
||||
}
|
||||
@ -553,7 +552,8 @@ public final class IncomingMessageHandler {
|
||||
final var contactsMessage = syncMessage.getContacts().get();
|
||||
context.getAttachmentHelper()
|
||||
.retrieveAttachment(contactsMessage.getContactsStream(),
|
||||
input -> context.getSyncHelper().handleSyncDeviceContacts(input, receiveConfig.ignoreAvatars()));
|
||||
input -> context.getSyncHelper()
|
||||
.handleSyncDeviceContacts(input, receiveConfig.ignoreAvatars()));
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
|
||||
}
|
||||
@ -758,9 +758,9 @@ public final class IncomingMessageHandler {
|
||||
groupV1 = new GroupInfoV1(groupId);
|
||||
}
|
||||
|
||||
if (groupInfo.getAvatar().isPresent()) {
|
||||
if (groupInfo.getAvatar().isPresent() && !receiveConfig.ignoreAvatars()) {
|
||||
var avatar = groupInfo.getAvatar().get();
|
||||
context.getGroupHelper().downloadGroupAvatar(groupV1.getGroupId(), avatar, receiveConfig.ignoreAvatars());
|
||||
context.getGroupHelper().downloadGroupAvatar(groupV1.getGroupId(), avatar);
|
||||
}
|
||||
|
||||
if (groupInfo.getName().isPresent()) {
|
||||
@ -800,7 +800,7 @@ public final class IncomingMessageHandler {
|
||||
}
|
||||
}
|
||||
if (groupContext.getGroupV2().isPresent()) {
|
||||
handleGroupV2Context(groupContext.getGroupV2().get());
|
||||
handleGroupV2Context(groupContext.getGroupV2().get(), receiveConfig.ignoreAvatars());
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,7 +882,8 @@ public final class IncomingMessageHandler {
|
||||
account.getStickerStore().addStickerPack(sticker);
|
||||
}
|
||||
if (!receiveConfig.ignoreStickers()) {
|
||||
context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
|
||||
context.getJobExecutor()
|
||||
.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
@ -895,14 +896,14 @@ public final class IncomingMessageHandler {
|
||||
private List<HandleAction> handleSignalServiceStoryMessage(
|
||||
SignalServiceStoryMessage message,
|
||||
RecipientId source,
|
||||
boolean ignoreAttachments
|
||||
ReceiveConfig receiveConfig
|
||||
) {
|
||||
var actions = new ArrayList<HandleAction>();
|
||||
if (message.getGroupContext().isPresent()) {
|
||||
handleGroupV2Context(message.getGroupContext().get());
|
||||
handleGroupV2Context(message.getGroupContext().get(), receiveConfig.ignoreAvatars());
|
||||
}
|
||||
|
||||
if (!ignoreAttachments) {
|
||||
if (!receiveConfig.ignoreAttachments()) {
|
||||
if (message.getFileAttachment().isPresent()) {
|
||||
context.getAttachmentHelper().downloadAttachment(message.getFileAttachment().get());
|
||||
}
|
||||
@ -924,13 +925,14 @@ public final class IncomingMessageHandler {
|
||||
return actions;
|
||||
}
|
||||
|
||||
private void handleGroupV2Context(final SignalServiceGroupV2 groupContext) {
|
||||
private void handleGroupV2Context(final SignalServiceGroupV2 groupContext, final boolean ignoreAvatars) {
|
||||
final var groupMasterKey = groupContext.getMasterKey();
|
||||
|
||||
context.getGroupHelper()
|
||||
.getOrMigrateGroup(groupMasterKey,
|
||||
groupContext.getRevision(),
|
||||
groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
|
||||
groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null,
|
||||
ignoreAvatars);
|
||||
}
|
||||
|
||||
private void handleIncomingProfileKey(final byte[] profileKeyBytes, final RecipientId source) {
|
||||
|
||||
@ -116,7 +116,8 @@ public final class ProfileHelper {
|
||||
.filter(recipientId -> !ExpiringProfileCredentialUtil.isValid(account.getProfileStore()
|
||||
.getExpiringProfileKeyCredential(recipientId)))
|
||||
.map(recipientId -> retrieveProfile(recipientId,
|
||||
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL).onErrorComplete());
|
||||
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL,
|
||||
false).onErrorComplete());
|
||||
Maybe.merge(profileFetches, 10).blockingSubscribe();
|
||||
|
||||
return recipientIds.stream().map(r -> account.getProfileStore().getExpiringProfileKeyCredential(r)).toList();
|
||||
@ -129,7 +130,9 @@ public final class ProfileHelper {
|
||||
}
|
||||
|
||||
try {
|
||||
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL));
|
||||
blockingGetProfile(retrieveProfile(recipientId,
|
||||
SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL,
|
||||
false));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to retrieve profile key credential, ignoring: {}", e.getMessage());
|
||||
return null;
|
||||
@ -241,7 +244,8 @@ public final class ProfileHelper {
|
||||
final var profileFetches = Flowable.fromIterable(recipientIds)
|
||||
.filter(recipientId -> force || isProfileRefreshRequired(profileStore.getProfile(recipientId)))
|
||||
.map(recipientId -> retrieveProfile(recipientId,
|
||||
SignalServiceProfile.RequestType.PROFILE).onErrorComplete());
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
false).onErrorComplete());
|
||||
Maybe.merge(profileFetches, 10).blockingSubscribe();
|
||||
|
||||
return recipientIds.stream().map(profileStore::getProfile).toList();
|
||||
@ -255,7 +259,7 @@ public final class ProfileHelper {
|
||||
}
|
||||
|
||||
try {
|
||||
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE));
|
||||
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE, false));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to retrieve profile, ignoring: {}", e.getMessage());
|
||||
}
|
||||
@ -272,29 +276,11 @@ public final class ProfileHelper {
|
||||
return now - profile.getLastUpdateTimestamp() >= 6 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
private Profile decryptProfileAndDownloadAvatar(
|
||||
final RecipientId recipientId,
|
||||
final ProfileKey profileKey,
|
||||
final SignalServiceProfile encryptedProfile,
|
||||
final boolean ignoreAvatars
|
||||
) {
|
||||
final var avatarPath = encryptedProfile.getAvatar();
|
||||
if (!ignoreAvatars) {
|
||||
downloadProfileAvatar(recipientId, avatarPath, profileKey, ignoreAvatars);
|
||||
}
|
||||
|
||||
return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
|
||||
}
|
||||
|
||||
public void downloadProfileAvatar(
|
||||
final RecipientId recipientId,
|
||||
final String avatarPath,
|
||||
final ProfileKey profileKey,
|
||||
final boolean ignoreAvatars
|
||||
final ProfileKey profileKey
|
||||
) {
|
||||
if (ignoreAvatars) {
|
||||
return;
|
||||
}
|
||||
var profile = account.getProfileStore().getProfile(recipientId);
|
||||
if (profile == null || !Objects.equals(avatarPath, profile.getAvatarUrlPath())) {
|
||||
logger.trace("Downloading profile avatar for {}", recipientId);
|
||||
@ -322,7 +308,8 @@ public final class ProfileHelper {
|
||||
|
||||
private Single<ProfileAndCredential> retrieveProfile(
|
||||
RecipientId recipientId,
|
||||
SignalServiceProfile.RequestType requestType
|
||||
SignalServiceProfile.RequestType requestType,
|
||||
final boolean ignoreAvatars
|
||||
) {
|
||||
var unidentifiedAccess = getUnidentifiedAccess(recipientId);
|
||||
var profileKey = Optional.ofNullable(account.getProfileStore().getProfileKey(recipientId));
|
||||
@ -348,7 +335,12 @@ public final class ProfileHelper {
|
||||
Profile newProfile = null;
|
||||
if (profileKey.isPresent()) {
|
||||
logger.trace("Decrypting profile");
|
||||
newProfile = decryptProfileAndDownloadAvatar(recipientId, profileKey.get(), encryptedProfile, false);
|
||||
final var avatarPath = encryptedProfile.getAvatar();
|
||||
if (!ignoreAvatars) {
|
||||
downloadProfileAvatar(recipientId, avatarPath, profileKey.get());
|
||||
}
|
||||
|
||||
newProfile = ProfileUtils.decryptProfile(profileKey.get(), encryptedProfile);
|
||||
}
|
||||
|
||||
if (newProfile == null) {
|
||||
|
||||
@ -326,8 +326,8 @@ public class SyncHelper {
|
||||
syncGroup.color = g.getColor().get();
|
||||
}
|
||||
|
||||
if (g.getAvatar().isPresent()) {
|
||||
context.getGroupHelper().downloadGroupAvatar(syncGroup.getGroupId(), g.getAvatar().get(), ignoreAvatars);
|
||||
if (g.getAvatar().isPresent() && !ignoreAvatars) {
|
||||
context.getGroupHelper().downloadGroupAvatar(syncGroup.getGroupId(), g.getAvatar().get());
|
||||
}
|
||||
syncGroup.archived = g.isArchived();
|
||||
account.getGroupStore().updateGroup(syncGroup);
|
||||
@ -381,7 +381,11 @@ public class SyncHelper {
|
||||
account.getContactStore().storeContact(recipientId, builder.build());
|
||||
|
||||
if (c.getAvatar().isPresent()) {
|
||||
storeContactAvatar(c.getAvatar().get(), address, ignoreAvatars);
|
||||
if (!ignoreAvatars) {
|
||||
storeContactAvatar(c.getAvatar().get(), address);
|
||||
} else {
|
||||
IOUtils.discardStream(c.getAvatar().get().getInputStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -430,10 +434,7 @@ public class SyncHelper {
|
||||
streamDetails.getContentType()));
|
||||
}
|
||||
|
||||
private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address, boolean ignoreAvatars) {
|
||||
if (ignoreAvatars) {
|
||||
return;
|
||||
}
|
||||
private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address) {
|
||||
try {
|
||||
context.getAvatarStore()
|
||||
.storeContactAvatar(address,
|
||||
|
||||
@ -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(), false);
|
||||
.downloadProfileAvatar(account.getSelfRecipientId(), avatarPath, account.getProfileKey());
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,14 @@ public class IOUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and discard all data from the input stream.
|
||||
* This is useful to ensure that all data is read and any resources are released, without needing to store the data in memory.
|
||||
*/
|
||||
public static void discardStream(InputStream input) throws IOException {
|
||||
copyStream(input, OutputStream.nullOutputStream());
|
||||
}
|
||||
|
||||
public static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
copyStream(input, output, 4096);
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.json.JsonReceiveMessageHandler;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.AlreadyReceivingException;
|
||||
import org.asamk.signal.manager.api.ReceiveConfig;
|
||||
import org.asamk.signal.output.JsonWriter;
|
||||
import org.asamk.signal.output.OutputWriter;
|
||||
import org.asamk.signal.output.PlainTextWriter;
|
||||
@ -29,6 +28,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.SequencedCollection;
|
||||
|
||||
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
|
||||
|
||||
public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<ReceiveCommand.ReceiveParams> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReceiveCommand.class);
|
||||
@ -80,12 +81,7 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<Receiv
|
||||
Shutdown.installHandler();
|
||||
final var timeout = ns.getDouble("timeout");
|
||||
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, ignoreAvatars, ignoreStickers, sendReadReceipts));
|
||||
m.setReceiveConfig(getReceiveConfig(ns));
|
||||
try {
|
||||
final var handler = switch (outputWriter) {
|
||||
case JsonWriter writer -> new JsonReceiveMessageHandler(m, writer);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user