mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Refactor sticker upload
This commit is contained in:
parent
23845eab47
commit
4ff28458ff
4 changed files with 76 additions and 103 deletions
|
@ -20,7 +20,7 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_3'
|
compile 'com.github.turasa:signal-service-java:2.15.3_unofficial_4'
|
||||||
compile 'org.bouncycastle:bcprov-jdk15on:1.64'
|
compile 'org.bouncycastle:bcprov-jdk15on:1.64'
|
||||||
compile 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
compile 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
||||||
compile 'org.freedesktop.dbus:dbus-java:2.7.0'
|
compile 'org.freedesktop.dbus:dbus-java:2.7.0'
|
||||||
|
|
|
@ -21,8 +21,6 @@ public class UploadStickerPackCommand implements LocalCommand {
|
||||||
try {
|
try {
|
||||||
String path = ns.getString("path");
|
String path = ns.getString("path");
|
||||||
String url = m.uploadStickerPack(path);
|
String url = m.uploadStickerPack(path);
|
||||||
System.out.println("");
|
|
||||||
System.out.println("Upload complete! Sticker pack URL:");
|
|
||||||
System.out.println(url);
|
System.out.println(url);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -34,6 +34,10 @@ class KeyUtils {
|
||||||
return getSecretBytes(16);
|
return getSecretBytes(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static byte[] createStickerUploadKey() {
|
||||||
|
return getSecretBytes(64);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getSecret(int size) {
|
private static String getSecret(int size) {
|
||||||
byte[] secret = getSecretBytes(size);
|
byte[] secret = getSecretBytes(size);
|
||||||
return Base64.encodeBytes(secret);
|
return Base64.encodeBytes(secret);
|
||||||
|
|
|
@ -81,8 +81,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest;
|
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifest.StickerInfo;
|
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
|
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
|
||||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
|
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
|
||||||
|
@ -108,13 +108,10 @@ import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||||
import org.whispersystems.signalservice.internal.push.StickerUploadAttributes;
|
|
||||||
import org.whispersystems.signalservice.internal.push.StickerUploadAttributesResponse;
|
|
||||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||||
import org.whispersystems.signalservice.internal.util.Hex;
|
import org.whispersystems.signalservice.internal.util.Hex;
|
||||||
import org.whispersystems.util.Base64;
|
import org.whispersystems.util.Base64;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -123,6 +120,8 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
@ -131,7 +130,6 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -870,8 +868,42 @@ public class Manager implements Signal {
|
||||||
account.getThreadStore().updateThread(thread);
|
account.getThreadStore().updateThread(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the sticker pack from path.
|
||||||
|
*
|
||||||
|
* @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
|
||||||
|
* @return if successful, returns the URL to install the sticker pack in the signal app
|
||||||
|
*/
|
||||||
public String uploadStickerPack(String path) throws IOException, StickerPackInvalidException {
|
public String uploadStickerPack(String path) throws IOException, StickerPackInvalidException {
|
||||||
JsonStickerPack pack = parseStickerPack(path);
|
SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path);
|
||||||
|
|
||||||
|
SignalServiceMessageSender messageSender = getMessageSender();
|
||||||
|
|
||||||
|
byte[] packKey = KeyUtils.createStickerUploadKey();
|
||||||
|
String packId = messageSender.uploadStickerManifest(manifest, packKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URI("https", "signal.art", "/addstickers/", "pack_id=" + URLEncoder.encode(packId, "utf-8") + "&pack_key=" + URLEncoder.encode(Hex.toStringCondensed(packKey), "utf-8"))
|
||||||
|
.toString();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(final String path) throws IOException, StickerPackInvalidException {
|
||||||
|
ZipFile zip = null;
|
||||||
|
String rootPath = null;
|
||||||
|
|
||||||
|
final File file = new File(path);
|
||||||
|
if (file.getName().endsWith(".zip")) {
|
||||||
|
zip = new ZipFile(file);
|
||||||
|
} else if (file.getName().equals("manifest.json")) {
|
||||||
|
rootPath = file.getParent();
|
||||||
|
} else {
|
||||||
|
throw new StickerPackInvalidException("Could not find manifest.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonStickerPack pack = parseStickerPack(rootPath, zip);
|
||||||
|
|
||||||
if (pack.stickers == null) {
|
if (pack.stickers == null) {
|
||||||
throw new StickerPackInvalidException("Must set a 'stickers' field.");
|
throw new StickerPackInvalidException("Must set a 'stickers' field.");
|
||||||
|
@ -882,126 +914,65 @@ public class Manager implements Signal {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
|
List<StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
|
||||||
for (int i = 0; i < pack.stickers.size(); i++) {
|
for (JsonStickerPack.JsonSticker sticker : pack.stickers) {
|
||||||
if (pack.stickers.get(i).file == null) {
|
if (sticker.file == null) {
|
||||||
throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
|
throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
|
||||||
}
|
}
|
||||||
if (!stickerDataContainsPath(path, pack.stickers.get(i).file)) {
|
|
||||||
throw new StickerPackInvalidException("Could not find find " + pack.stickers.get(i).file);
|
Pair<InputStream, Long> data;
|
||||||
|
try {
|
||||||
|
data = getInputStreamAndLength(rootPath, zip, sticker.file);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
throw new StickerPackInvalidException("Could not find find " + sticker.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
StickerInfo stickerInfo = new StickerInfo(i, Optional.fromNullable(pack.stickers.get(i).emoji).or(""));
|
StickerInfo stickerInfo = new StickerInfo(data.first(), data.second(), Optional.fromNullable(sticker.emoji).or(""));
|
||||||
stickers.add(stickerInfo);
|
stickers.add(stickerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean uniqueCover = false;
|
StickerInfo cover = null;
|
||||||
StickerInfo cover = stickers.get(0);
|
|
||||||
if (pack.cover != null) {
|
if (pack.cover != null) {
|
||||||
if (pack.cover.file == null) {
|
if (pack.cover.file == null) {
|
||||||
throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
|
throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
|
||||||
}
|
}
|
||||||
if (!stickerDataContainsPath(path, pack.cover.file)) {
|
|
||||||
throw new StickerPackInvalidException("Could not find find cover " + pack.cover.file);
|
Pair<InputStream, Long> data;
|
||||||
|
try {
|
||||||
|
data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueCover = true;
|
cover = new StickerInfo(data.first(), data.second(), Optional.fromNullable(pack.cover.emoji).or(""));
|
||||||
cover = new StickerInfo(pack.stickers.size(), Optional.fromNullable(pack.cover.emoji).or(""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalServiceStickerManifest manifest = new SignalServiceStickerManifest(
|
return new SignalServiceStickerManifestUpload(
|
||||||
Optional.fromNullable(pack.title).or(""),
|
pack.title,
|
||||||
Optional.fromNullable(pack.author).or(""),
|
pack.author,
|
||||||
cover,
|
cover,
|
||||||
stickers);
|
stickers);
|
||||||
|
|
||||||
SignalServiceMessageSender messageSender = new SignalServiceMessageSender(
|
|
||||||
BaseConfig.serviceConfiguration,
|
|
||||||
null,
|
|
||||||
username,
|
|
||||||
account.getPassword(),
|
|
||||||
account.getDeviceId(),
|
|
||||||
account.getSignalProtocolStore(),
|
|
||||||
BaseConfig.USER_AGENT,
|
|
||||||
account.isMultiDevice(),
|
|
||||||
Optional.fromNullable(messagePipe),
|
|
||||||
Optional.fromNullable(unidentifiedMessagePipe),
|
|
||||||
Optional.<SignalServiceMessageSender.EventListener>absent());
|
|
||||||
|
|
||||||
System.out.println("Starting upload process...");
|
|
||||||
Pair<byte[], StickerUploadAttributesResponse> responsePair = messageSender.getStickerUploadAttributes(stickers.size() + (uniqueCover ? 1 : 0));
|
|
||||||
byte[] packKey = responsePair.first();
|
|
||||||
StickerUploadAttributesResponse response = responsePair.second();
|
|
||||||
|
|
||||||
System.out.println("Uploading manifest...");
|
|
||||||
messageSender.uploadStickerManifest(manifest, packKey, response.getManifest());
|
|
||||||
|
|
||||||
Map<Integer, StickerUploadAttributes> attrById = new HashMap<>();
|
|
||||||
|
|
||||||
for (StickerUploadAttributes attr : response.getStickers()) {
|
|
||||||
attrById.put(attr.getId(), attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < pack.stickers.size(); i++) {
|
|
||||||
System.out.println("Uploading sticker " + (i+1) + "/" + pack.stickers.size() + "...");
|
|
||||||
StickerUploadAttributes attr = attrById.get(i);
|
|
||||||
if (attr == null) {
|
|
||||||
throw new StickerPackInvalidException("Upload attributes missing for id " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] data = readStickerDataFromPath(path, pack.stickers.get(i).file);
|
|
||||||
messageSender.uploadSticker(new ByteArrayInputStream(data), data.length, packKey, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uniqueCover) {
|
|
||||||
System.out.println("Uploading unique cover...");
|
|
||||||
StickerUploadAttributes attr = attrById.get(pack.stickers.size());
|
|
||||||
if (attr == null) {
|
|
||||||
throw new StickerPackInvalidException("Upload attributes missing for cover with id " + pack.stickers.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] data = readStickerDataFromPath(path, pack.cover.file);
|
|
||||||
messageSender.uploadSticker(new ByteArrayInputStream(data), data.length, packKey, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "https://signal.art/addstickers/#pack_id=" + response.getPackId() + "&pack_key=" + Hex.toStringCondensed(packKey).replaceAll(" ", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] readStickerDataFromPath(String rootPath, String subFile) throws IOException, StickerPackInvalidException {
|
private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException, StickerPackInvalidException {
|
||||||
if (rootPath.endsWith(".zip")) {
|
InputStream inputStream;
|
||||||
ZipFile zip = new ZipFile(rootPath);
|
if (zip != null) {
|
||||||
ZipEntry entry = zip.getEntry(subFile);
|
inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
|
||||||
return IOUtils.readFully(zip.getInputStream(entry));
|
|
||||||
} else if (rootPath.endsWith(".json")) {
|
|
||||||
String dir = new File(rootPath).getParent();
|
|
||||||
FileInputStream fis = new FileInputStream(new File(dir, subFile));
|
|
||||||
return IOUtils.readFully(fis);
|
|
||||||
} else {
|
} else {
|
||||||
throw new StickerPackInvalidException("Must point to either a ZIP or JSON file.");
|
inputStream = new FileInputStream((new File(rootPath, "manifest.json")));
|
||||||
}
|
}
|
||||||
|
return new ObjectMapper().readValue(inputStream, JsonStickerPack.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean stickerDataContainsPath(String rootPath, String subFile) throws IOException {
|
private static Pair<InputStream, Long> getInputStreamAndLength(final String rootPath, final ZipFile zip, final String subfile) throws IOException {
|
||||||
if (rootPath.endsWith(".zip")) {
|
if (zip != null) {
|
||||||
ZipFile zip = new ZipFile(rootPath);
|
final ZipEntry entry = zip.getEntry(subfile);
|
||||||
return zip.getEntry(subFile) != null;
|
return new Pair<>(zip.getInputStream(entry), entry.getSize());
|
||||||
} else if (rootPath.endsWith(".json")) {
|
|
||||||
String dir = new File(rootPath).getParent();
|
|
||||||
return new File(dir, subFile).exists();
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
final File file = new File(rootPath, subfile);
|
||||||
|
return new Pair<>(new FileInputStream(file), file.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonStickerPack parseStickerPack(String rootPath) throws IOException, StickerPackInvalidException {
|
|
||||||
if (!stickerDataContainsPath(rootPath, "manifest.json")) {
|
|
||||||
throw new StickerPackInvalidException("Could not find manifest.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = new String(readStickerDataFromPath(rootPath, "manifest.json"));
|
|
||||||
|
|
||||||
return new ObjectMapper().readValue(json, JsonStickerPack.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestSyncGroups() throws IOException {
|
private void requestSyncGroups() throws IOException {
|
||||||
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
|
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
|
||||||
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue