import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.GroupId;
-import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.MessageEnvelope.Sync.MessageRequestResponse;
import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.StickerPack;
-import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.MimeUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
-import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
+import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
+import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactAvatar;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContactsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage;
import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
+import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SyncMessage;
.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST));
}
+ public void sendSyncReceiptMessage(ServiceId sender, SignalServiceReceiptMessage receiptMessage) {
+ if (receiptMessage.isReadReceipt()) {
+ final var readMessages = receiptMessage.getTimestamps()
+ .stream()
+ .map(t -> new ReadMessage(sender, t))
+ .toList();
+ context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages));
+ } else if (receiptMessage.isViewedReceipt()) {
+ final var viewedMessages = receiptMessage.getTimestamps()
+ .stream()
+ .map(t -> new ViewedMessage(sender, t))
+ .toList();
+ context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages));
+ }
+ }
+
public void sendGroups() throws IOException {
var groupsFile = IOUtils.createTempFile();
if (groupsFile.exists() && groupsFile.length() > 0) {
try (var groupsFileStream = new FileInputStream(groupsFile)) {
+ final var uploadSpec = context.getDependencies().getMessageSender().getResumableUploadSpec();
var attachmentStream = SignalServiceAttachment.newStreamBuilder()
.withStream(groupsFileStream)
.withContentType(MimeUtils.OCTET_STREAM)
.withLength(groupsFile.length())
+ .withResumableUploadSpec(uploadSpec)
.build();
context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream));
final var contact = contactPair.second();
final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
- out.write(getDeviceContact(address, recipientId, contact));
+ final var deviceContact = getDeviceContact(address, recipientId, contact);
+ out.write(deviceContact);
+ deviceContact.getAvatar().ifPresent(a -> {
+ try {
+ a.getInputStream().close();
+ } catch (IOException ignored) {
+ }
+ });
}
if (account.getProfileKey() != null) {
final var address = account.getSelfRecipientAddress();
final var recipientId = account.getSelfRecipientId();
final var contact = account.getContactStore().getContact(recipientId);
- out.write(getDeviceContact(address, recipientId, contact));
+ final var deviceContact = getDeviceContact(address, recipientId, contact);
+ out.write(deviceContact);
+ deviceContact.getAvatar().ifPresent(a -> {
+ try {
+ a.getInputStream().close();
+ } catch (IOException ignored) {
+ }
+ });
}
}
if (contactsFile.exists() && contactsFile.length() > 0) {
try (var contactsFileStream = new FileInputStream(contactsFile)) {
+ final var uploadSpec = context.getDependencies().getMessageSender().getResumableUploadSpec();
var attachmentStream = SignalServiceAttachment.newStreamBuilder()
.withStream(contactsFileStream)
.withContentType(MimeUtils.OCTET_STREAM)
.withLength(contactsFile.length())
+ .withResumableUploadSpec(uploadSpec)
.build();
context.getSendHelper()
@NotNull
private DeviceContact getDeviceContact(
- final RecipientAddress address, final RecipientId recipientId, final Contact contact
+ final RecipientAddress address,
+ final RecipientId recipientId,
+ final Contact contact
) throws IOException {
var currentIdentity = address.serviceId().isEmpty()
? null
Optional.ofNullable(contact == null ? null : contact.color()),
Optional.ofNullable(verifiedMessage),
Optional.ofNullable(profileKey),
- contact != null && contact.isBlocked(),
Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()),
+ Optional.ofNullable(contact == null ? null : contact.messageExpirationTimeVersion()),
Optional.empty(),
contact != null && contact.isArchived());
}
public SendMessageResult sendBlockedList() {
- var addresses = new ArrayList<SignalServiceAddress>();
+ var addresses = new ArrayList<BlockedListMessage.Individual>();
for (var record : account.getContactStore().getContacts()) {
if (record.second().isBlocked()) {
- addresses.add(context.getRecipientHelper().resolveSignalServiceAddress(record.first()));
+ final var address = account.getRecipientAddressResolver().resolveRecipientAddress(record.first());
+ if (address.aci().isPresent() || address.number().isPresent()) {
+ addresses.add(new BlockedListMessage.Individual(address.aci().orElse(null),
+ address.number().orElse(null)));
+ }
}
}
var groupIds = new ArrayList<byte[]>();
}
public SendMessageResult sendVerifiedMessage(
- SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel
+ SignalServiceAddress destination,
+ IdentityKey identityKey,
+ TrustLevel trustLevel
) {
var verifiedMessage = new VerifiedMessage(destination,
identityKey,
}
public SendMessageResult sendKeysMessage() {
- var keysMessage = new KeysMessage(Optional.ofNullable(account.getOrCreateStorageKey()),
- Optional.ofNullable(account.getOrCreatePinMasterKey()));
+ var keysMessage = new KeysMessage(account.getOrCreateStorageKey(),
+ account.getOrCreatePinMasterKey(),
+ account.getOrCreateAccountEntropyPool(),
+ account.getOrCreateMediaRootBackupKey());
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage));
}
public SendMessageResult sendStickerOperationsMessage(
- List<StickerPack> installStickers, List<StickerPack> removeStickers
+ List<StickerPack> installStickers,
+ List<StickerPack> removeStickers
) {
var installStickerMessages = installStickers.stream().map(s -> getStickerPackOperationMessage(s, true));
var removeStickerMessages = removeStickers.stream().map(s -> getStickerPackOperationMessage(s, false));
}
private static StickerPackOperationMessage getStickerPackOperationMessage(
- final StickerPack s, final boolean installed
+ final StickerPack s,
+ final boolean installed
) {
return new StickerPackOperationMessage(s.packId().serialize(),
s.packKey(),
c = s.read();
} catch (IOException e) {
if (e.getMessage() != null && e.getMessage().contains("Missing contact address!")) {
- logger.warn("Sync contacts contained invalid contact, ignoring: {}", e.getMessage());
+ logger.debug("Sync contacts contained invalid contact, ignoring: {}", e.getMessage());
continue;
} else {
throw e;
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (c.getExpirationTimer().isPresent()) {
- builder.withMessageExpirationTime(c.getExpirationTimer().get());
+ if (c.getExpirationTimerVersion().isPresent() && (
+ contact == null || c.getExpirationTimerVersion().get() > contact.messageExpirationTimeVersion()
+ )) {
+ builder.withMessageExpirationTime(c.getExpirationTimer().get());
+ builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get());
+ } else {
+ logger.debug(
+ "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: {}",
+ recipientId,
+ c.getExpirationTimerVersion(),
+ contact == null ? 1 : contact.messageExpirationTimeVersion());
+ }
}
- builder.withIsBlocked(c.isBlocked());
builder.withIsArchived(c.isArchived());
account.getContactStore().storeContact(recipientId, builder.build());
if (c.getAvatar().isPresent()) {
- downloadContactAvatar(c.getAvatar().get(), address);
+ storeContactAvatar(c.getAvatar().get(), address);
}
}
}
- public SendMessageResult sendMessageRequestResponse(
- final MessageEnvelope.Sync.MessageRequestResponse.Type type, final GroupId groupId
- ) {
+ public SendMessageResult sendMessageRequestResponse(final MessageRequestResponse.Type type, final GroupId groupId) {
final var response = MessageRequestResponseMessage.forGroup(groupId.serialize(), localToRemoteType(type));
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
}
public SendMessageResult sendMessageRequestResponse(
- final MessageEnvelope.Sync.MessageRequestResponse.Type type, final RecipientId recipientId
+ final MessageRequestResponse.Type type,
+ final RecipientId recipientId
) {
final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
if (address.serviceId().isEmpty()) {
return null;
}
+ context.getContactHelper()
+ .setContactProfileSharing(recipientId,
+ type == MessageRequestResponse.Type.ACCEPT
+ || type == MessageRequestResponse.Type.UNBLOCK_AND_ACCEPT);
final var response = MessageRequestResponseMessage.forIndividual(address.serviceId().get(),
localToRemoteType(type));
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
return context.getSendHelper().sendSyncMessage(message);
}
- private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(RecipientAddress address) throws IOException {
+ private Optional<DeviceContactAvatar> createContactAvatarAttachment(RecipientAddress address) throws IOException {
final var streamDetails = context.getAvatarStore().retrieveContactAvatar(address);
if (streamDetails == null) {
return Optional.empty();
}
- return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()));
+ return Optional.of(new DeviceContactAvatar(streamDetails.getStream(),
+ streamDetails.getLength(),
+ streamDetails.getContentType()));
}
- private void downloadContactAvatar(SignalServiceAttachment avatar, RecipientAddress address) {
+ private void storeContactAvatar(DeviceContactAvatar avatar, RecipientAddress address) {
try {
context.getAvatarStore()
.storeContactAvatar(address,
- outputStream -> context.getAttachmentHelper().retrieveAttachment(avatar, outputStream));
+ outputStream -> IOUtils.copyStream(avatar.getInputStream(), outputStream));
} catch (IOException e) {
logger.warn("Failed to download avatar for contact {}, ignoring: {}", address, e.getMessage());
}
}
- private MessageRequestResponseMessage.Type localToRemoteType(final MessageEnvelope.Sync.MessageRequestResponse.Type type) {
+ private MessageRequestResponseMessage.Type localToRemoteType(final MessageRequestResponse.Type type) {
return switch (type) {
case UNKNOWN -> MessageRequestResponseMessage.Type.UNKNOWN;
case ACCEPT -> MessageRequestResponseMessage.Type.ACCEPT;