import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.MimeUtils;
+import org.asamk.signal.manager.util.Utils;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
+import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.File;
import java.io.FileOutputStream;
Optional.ofNullable(pointer.getContentType()));
}
+ public StreamDetails retrieveAttachment(final String id) throws IOException {
+ final var attachmentFile = new File(attachmentsPath, id);
+ if (!attachmentFile.exists()) {
+ return null;
+ }
+ return Utils.createStreamDetailsFromFile(attachmentFile);
+ }
+
private void storeAttachment(final File attachmentFile, final AttachmentStorer storer) throws IOException {
createAttachmentsDir();
try (OutputStream output = new FileOutputStream(attachmentFile)) {
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.time.Duration;
import java.util.Collection;
void addClosedListener(Runnable listener);
+ InputStream retrieveAttachment(final String id) throws IOException;
+
@Override
void close() throws IOException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
}
}
+ @Override
+ public InputStream retrieveAttachment(final String id) throws IOException {
+ return context.getAttachmentHelper().retrieveAttachment(id).getStream();
+ }
+
@Override
public void close() {
Thread thread;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
+import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.File;
import java.io.IOException;
return attachmentStore.getAttachmentFile(pointer);
}
+ public StreamDetails retrieveAttachment(final String id) throws IOException {
+ return attachmentStore.retrieveAttachment(id);
+ }
+
+
public List<SignalServiceAttachment> uploadAttachments(final List<String> attachments) throws AttachmentInvalidException, IOException {
var attachmentStreams = AttachmentUtils.createAttachmentStreams(attachments);
PATH::
The path of the manifest.json or a zip file containing the sticker pack you wish to upload.
+=== getAttachment
+
+Gets teh raw data for a specified attachment. This is done using the ID of the attachment the recipient or group ID.
+The attachment data is returned as a Base64 String.
+
+*--id* [ID]::
+The ID of the attachment as given in the attachment list of the message.
+
+*--recipient* [RECIPIENT]::
+Specify the number which sent the attachment. Referred to generally as recipient.
+
+*-g* [GROUP], *--group-id* [GROUP]::
+Alternatively, specify the group IDs that for which to get the attachment.
+
=== daemon
signal-cli can run in daemon mode and provides an experimental dbus or JSON-RPC interface.
static {
addCommand(new AddDeviceCommand());
+ addCommand(new GetAttachmentCommand());
addCommand(new BlockCommand());
addCommand(new DaemonCommand());
addCommand(new DeleteLocalAccountDataCommand());
--- /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.UnexpectedErrorException;
+import org.asamk.signal.commands.exceptions.UserErrorException;
+import org.asamk.signal.json.JsonAttachmentData;
+import org.asamk.signal.manager.Manager;
+import org.asamk.signal.output.JsonWriter;
+import org.asamk.signal.output.OutputWriter;
+import org.asamk.signal.output.PlainTextWriter;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Base64;
+
+public class GetAttachmentCommand implements JsonRpcLocalCommand {
+
+ @Override
+ public String getName() {
+ return "getAttachment";
+ }
+
+ @Override
+ public void attachToSubparser(final Subparser subparser) {
+ subparser.addArgument("--id")
+ .required(true)
+ .help("The ID of the attachment file.");
+ var mut = subparser.addMutuallyExclusiveGroup()
+ .required(true);
+ mut.addArgument("--recipient")
+ .help("Sender of the attachment");
+ mut.addArgument("-g", "--group-id")
+ .help("Group in which the attachment was received");
+ }
+
+ @Override
+ public void handleCommand(
+ final Namespace ns,
+ final Manager m,
+ final OutputWriter outputWriter
+ ) throws CommandException {
+
+ final var id = ns.getString("id");
+
+ try(InputStream attachment = m.retrieveAttachment(id)) {
+ if (outputWriter instanceof PlainTextWriter writer) {
+ final var bytes = attachment.readAllBytes();
+ final var base64 = Base64.getEncoder().encodeToString(bytes);
+ writer.println(base64);
+ } else if (outputWriter instanceof JsonWriter writer) {
+ writer.write(new JsonAttachmentData(attachment));
+ }
+ } catch (FileNotFoundException ex) {
+ throw new UserErrorException("Could not find attachment with ID: " + id, ex);
+ } catch (IOException ex) {
+ throw new UnexpectedErrorException("An error occurred reading attachment: " + id, ex);
+ }
+ }
+}
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
}).toList();
}
+ @Override
+ public InputStream retrieveAttachment(final String id) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
@SuppressWarnings("unchecked")
private <T> T getValue(
final Map<String, Variant<?>> stringVariantMap, final String field
--- /dev/null
+package org.asamk.signal.json;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import java.io.InputStream;
+
+public record JsonAttachmentData(
+ @JsonSerialize(using=JsonStreamSerializer.class) InputStream data
+) {}
--- /dev/null
+package org.asamk.signal.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class JsonStreamSerializer extends JsonSerializer<InputStream> {
+
+ @Override
+ public void serialize(
+ final InputStream value,
+ final JsonGenerator jsonGenerator,
+ final SerializerProvider serializers
+ ) throws IOException {
+ jsonGenerator.writeBinary(value, -1);
+ }
+}