mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
parent
f40c351662
commit
2d068997c5
7 changed files with 253 additions and 14 deletions
|
@ -18,6 +18,19 @@ public class JsonStickerPack {
|
|||
@JsonProperty
|
||||
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 {
|
||||
|
||||
@JsonProperty
|
||||
|
@ -28,5 +41,15 @@ public class JsonStickerPack {
|
|||
|
||||
@JsonProperty
|
||||
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.ProfileHelper;
|
||||
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.groups.GroupInfo;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
|
||||
|
@ -202,6 +205,7 @@ public class Manager implements Closeable {
|
|||
private final PinHelper pinHelper;
|
||||
private final AvatarStore avatarStore;
|
||||
private final AttachmentStore attachmentStore;
|
||||
private final StickerPackStore stickerPackStore;
|
||||
private final SignalSessionLock sessionLock = new SignalSessionLock() {
|
||||
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
|
||||
|
||||
|
@ -275,6 +279,7 @@ public class Manager implements Closeable {
|
|||
this::resolveSignalServiceAddress);
|
||||
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
|
||||
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
|
||||
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
|
@ -1434,18 +1439,20 @@ public class Manager implements Closeable {
|
|||
var messageSender = createMessageSender();
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
"pack_id="
|
||||
+ URLEncoder.encode(Hex.toStringCondensed(packId.serialize()), StandardCharsets.UTF_8)
|
||||
+ "&pack_key="
|
||||
+ URLEncoder.encode(Hex.toStringCondensed(packKey), StandardCharsets.UTF_8)).toString();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
@ -1939,6 +1946,7 @@ public class Manager implements Closeable {
|
|||
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
|
||||
account.getStickerStore().updateSticker(sticker);
|
||||
}
|
||||
enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
@ -2461,16 +2469,23 @@ public class Manager implements Closeable {
|
|||
continue;
|
||||
}
|
||||
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);
|
||||
if (sticker == null) {
|
||||
if (!m.getPackKey().isPresent()) {
|
||||
continue;
|
||||
if (m.getPackKey().isPresent()) {
|
||||
if (sticker == null) {
|
||||
sticker = new Sticker(stickerPackId, m.getPackKey().get());
|
||||
}
|
||||
if (installed) {
|
||||
enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
|
||||
}
|
||||
sticker = new Sticker(stickerPackId, m.getPackKey().get());
|
||||
}
|
||||
sticker.setInstalled(!m.getType().isPresent()
|
||||
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL);
|
||||
account.getStickerStore().updateSticker(sticker);
|
||||
|
||||
if (sticker != null) {
|
||||
sticker.setInstalled(installed);
|
||||
account.getStickerStore().updateSticker(sticker);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (syncMessage.getFetchType().isPresent()) {
|
||||
|
@ -2939,6 +2954,11 @@ public class Manager implements Closeable {
|
|||
return account.getRecipientStore().resolveRecipientTrusted(address);
|
||||
}
|
||||
|
||||
private void enqueueJob(Job job) {
|
||||
var context = new Context(account, accountManager, messageReceiver, stickerPackStore);
|
||||
job.run(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
close(true);
|
||||
|
|
|
@ -7,17 +7,22 @@ public class PathConfig {
|
|||
private final File dataPath;
|
||||
private final File attachmentsPath;
|
||||
private final File avatarsPath;
|
||||
private final File stickerPacksPath;
|
||||
|
||||
public static PathConfig createDefault(final File settingsPath) {
|
||||
return new PathConfig(new File(settingsPath, "data"),
|
||||
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.attachmentsPath = attachmentsPath;
|
||||
this.avatarsPath = avatarsPath;
|
||||
this.stickerPacksPath = stickerPacksPath;
|
||||
}
|
||||
|
||||
public File getDataPath() {
|
||||
|
@ -31,4 +36,8 @@ public class PathConfig {
|
|||
public File getAvatarsPath() {
|
||||
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