*/
package org.asamk.signal.manager;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
import org.asamk.Signal;
import org.asamk.signal.AttachmentInvalidException;
import org.asamk.signal.GroupNotFoundException;
+import org.asamk.signal.JsonStickerPack;
import org.asamk.signal.NotAGroupMemberException;
+import org.asamk.signal.StickerPackInvalidException;
import org.asamk.signal.TrustLevel;
import org.asamk.signal.UserAlreadyExists;
import org.asamk.signal.storage.SignalAccount;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
+import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
+import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
+import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.util.Base64;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
public class Manager implements Signal {
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 {
+ 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) {
+ throw new StickerPackInvalidException("Must set a 'stickers' field.");
+ }
+
+ if (pack.stickers.isEmpty()) {
+ throw new StickerPackInvalidException("Must include stickers.");
+ }
+
+ List<StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
+ for (JsonStickerPack.JsonSticker sticker : pack.stickers) {
+ if (sticker.file == null) {
+ throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
+ }
+
+ 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(data.first(), data.second(), Optional.fromNullable(sticker.emoji).or(""));
+ stickers.add(stickerInfo);
+ }
+
+ StickerInfo cover = null;
+ if (pack.cover != null) {
+ if (pack.cover.file == null) {
+ throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
+ }
+
+ 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);
+ }
+
+ cover = new StickerInfo(data.first(), data.second(), Optional.fromNullable(pack.cover.emoji).or(""));
+ }
+
+ return new SignalServiceStickerManifestUpload(
+ pack.title,
+ pack.author,
+ cover,
+ stickers);
+ }
+
+ private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException, StickerPackInvalidException {
+ InputStream inputStream;
+ if (zip != null) {
+ inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
+ } else {
+ inputStream = new FileInputStream((new File(rootPath, "manifest.json")));
+ }
+ return new ObjectMapper().readValue(inputStream, JsonStickerPack.class);
+ }
+
+ private static Pair<InputStream, Long> getInputStreamAndLength(final String rootPath, final ZipFile zip, final String subfile) throws IOException {
+ if (zip != null) {
+ final ZipEntry entry = zip.getEntry(subfile);
+ return new Pair<>(zip.getInputStream(entry), entry.getSize());
+ } else {
+ final File file = new File(rootPath, subfile);
+ return new Pair<>(new FileInputStream(file), file.length());
+ }
+ }
+
private void requestSyncGroups() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));