mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
parent
f40c351662
commit
2d068997c5
7 changed files with 253 additions and 14 deletions
|
@ -18,6 +18,19 @@ public class JsonStickerPack {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public List<JsonSticker> stickers;
|
public List<JsonSticker> stickers;
|
||||||
|
|
||||||
|
// For deserialization
|
||||||
|
private JsonStickerPack() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonStickerPack(
|
||||||
|
final String title, final String author, final JsonSticker cover, final List<JsonSticker> stickers
|
||||||
|
) {
|
||||||
|
this.title = title;
|
||||||
|
this.author = author;
|
||||||
|
this.cover = cover;
|
||||||
|
this.stickers = stickers;
|
||||||
|
}
|
||||||
|
|
||||||
public static class JsonSticker {
|
public static class JsonSticker {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
|
@ -28,5 +41,15 @@ public class JsonStickerPack {
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
public String contentType;
|
public String contentType;
|
||||||
|
|
||||||
|
// For deserialization
|
||||||
|
private JsonSticker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonSticker(final String emoji, final String file, final String contentType) {
|
||||||
|
this.emoji = emoji;
|
||||||
|
this.file = file;
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,9 @@ import org.asamk.signal.manager.helper.GroupV2Helper;
|
||||||
import org.asamk.signal.manager.helper.PinHelper;
|
import org.asamk.signal.manager.helper.PinHelper;
|
||||||
import org.asamk.signal.manager.helper.ProfileHelper;
|
import org.asamk.signal.manager.helper.ProfileHelper;
|
||||||
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
|
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
|
||||||
|
import org.asamk.signal.manager.jobs.Context;
|
||||||
|
import org.asamk.signal.manager.jobs.Job;
|
||||||
|
import org.asamk.signal.manager.jobs.RetrieveStickerPackJob;
|
||||||
import org.asamk.signal.manager.storage.SignalAccount;
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
||||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
||||||
|
@ -202,6 +205,7 @@ public class Manager implements Closeable {
|
||||||
private final PinHelper pinHelper;
|
private final PinHelper pinHelper;
|
||||||
private final AvatarStore avatarStore;
|
private final AvatarStore avatarStore;
|
||||||
private final AttachmentStore attachmentStore;
|
private final AttachmentStore attachmentStore;
|
||||||
|
private final StickerPackStore stickerPackStore;
|
||||||
private final SignalSessionLock sessionLock = new SignalSessionLock() {
|
private final SignalSessionLock sessionLock = new SignalSessionLock() {
|
||||||
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
|
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
|
||||||
|
|
||||||
|
@ -275,6 +279,7 @@ public class Manager implements Closeable {
|
||||||
this::resolveSignalServiceAddress);
|
this::resolveSignalServiceAddress);
|
||||||
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
|
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
|
||||||
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
|
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
|
||||||
|
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
|
@ -1434,18 +1439,20 @@ public class Manager implements Closeable {
|
||||||
var messageSender = createMessageSender();
|
var messageSender = createMessageSender();
|
||||||
|
|
||||||
var packKey = KeyUtils.createStickerUploadKey();
|
var packKey = KeyUtils.createStickerUploadKey();
|
||||||
var packId = messageSender.uploadStickerManifest(manifest, packKey);
|
var packIdString = messageSender.uploadStickerManifest(manifest, packKey);
|
||||||
|
var packId = StickerPackId.deserialize(Hex.fromStringCondensed(packIdString));
|
||||||
|
|
||||||
var sticker = new Sticker(StickerPackId.deserialize(Hex.fromStringCondensed(packId)), packKey);
|
var sticker = new Sticker(packId, packKey);
|
||||||
account.getStickerStore().updateSticker(sticker);
|
account.getStickerStore().updateSticker(sticker);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URI("https",
|
return new URI("https",
|
||||||
"signal.art",
|
"signal.art",
|
||||||
"/addstickers/",
|
"/addstickers/",
|
||||||
"pack_id=" + URLEncoder.encode(packId, StandardCharsets.UTF_8) + "&pack_key=" + URLEncoder.encode(
|
"pack_id="
|
||||||
Hex.toStringCondensed(packKey),
|
+ URLEncoder.encode(Hex.toStringCondensed(packId.serialize()), StandardCharsets.UTF_8)
|
||||||
StandardCharsets.UTF_8)).toString();
|
+ "&pack_key="
|
||||||
|
+ URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)).toString();
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
@ -1939,6 +1946,7 @@ public class Manager implements Closeable {
|
||||||
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
|
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
|
||||||
account.getStickerStore().updateSticker(sticker);
|
account.getStickerStore().updateSticker(sticker);
|
||||||
}
|
}
|
||||||
|
enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
|
||||||
}
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
@ -2461,18 +2469,25 @@ public class Manager implements Closeable {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
|
final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
|
||||||
|
final var installed = !m.getType().isPresent()
|
||||||
|
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
|
||||||
|
|
||||||
var sticker = account.getStickerStore().getSticker(stickerPackId);
|
var sticker = account.getStickerStore().getSticker(stickerPackId);
|
||||||
|
if (m.getPackKey().isPresent()) {
|
||||||
if (sticker == null) {
|
if (sticker == null) {
|
||||||
if (!m.getPackKey().isPresent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sticker = new Sticker(stickerPackId, m.getPackKey().get());
|
sticker = new Sticker(stickerPackId, m.getPackKey().get());
|
||||||
}
|
}
|
||||||
sticker.setInstalled(!m.getType().isPresent()
|
if (installed) {
|
||||||
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL);
|
enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sticker != null) {
|
||||||
|
sticker.setInstalled(installed);
|
||||||
account.getStickerStore().updateSticker(sticker);
|
account.getStickerStore().updateSticker(sticker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (syncMessage.getFetchType().isPresent()) {
|
if (syncMessage.getFetchType().isPresent()) {
|
||||||
switch (syncMessage.getFetchType().get()) {
|
switch (syncMessage.getFetchType().get()) {
|
||||||
case LOCAL_PROFILE:
|
case LOCAL_PROFILE:
|
||||||
|
@ -2939,6 +2954,11 @@ public class Manager implements Closeable {
|
||||||
return account.getRecipientStore().resolveRecipientTrusted(address);
|
return account.getRecipientStore().resolveRecipientTrusted(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void enqueueJob(Job job) {
|
||||||
|
var context = new Context(account, accountManager, messageReceiver, stickerPackStore);
|
||||||
|
job.run(context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
close(true);
|
close(true);
|
||||||
|
|
|
@ -7,17 +7,22 @@ public class PathConfig {
|
||||||
private final File dataPath;
|
private final File dataPath;
|
||||||
private final File attachmentsPath;
|
private final File attachmentsPath;
|
||||||
private final File avatarsPath;
|
private final File avatarsPath;
|
||||||
|
private final File stickerPacksPath;
|
||||||
|
|
||||||
public static PathConfig createDefault(final File settingsPath) {
|
public static PathConfig createDefault(final File settingsPath) {
|
||||||
return new PathConfig(new File(settingsPath, "data"),
|
return new PathConfig(new File(settingsPath, "data"),
|
||||||
new File(settingsPath, "attachments"),
|
new File(settingsPath, "attachments"),
|
||||||
new File(settingsPath, "avatars"));
|
new File(settingsPath, "avatars"),
|
||||||
|
new File(settingsPath, "stickers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PathConfig(final File dataPath, final File attachmentsPath, final File avatarsPath) {
|
private PathConfig(
|
||||||
|
final File dataPath, final File attachmentsPath, final File avatarsPath, final File stickerPacksPath
|
||||||
|
) {
|
||||||
this.dataPath = dataPath;
|
this.dataPath = dataPath;
|
||||||
this.attachmentsPath = attachmentsPath;
|
this.attachmentsPath = attachmentsPath;
|
||||||
this.avatarsPath = avatarsPath;
|
this.avatarsPath = avatarsPath;
|
||||||
|
this.stickerPacksPath = stickerPacksPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getDataPath() {
|
public File getDataPath() {
|
||||||
|
@ -31,4 +36,8 @@ public class PathConfig {
|
||||||
public File getAvatarsPath() {
|
public File getAvatarsPath() {
|
||||||
return avatarsPath;
|
return avatarsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getStickerPacksPath() {
|
||||||
|
return stickerPacksPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.stickers.StickerPackId;
|
||||||
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
|
import org.whispersystems.signalservice.internal.util.Hex;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class StickerPackStore {
|
||||||
|
|
||||||
|
private final File stickersPath;
|
||||||
|
|
||||||
|
public StickerPackStore(final File stickersPath) {
|
||||||
|
this.stickersPath = stickersPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean existsStickerPack(StickerPackId stickerPackId) {
|
||||||
|
return getStickerPackManifestFile(stickerPackId).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeManifest(StickerPackId stickerPackId, JsonStickerPack manifest) throws IOException {
|
||||||
|
try (OutputStream output = new FileOutputStream(getStickerPackManifestFile(stickerPackId))) {
|
||||||
|
try (var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8))) {
|
||||||
|
new ObjectMapper().writeValue(writer, manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeSticker(StickerPackId stickerPackId, int stickerId, StickerStorer storer) throws IOException {
|
||||||
|
createStickerPackDir(stickerPackId);
|
||||||
|
try (OutputStream output = new FileOutputStream(getStickerPackStickerFile(stickerPackId, stickerId))) {
|
||||||
|
storer.store(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getStickerPackManifestFile(StickerPackId stickerPackId) {
|
||||||
|
return new File(getStickerPackPath(stickerPackId), "manifest.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getStickerPackStickerFile(StickerPackId stickerPackId, int stickerId) {
|
||||||
|
return new File(getStickerPackPath(stickerPackId), String.valueOf(stickerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getStickerPackPath(StickerPackId stickerPackId) {
|
||||||
|
return new File(stickersPath, Hex.toStringCondensed(stickerPackId.serialize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createStickerPackDir(StickerPackId stickerPackId) throws IOException {
|
||||||
|
IOUtils.createPrivateDirectories(getStickerPackPath(stickerPackId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface StickerStorer {
|
||||||
|
|
||||||
|
void store(OutputStream outputStream) throws IOException;
|
||||||
|
}
|
||||||
|
}
|
42
lib/src/main/java/org/asamk/signal/manager/jobs/Context.java
Normal file
42
lib/src/main/java/org/asamk/signal/manager/jobs/Context.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package org.asamk.signal.manager.jobs;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.StickerPackStore;
|
||||||
|
import org.asamk.signal.manager.storage.SignalAccount;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||||
|
|
||||||
|
public class Context {
|
||||||
|
|
||||||
|
private SignalAccount account;
|
||||||
|
private SignalServiceAccountManager accountManager;
|
||||||
|
private SignalServiceMessageReceiver messageReceiver;
|
||||||
|
private StickerPackStore stickerPackStore;
|
||||||
|
|
||||||
|
public Context(
|
||||||
|
final SignalAccount account,
|
||||||
|
final SignalServiceAccountManager accountManager,
|
||||||
|
final SignalServiceMessageReceiver messageReceiver,
|
||||||
|
final StickerPackStore stickerPackStore
|
||||||
|
) {
|
||||||
|
this.account = account;
|
||||||
|
this.accountManager = accountManager;
|
||||||
|
this.messageReceiver = messageReceiver;
|
||||||
|
this.stickerPackStore = stickerPackStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalAccount getAccount() {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalServiceAccountManager getAccountManager() {
|
||||||
|
return accountManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalServiceMessageReceiver getMessageReceiver() {
|
||||||
|
return messageReceiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StickerPackStore getStickerPackStore() {
|
||||||
|
return stickerPackStore;
|
||||||
|
}
|
||||||
|
}
|
6
lib/src/main/java/org/asamk/signal/manager/jobs/Job.java
Normal file
6
lib/src/main/java/org/asamk/signal/manager/jobs/Job.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package org.asamk.signal.manager.jobs;
|
||||||
|
|
||||||
|
public interface Job {
|
||||||
|
|
||||||
|
void run(Context context);
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.asamk.signal.manager.jobs;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.JsonStickerPack;
|
||||||
|
import org.asamk.signal.manager.storage.stickers.StickerPackId;
|
||||||
|
import org.asamk.signal.manager.util.IOUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.libsignal.InvalidMessageException;
|
||||||
|
import org.whispersystems.signalservice.internal.util.Hex;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class RetrieveStickerPackJob implements Job {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(RetrieveStickerPackJob.class);
|
||||||
|
|
||||||
|
private final StickerPackId packId;
|
||||||
|
private final byte[] packKey;
|
||||||
|
|
||||||
|
public RetrieveStickerPackJob(final StickerPackId packId, final byte[] packKey) {
|
||||||
|
this.packId = packId;
|
||||||
|
this.packKey = packKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(Context context) {
|
||||||
|
if (context.getStickerPackStore().existsStickerPack(packId)) {
|
||||||
|
logger.debug("Sticker pack {} already downloaded.", Hex.toStringCondensed(packId.serialize()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("Retrieving sticker pack {}.", Hex.toStringCondensed(packId.serialize()));
|
||||||
|
try {
|
||||||
|
final var manifest = context.getMessageReceiver().retrieveStickerManifest(packId.serialize(), packKey);
|
||||||
|
|
||||||
|
final var stickerIds = new HashSet<Integer>();
|
||||||
|
if (manifest.getCover().isPresent()) {
|
||||||
|
stickerIds.add(manifest.getCover().get().getId());
|
||||||
|
}
|
||||||
|
for (var sticker : manifest.getStickers()) {
|
||||||
|
stickerIds.add(sticker.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var id : stickerIds) {
|
||||||
|
final var inputStream = context.getMessageReceiver().retrieveSticker(packId.serialize(), packKey, id);
|
||||||
|
context.getStickerPackStore().storeSticker(packId, id, o -> IOUtils.copyStream(inputStream, o));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var jsonManifest = new JsonStickerPack(manifest.getTitle().orNull(),
|
||||||
|
manifest.getAuthor().orNull(),
|
||||||
|
manifest.getCover()
|
||||||
|
.transform(c -> new JsonStickerPack.JsonSticker(c.getEmoji(),
|
||||||
|
String.valueOf(c.getId()),
|
||||||
|
c.getContentType()))
|
||||||
|
.orNull(),
|
||||||
|
manifest.getStickers()
|
||||||
|
.stream()
|
||||||
|
.map(c -> new JsonStickerPack.JsonSticker(c.getEmoji(),
|
||||||
|
String.valueOf(c.getId()),
|
||||||
|
c.getContentType()))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
context.getStickerPackStore().storeManifest(packId, jsonManifest);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to retrieve sticker pack {}: {}",
|
||||||
|
Hex.toStringCondensed(packId.serialize()),
|
||||||
|
e.getMessage());
|
||||||
|
} catch (InvalidMessageException e) {
|
||||||
|
logger.warn("Failed to retrieve sticker pack {}, invalid pack data: {}",
|
||||||
|
Hex.toStringCondensed(packId.serialize()),
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue