From 4acc9a96e34995c86c076d9020a40251c726c64c Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 24 Nov 2020 21:44:57 +0100 Subject: [PATCH] Implement a sticker store --- .../org/asamk/signal/manager/Manager.java | 33 ++++++++- .../asamk/signal/storage/SignalAccount.java | 17 +++++ .../signal/storage/stickers/Sticker.java | 35 ++++++++++ .../signal/storage/stickers/StickerStore.java | 70 +++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/asamk/signal/storage/stickers/Sticker.java create mode 100644 src/main/java/org/asamk/signal/storage/stickers/StickerStore.java diff --git a/src/main/java/org/asamk/signal/manager/Manager.java b/src/main/java/org/asamk/signal/manager/Manager.java index 07c8b583..56376f0e 100644 --- a/src/main/java/org/asamk/signal/manager/Manager.java +++ b/src/main/java/org/asamk/signal/manager/Manager.java @@ -26,6 +26,7 @@ import org.asamk.signal.storage.groups.GroupInfoV2; import org.asamk.signal.storage.profiles.SignalProfile; import org.asamk.signal.storage.profiles.SignalProfileEntry; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.storage.stickers.Sticker; import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.Util; import org.signal.libsignal.metadata.InvalidMetadataMessageException; @@ -103,6 +104,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage; 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.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.push.ContactTokenDetails; @@ -871,6 +873,10 @@ public class Manager implements Closeable { byte[] packKey = KeyUtils.createStickerUploadKey(); String packId = messageSender.uploadStickerManifest(manifest, packKey); + Sticker sticker = new Sticker(Hex.fromStringCondensed(packId), packKey); + account.getStickerStore().updateSticker(sticker); + account.save(); + try { return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, StandardCharsets.UTF_8) + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)) .toString(); @@ -1409,6 +1415,14 @@ public class Manager implements Closeable { } } } + if (message.getSticker().isPresent()) { + final SignalServiceDataMessage.Sticker messageSticker = message.getSticker().get(); + Sticker sticker = account.getStickerStore().getSticker(messageSticker.getPackId()); + if (sticker == null) { + sticker = new Sticker(messageSticker.getPackId(), messageSticker.getPackKey()); + account.getStickerStore().updateSticker(sticker); + } + } return actions; } @@ -1647,7 +1661,7 @@ public class Manager implements Closeable { if (rm.isBlockedListRequest()) { actions.add(SendSyncBlockedListAction.create()); } - // TODO Handle rm.isConfigurationRequest(); + // TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest(); } if (syncMessage.getGroups().isPresent()) { File tmpFile = null; @@ -1773,6 +1787,23 @@ public class Manager implements Closeable { final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); account.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); } + if (syncMessage.getStickerPackOperations().isPresent()) { + final List stickerPackOperationMessages = syncMessage.getStickerPackOperations().get(); + for (StickerPackOperationMessage m : stickerPackOperationMessages) { + if (!m.getPackId().isPresent()) { + continue; + } + Sticker sticker = account.getStickerStore().getSticker(m.getPackId().get()); + if (sticker == null) { + if (!m.getPackKey().isPresent()) { + continue; + } + sticker = new Sticker(m.getPackId().get(), m.getPackKey().get()); + } + sticker.setInstalled(!m.getType().isPresent() || m.getType().get() == StickerPackOperationMessage.Type.INSTALL); + account.getStickerStore().updateSticker(sticker); + } + } if (syncMessage.getConfiguration().isPresent()) { // TODO } diff --git a/src/main/java/org/asamk/signal/storage/SignalAccount.java b/src/main/java/org/asamk/signal/storage/SignalAccount.java index d3c2506d..dbb0ac04 100644 --- a/src/main/java/org/asamk/signal/storage/SignalAccount.java +++ b/src/main/java/org/asamk/signal/storage/SignalAccount.java @@ -21,6 +21,7 @@ import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.protocol.RecipientStore; import org.asamk.signal.storage.protocol.SessionInfo; import org.asamk.signal.storage.protocol.SignalServiceAddressResolver; +import org.asamk.signal.storage.stickers.StickerStore; import org.asamk.signal.storage.threads.LegacyJsonThreadStore; import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.util.IOUtils; @@ -72,6 +73,7 @@ public class SignalAccount implements Closeable { private JsonContactsStore contactStore; private RecipientStore recipientStore; private ProfileStore profileStore; + private StickerStore stickerStore; private SignalAccount(final FileChannel fileChannel, final FileLock lock) { this.fileChannel = fileChannel; @@ -114,6 +116,7 @@ public class SignalAccount implements Closeable { account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); account.profileStore = new ProfileStore(); + account.stickerStore = new StickerStore(); account.registered = false; return account; @@ -140,6 +143,7 @@ public class SignalAccount implements Closeable { account.contactStore = new JsonContactsStore(); account.recipientStore = new RecipientStore(); account.profileStore = new ProfileStore(); + account.stickerStore = new StickerStore(); account.registered = true; account.isMultiDevice = true; @@ -267,6 +271,14 @@ public class SignalAccount implements Closeable { profileStore = new ProfileStore(); } + JsonNode stickerStoreNode = rootNode.get("stickerStore"); + if (stickerStoreNode != null) { + stickerStore = jsonProcessor.convertValue(stickerStoreNode, StickerStore.class); + } + if (stickerStore == null) { + stickerStore = new StickerStore(); + } + JsonNode threadStoreNode = rootNode.get("threadStore"); if (threadStoreNode != null) { LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class); @@ -314,6 +326,7 @@ public class SignalAccount implements Closeable { .putPOJO("contactStore", contactStore) .putPOJO("recipientStore", recipientStore) .putPOJO("profileStore", profileStore) + .putPOJO("stickerStore", stickerStore) ; try { try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { @@ -379,6 +392,10 @@ public class SignalAccount implements Closeable { return profileStore; } + public StickerStore getStickerStore() { + return stickerStore; + } + public String getUsername() { return username; } diff --git a/src/main/java/org/asamk/signal/storage/stickers/Sticker.java b/src/main/java/org/asamk/signal/storage/stickers/Sticker.java new file mode 100644 index 00000000..386924c4 --- /dev/null +++ b/src/main/java/org/asamk/signal/storage/stickers/Sticker.java @@ -0,0 +1,35 @@ +package org.asamk.signal.storage.stickers; + +public class Sticker { + + private final byte[] packId; + private final byte[] packKey; + private boolean installed; + + public Sticker(final byte[] packId, final byte[] packKey) { + this.packId = packId; + this.packKey = packKey; + } + + public Sticker(final byte[] packId, final byte[] packKey, final boolean installed) { + this.packId = packId; + this.packKey = packKey; + this.installed = installed; + } + + public byte[] getPackId() { + return packId; + } + + public byte[] getPackKey() { + return packKey; + } + + public boolean isInstalled() { + return installed; + } + + public void setInstalled(final boolean installed) { + this.installed = installed; + } +} diff --git a/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java b/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java new file mode 100644 index 00000000..7b87369d --- /dev/null +++ b/src/main/java/org/asamk/signal/storage/stickers/StickerStore.java @@ -0,0 +1,70 @@ +package org.asamk.signal.storage.stickers; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import org.whispersystems.util.Base64; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class StickerStore { + + private static final ObjectMapper jsonProcessor = new ObjectMapper(); + + @JsonSerialize(using = StickersSerializer.class) + @JsonDeserialize(using = StickersDeserializer.class) + private final Map stickers = new HashMap<>(); + + public Sticker getSticker(byte[] packId) { + return stickers.get(packId); + } + + public void updateSticker(Sticker sticker) { + stickers.put(sticker.getPackId(), sticker); + } + + private static class StickersSerializer extends JsonSerializer> { + + @Override + public void serialize(final Map value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException { + final Collection stickers = value.values(); + jgen.writeStartArray(stickers.size()); + for (Sticker sticker : stickers) { + jgen.writeStartObject(); + jgen.writeStringField("packId", Base64.encodeBytes(sticker.getPackId())); + jgen.writeStringField("packKey", Base64.encodeBytes(sticker.getPackKey())); + jgen.writeBooleanField("installed", sticker.isInstalled()); + jgen.writeEndObject(); + } + jgen.writeEndArray(); + } + } + + private static class StickersDeserializer extends JsonDeserializer> { + + @Override + public Map deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + Map stickers = new HashMap<>(); + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + for (JsonNode n : node) { + byte[] packId = Base64.decode(n.get("packId").asText()); + byte[] packKey = Base64.decode(n.get("packKey").asText()); + boolean installed = n.get("installed").asBoolean(false); + stickers.put(packId, new Sticker(packId, packKey, installed)); + } + + return stickers; + } + } +} -- 2.50.1