## [Unreleased]
+### Added
+
+- New `addStickerPack` command
+
## [0.12.0] - 2023-08-11
**Attention**: Now requires native libsignal-client version 0.30.0
*/
StickerPackUrl uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
+ void installStickerPack(StickerPackUrl url) throws IOException;
+
List<StickerPack> getStickerPacks();
void requestAllSyncData() throws IOException;
public static StickerPackUrl fromUri(URI uri) throws InvalidStickerPackLinkException {
final var rawQuery = uri.getRawFragment();
if (isEmpty(rawQuery)) {
- throw new RuntimeException("Invalid sticker pack uri");
+ throw new InvalidStickerPackLinkException("Invalid sticker pack uri");
}
var query = Utils.getQueryMap(rawQuery);
continue;
}
final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
+ final var stickerPackKey = m.getPackKey().orElse(null);
final var installed = m.getType().isEmpty()
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
- var sticker = account.getStickerStore().getStickerPack(stickerPackId);
- if (m.getPackKey().isPresent()) {
- if (sticker == null) {
- sticker = new StickerPack(-1, stickerPackId, m.getPackKey().get(), installed);
- account.getStickerStore().addStickerPack(sticker);
- }
- if (installed) {
- context.getJobExecutor()
- .enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
- }
- }
+ final var sticker = context.getStickerHelper()
+ .addOrUpdateStickerPack(stickerPackId, stickerPackKey, installed);
- if (sticker != null && sticker.isInstalled() != installed) {
- account.getStickerStore().updateStickerPackInstalled(sticker.packId(), installed);
+ if (sticker != null && installed) {
+ context.getJobExecutor().enqueueJob(new RetrieveStickerPackJob(stickerPackId, sticker.packKey()));
}
}
}
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
var messageSender = dependencies.getMessageSender();
+ if (!account.isMultiDevice()) {
+ logger.trace("Not sending sync message because there are no linked devices.");
+ return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty());
+ }
try {
return messageSender.sendSyncMessage(message, context.getUnidentifiedAccessHelper().getAccessForSync());
} catch (UnregisteredUserException e) {
import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack;
+import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.asamk.signal.manager.util.IOUtils;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.slf4j.Logger;
this.context = context;
}
+ public StickerPack addOrUpdateStickerPack(
+ final StickerPackId stickerPackId, final byte[] stickerPackKey, final boolean installed
+ ) {
+ final var sticker = account.getStickerStore().getStickerPack(stickerPackId);
+ if (sticker != null) {
+ if (sticker.isInstalled() != installed) {
+ account.getStickerStore().updateStickerPackInstalled(sticker.packId(), installed);
+ }
+ return sticker;
+ }
+
+ if (stickerPackKey == null) {
+ return null;
+ }
+
+ final var newSticker = new StickerPack(-1, stickerPackId, stickerPackKey, installed);
+ account.getStickerStore().addStickerPack(newSticker);
+ return newSticker;
+ }
+
public JsonStickerPack getOrRetrieveStickerPack(
StickerPackId packId, byte[] packKey
) throws InvalidStickerException {
- if (!context.getStickerPackStore().existsStickerPack(packId)) {
- try {
- retrieveStickerPack(packId, packKey);
- } catch (InvalidMessageException | IOException e) {
- throw new InvalidStickerException("Failed to retrieve sticker pack");
- }
+ try {
+ retrieveStickerPack(packId, packKey);
+ } catch (InvalidMessageException | IOException e) {
+ throw new InvalidStickerException("Failed to retrieve sticker pack");
}
final JsonStickerPack manifest;
try {
}
public void retrieveStickerPack(StickerPackId packId, byte[] packKey) throws InvalidMessageException, IOException {
+ 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()));
final var messageReceiver = dependencies.getMessageReceiver();
final var manifest = messageReceiver.retrieveStickerManifest(packId.serialize(), packKey);
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.MimeUtils;
+import org.jetbrains.annotations.NotNull;
import org.signal.libsignal.protocol.IdentityKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
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.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
public class SyncHelper {
context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage));
}
+ public void sendStickerOperationsMessage(List<StickerPack> installStickers, List<StickerPack> removeStickers) {
+ var installStickerMessages = installStickers.stream().map(s -> getStickerPackOperationMessage(s, true));
+ var removeStickerMessages = removeStickers.stream().map(s -> getStickerPackOperationMessage(s, false));
+ var stickerMessages = Stream.concat(installStickerMessages, removeStickerMessages).toList();
+ context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forStickerPackOperations(stickerMessages));
+ }
+
+ @NotNull
+ private static StickerPackOperationMessage getStickerPackOperationMessage(
+ final StickerPack s, final boolean installed
+ ) {
+ return new StickerPackOperationMessage(s.packId().serialize(),
+ s.packKey(),
+ installed ? StickerPackOperationMessage.Type.INSTALL : StickerPackOperationMessage.Type.REMOVE);
+ }
+
public void sendConfigurationMessage() {
final var config = account.getConfigurationStore();
var configurationMessage = new ConfigurationMessage(Optional.ofNullable(config.getReadReceipts()),
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.MimeUtils;
import org.asamk.signal.manager.util.StickerUtils;
+import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
var sticker = new StickerPack(packId, packKey);
account.getStickerStore().addStickerPack(sticker);
+ context.getSyncHelper().sendStickerOperationsMessage(List.of(sticker), List.of());
return new StickerPackUrl(packId, packKey);
}
+ @Override
+ public void installStickerPack(StickerPackUrl url) throws IOException {
+ final var packId = url.getPackId();
+ final var packKey = url.getPackKey();
+ try {
+ context.getStickerHelper().retrieveStickerPack(packId, packKey);
+ } catch (InvalidMessageException e) {
+ throw new IOException(e);
+ }
+
+ final var sticker = context.getStickerHelper().addOrUpdateStickerPack(packId, packKey, true);
+ context.getSyncHelper().sendStickerOperationsMessage(List.of(sticker), List.of());
+ }
+
@Override
public List<org.asamk.signal.manager.api.StickerPack> getStickerPacks() {
final var stickerPackStore = context.getStickerPackStore();
@Override
public void run(Context context) {
- if (context.getStickerPackStore().existsStickerPack(packId)) {
- logger.debug("Sticker pack {} already downloaded.", Hex.toStringCondensed(packId.serialize()));
- return;
- }
try {
context.getStickerHelper().retrieveStickerPack(packId, packKey);
} catch (IOException e) {
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
+import java.util.List;
public class StickerStore {
this.database = database;
}
- public Collection<StickerPack> getStickerPacks() {
+ public List<StickerPack> getStickerPacks() {
final var sql = (
"""
SELECT s._id, s.pack_id, s.pack_key, s.installed
Show a list of known sticker packs.
+=== addStickerPack
+
+Install a sticker pack for this account.
+
+*--uri* [URI]::
+Specify the uri of the sticker pack.
+e.g. https://signal.art/addstickers/#pack_id=XXX&pack_key=XXX)"
+
=== getAttachment
Gets the raw data for a specified attachment.
--- /dev/null
+package org.asamk.signal.commands;
+
+import net.sourceforge.argparse4j.inf.Namespace;
+import net.sourceforge.argparse4j.inf.Subparser;
+
+import org.asamk.signal.commands.exceptions.CommandException;
+import org.asamk.signal.commands.exceptions.IOErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.StickerPackUrl;
+import org.asamk.signal.output.OutputWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class AddStickerPackCommand implements JsonRpcLocalCommand {
+
+ private final static Logger logger = LoggerFactory.getLogger(AddStickerPackCommand.class);
+
+ @Override
+ public String getName() {
+ return "addStickerPack";
+ }
+
+ @Override
+ public void attachToSubparser(final Subparser subparser) {
+ subparser.help("Install a sticker pack for this account.");
+ subparser.addArgument("--uri")
+ .required(true)
+ .nargs("+")
+ .help("Specify the uri of the sticker pack. (e.g. https://signal.art/addstickers/#pack_id=XXX&pack_key=XXX)");
+ }
+
+ @Override
+ public void handleCommand(
+ final Namespace ns, final Manager m, final OutputWriter outputWriter
+ ) throws CommandException {
+ final var uris = ns.<String>getList("uri");
+ for (final var uri : uris) {
+ final URI stickerUri;
+ try {
+ stickerUri = new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new UserErrorException("Sticker pack uri has invalid format: " + e.getMessage());
+ }
+
+ try {
+ var stickerPackUrl = StickerPackUrl.fromUri(stickerUri);
+ m.installStickerPack(stickerPackUrl);
+ } catch (IOException e) {
+ logger.error("Install sticker pack failed", e);
+ throw new IOErrorException("Install sticker pack failed", e);
+ } catch (StickerPackUrl.InvalidStickerPackLinkException e) {
+ logger.error("Invalid sticker pack link", e);
+ throw new UserErrorException("Invalid sticker pack link", e);
+ }
+ }
+ }
+}
addCommand(new FinishLinkCommand());
addCommand(new GetAttachmentCommand());
addCommand(new GetUserStatusCommand());
+ addCommand(new AddStickerPackCommand());
addCommand(new JoinGroupCommand());
addCommand(new JsonRpcDispatcherCommand());
addCommand(new LinkCommand());
}
}
+ @Override
+ public void installStickerPack(final StickerPackUrl url) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public List<StickerPack> getStickerPacks() {
throw new UnsupportedOperationException();