mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-04 21:20:39 +00:00
try to merge again
This commit is contained in:
parent
6d18f311e6
commit
685fce477c
184 changed files with 14906 additions and 1705 deletions
|
@ -15,17 +15,45 @@ public interface Signal extends DBusInterface {
|
|||
|
||||
long sendMessage(
|
||||
String message, List<String> attachments, String recipient
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber;
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity;
|
||||
|
||||
long sendMessage(
|
||||
String message, List<String> attachments, List<String> recipients
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UnregisteredUser, Error.UntrustedIdentity;
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity;
|
||||
|
||||
void sendEndSessionMessage(List<String> recipients) throws Error.Failure, Error.InvalidNumber, Error.UnregisteredUser, Error.UntrustedIdentity;
|
||||
long sendRemoteDeleteMessage(
|
||||
long targetSentTimestamp, String recipient
|
||||
) throws Error.Failure, Error.InvalidNumber;
|
||||
|
||||
long sendRemoteDeleteMessage(
|
||||
long targetSentTimestamp, List<String> recipients
|
||||
) throws Error.Failure, Error.InvalidNumber;
|
||||
|
||||
long sendGroupRemoteDeleteMessage(
|
||||
long targetSentTimestamp, byte[] groupId
|
||||
) throws Error.Failure, Error.GroupNotFound;
|
||||
|
||||
long sendMessageReaction(
|
||||
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, String recipient
|
||||
) throws Error.InvalidNumber, Error.Failure;
|
||||
|
||||
long sendMessageReaction(
|
||||
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List<String> recipients
|
||||
) throws Error.InvalidNumber, Error.Failure;
|
||||
|
||||
long sendNoteToSelfMessage(
|
||||
String message, List<String> attachments
|
||||
) throws Error.AttachmentInvalid, Error.Failure;
|
||||
|
||||
void sendEndSessionMessage(List<String> recipients) throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity;
|
||||
|
||||
long sendGroupMessage(
|
||||
String message, List<String> attachments, byte[] groupId
|
||||
) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid, Error.UnregisteredUser, Error.UntrustedIdentity;
|
||||
) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid;
|
||||
|
||||
long sendGroupMessageReaction(
|
||||
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId
|
||||
) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber;
|
||||
|
||||
String getContactName(String number) throws Error.InvalidNumber;
|
||||
|
||||
|
@ -43,10 +71,30 @@ public interface Signal extends DBusInterface {
|
|||
|
||||
byte[] updateGroup(
|
||||
byte[] groupId, String name, List<String> members, String avatar
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.UnregisteredUser, Error.UntrustedIdentity;
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound;
|
||||
|
||||
boolean isRegistered();
|
||||
|
||||
void updateProfile(
|
||||
String name, String about, String aboutEmoji, String avatarPath, boolean removeAvatar
|
||||
) throws Error.Failure;
|
||||
|
||||
String version();
|
||||
|
||||
List<String> listNumbers();
|
||||
|
||||
List<String> getContactNumber(final String name) throws Error.Failure;
|
||||
|
||||
void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure;
|
||||
|
||||
boolean isContactBlocked(final String number);
|
||||
|
||||
boolean isGroupBlocked(final byte[] groupId);
|
||||
|
||||
boolean isMember(final byte[] groupId);
|
||||
|
||||
void joinGroup(final String groupLink) throws Error.Failure;
|
||||
|
||||
class MessageReceived extends DBusSignal {
|
||||
|
||||
private final long timestamp;
|
||||
|
@ -194,13 +242,6 @@ public interface Signal extends DBusInterface {
|
|||
}
|
||||
}
|
||||
|
||||
class UnregisteredUser extends DBusExecutionException {
|
||||
|
||||
public UnregisteredUser(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
class UntrustedIdentity extends DBusExecutionException {
|
||||
|
||||
public UntrustedIdentity(final String message) {
|
||||
|
|
322
src/main/java/org/asamk/signal/App.java
Normal file
322
src/main/java/org/asamk/signal/App.java
Normal file
|
@ -0,0 +1,322 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import net.sourceforge.argparse4j.ArgumentParsers;
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.ArgumentParser;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.commands.Command;
|
||||
import org.asamk.signal.commands.Commands;
|
||||
import org.asamk.signal.commands.DbusCommand;
|
||||
import org.asamk.signal.commands.ExtendedDbusCommand;
|
||||
import org.asamk.signal.commands.LocalCommand;
|
||||
import org.asamk.signal.commands.MultiLocalCommand;
|
||||
import org.asamk.signal.commands.ProvisioningCommand;
|
||||
import org.asamk.signal.commands.RegistrationCommand;
|
||||
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.manager.Manager;
|
||||
import org.asamk.signal.manager.NotRegisteredException;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.RegistrationManager;
|
||||
import org.asamk.signal.manager.config.ServiceConfig;
|
||||
import org.asamk.signal.manager.config.ServiceEnvironment;
|
||||
import org.asamk.signal.util.IOUtils;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class App {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
private final Namespace ns;
|
||||
|
||||
static ArgumentParser buildArgumentParser() {
|
||||
var parser = ArgumentParsers.newFor("signal-cli")
|
||||
.build()
|
||||
.defaultHelp(true)
|
||||
.description("Commandline interface for Signal.")
|
||||
.version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION);
|
||||
|
||||
parser.addArgument("-v", "--version").help("Show package version.").action(Arguments.version());
|
||||
parser.addArgument("--verbose")
|
||||
.help("Raise log level and include lib signal logs.")
|
||||
.action(Arguments.storeTrue());
|
||||
parser.addArgument("--config")
|
||||
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
|
||||
|
||||
parser.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification.");
|
||||
|
||||
var mut = parser.addMutuallyExclusiveGroup();
|
||||
mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue());
|
||||
mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue());
|
||||
|
||||
parser.addArgument("-o", "--output")
|
||||
.help("Choose to output in plain text or JSON")
|
||||
.type(Arguments.enumStringType(OutputType.class))
|
||||
.setDefault(OutputType.PLAIN_TEXT);
|
||||
|
||||
var subparsers = parser.addSubparsers().title("subcommands").dest("command");
|
||||
|
||||
final var commands = Commands.getCommands();
|
||||
for (var entry : commands.entrySet()) {
|
||||
var subparser = subparsers.addParser(entry.getKey());
|
||||
entry.getValue().attachToSubparser(subparser);
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
public App(final Namespace ns) {
|
||||
this.ns = ns;
|
||||
}
|
||||
|
||||
public void init() throws CommandException {
|
||||
var commandKey = ns.getString("command");
|
||||
var command = Commands.getCommand(commandKey);
|
||||
if (command == null) {
|
||||
throw new UserErrorException("Command not implemented!");
|
||||
}
|
||||
|
||||
OutputType outputType = ns.get("output");
|
||||
if (!command.getSupportedOutputTypes().contains(outputType)) {
|
||||
throw new UserErrorException("Command doesn't support output type " + outputType.toString());
|
||||
}
|
||||
|
||||
var username = ns.getString("username");
|
||||
|
||||
final boolean useDbus = ns.getBoolean("dbus");
|
||||
final boolean useDbusSystem = ns.getBoolean("dbus_system");
|
||||
if (useDbus || useDbusSystem) {
|
||||
// If username is null, it will connect to the default object path
|
||||
initDbusClient(command, username, useDbusSystem);
|
||||
return;
|
||||
}
|
||||
|
||||
final File dataPath;
|
||||
var config = ns.getString("config");
|
||||
if (config != null) {
|
||||
dataPath = new File(config);
|
||||
} else {
|
||||
dataPath = getDefaultDataPath();
|
||||
}
|
||||
|
||||
final var serviceEnvironment = ServiceEnvironment.LIVE;
|
||||
|
||||
if (!ServiceConfig.getCapabilities().isGv2()) {
|
||||
logger.warn("WARNING: Support for new group V2 is disabled,"
|
||||
+ " because the required native library dependency is missing: libzkgroup");
|
||||
}
|
||||
|
||||
if (!ServiceConfig.isSignalClientAvailable()) {
|
||||
throw new UserErrorException("Missing required native library dependency: libsignal-client");
|
||||
}
|
||||
|
||||
if (command instanceof ProvisioningCommand) {
|
||||
if (username != null) {
|
||||
throw new UserErrorException("You cannot specify a username (phone number) when linking");
|
||||
}
|
||||
|
||||
handleProvisioningCommand((ProvisioningCommand) command, dataPath, serviceEnvironment);
|
||||
return;
|
||||
}
|
||||
|
||||
if (username == null) {
|
||||
var usernames = Manager.getAllLocalUsernames(dataPath);
|
||||
|
||||
if (command instanceof MultiLocalCommand) {
|
||||
handleMultiLocalCommand((MultiLocalCommand) command, dataPath, serviceEnvironment, usernames);
|
||||
return;
|
||||
}
|
||||
|
||||
if (usernames.size() == 0) {
|
||||
throw new UserErrorException("No local users found, you first need to register or link an account");
|
||||
} else if (usernames.size() > 1) {
|
||||
throw new UserErrorException(
|
||||
"Multiple users found, you need to specify a username (phone number) with -u");
|
||||
}
|
||||
|
||||
username = usernames.get(0);
|
||||
} else if (!PhoneNumberFormatter.isValidNumber(username, null)) {
|
||||
throw new UserErrorException("Invalid username (phone number), make sure you include the country code.");
|
||||
}
|
||||
|
||||
if (command instanceof RegistrationCommand) {
|
||||
handleRegistrationCommand((RegistrationCommand) command, username, dataPath, serviceEnvironment);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(command instanceof LocalCommand)) {
|
||||
throw new UserErrorException("Command only works via dbus");
|
||||
}
|
||||
|
||||
handleLocalCommand((LocalCommand) command, username, dataPath, serviceEnvironment);
|
||||
}
|
||||
|
||||
private void handleProvisioningCommand(
|
||||
final ProvisioningCommand command, final File dataPath, final ServiceEnvironment serviceEnvironment
|
||||
) throws CommandException {
|
||||
var pm = ProvisioningManager.init(dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
command.handleCommand(ns, pm);
|
||||
}
|
||||
|
||||
private void handleRegistrationCommand(
|
||||
final RegistrationCommand command,
|
||||
final String username,
|
||||
final File dataPath,
|
||||
final ServiceEnvironment serviceEnvironment
|
||||
) throws CommandException {
|
||||
final RegistrationManager manager;
|
||||
try {
|
||||
manager = RegistrationManager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
} catch (Throwable e) {
|
||||
throw new UnexpectedErrorException("Error loading or creating state file: "
|
||||
+ e.getMessage()
|
||||
+ " ("
|
||||
+ e.getClass().getSimpleName()
|
||||
+ ")");
|
||||
}
|
||||
try (var m = manager) {
|
||||
command.handleCommand(ns, m);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalCommand(
|
||||
final LocalCommand command,
|
||||
final String username,
|
||||
final File dataPath,
|
||||
final ServiceEnvironment serviceEnvironment
|
||||
) throws CommandException {
|
||||
try (var m = loadManager(username, dataPath, serviceEnvironment)) {
|
||||
command.handleCommand(ns, m);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMultiLocalCommand(
|
||||
final MultiLocalCommand command,
|
||||
final File dataPath,
|
||||
final ServiceEnvironment serviceEnvironment,
|
||||
final List<String> usernames
|
||||
) throws CommandException {
|
||||
final var managers = new ArrayList<Manager>();
|
||||
for (String u : usernames) {
|
||||
try {
|
||||
managers.add(loadManager(u, dataPath, serviceEnvironment));
|
||||
} catch (CommandException e) {
|
||||
logger.warn("Ignoring {}: {}", u, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
command.handleCommand(ns, managers);
|
||||
|
||||
for (var m : managers) {
|
||||
try {
|
||||
m.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cleanup failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Manager loadManager(
|
||||
final String username, final File dataPath, final ServiceEnvironment serviceEnvironment
|
||||
) throws CommandException {
|
||||
Manager manager;
|
||||
try {
|
||||
manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT);
|
||||
} catch (NotRegisteredException e) {
|
||||
throw new UserErrorException("User " + username + " is not registered.");
|
||||
} catch (Throwable e) {
|
||||
throw new UnexpectedErrorException("Error loading state file for user "
|
||||
+ username
|
||||
+ ": "
|
||||
+ e.getMessage()
|
||||
+ " ("
|
||||
+ e.getClass().getSimpleName()
|
||||
+ ")");
|
||||
}
|
||||
|
||||
try {
|
||||
manager.checkAccountState();
|
||||
} catch (IOException e) {
|
||||
throw new UnexpectedErrorException("Error while checking account " + username + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
private void initDbusClient(
|
||||
final Command command, final String username, final boolean systemBus
|
||||
) throws CommandException {
|
||||
try {
|
||||
DBusConnection.DBusBusType busType;
|
||||
if (systemBus) {
|
||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||
} else {
|
||||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
try (var dBusConn = DBusConnection.getConnection(busType)) {
|
||||
var ts = dBusConn.getRemoteObject(DbusConfig.getBusname(),
|
||||
DbusConfig.getObjectPath(username),
|
||||
Signal.class);
|
||||
|
||||
handleCommand(command, ts, dBusConn);
|
||||
}
|
||||
} catch (DBusException | IOException e) {
|
||||
logger.error("Dbus client failed", e);
|
||||
throw new UnexpectedErrorException("Dbus client failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCommand(Command command, Signal ts, DBusConnection dBusConn) throws CommandException {
|
||||
if (command instanceof ExtendedDbusCommand) {
|
||||
((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
|
||||
} else if (command instanceof DbusCommand) {
|
||||
((DbusCommand) command).handleCommand(ns, ts);
|
||||
} else {
|
||||
throw new UserErrorException("Command is not yet implemented via dbus");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
|
||||
* - $HOME/.config/signal
|
||||
* - $HOME/.config/textsecure
|
||||
*
|
||||
* @return the data directory to be used by signal-cli.
|
||||
*/
|
||||
private static File getDefaultDataPath() {
|
||||
var dataPath = new File(IOUtils.getDataHomeDir(), "signal-cli");
|
||||
if (dataPath.exists()) {
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
var configPath = new File(System.getProperty("user.home"), ".config");
|
||||
|
||||
var legacySettingsPath = new File(configPath, "signal");
|
||||
if (legacySettingsPath.exists()) {
|
||||
return legacySettingsPath;
|
||||
}
|
||||
|
||||
legacySettingsPath = new File(configPath, "textsecure");
|
||||
if (legacySettingsPath.exists()) {
|
||||
return legacySettingsPath;
|
||||
}
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,22 @@ package org.asamk.signal;
|
|||
|
||||
public class DbusConfig {
|
||||
|
||||
public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
|
||||
public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
|
||||
private static final String SIGNAL_BUSNAME = "org.asamk.Signal";
|
||||
private static final String SIGNAL_OBJECT_BASE_PATH = "/org/asamk/Signal";
|
||||
|
||||
public static String getBusname() {
|
||||
return SIGNAL_BUSNAME;
|
||||
}
|
||||
|
||||
public static String getObjectPath() {
|
||||
return getObjectPath(null);
|
||||
}
|
||||
|
||||
public static String getObjectPath(String username) {
|
||||
if (username == null) {
|
||||
return SIGNAL_OBJECT_BASE_PATH;
|
||||
}
|
||||
|
||||
return SIGNAL_OBJECT_BASE_PATH + "/" + username.replace('+', '_');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.GroupUtils;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.groups.GroupUtils;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
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.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -46,11 +41,11 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
|||
e.printStackTrace();
|
||||
}
|
||||
} else if (content != null) {
|
||||
final SignalServiceAddress sender = !envelope.isUnidentifiedSender() && envelope.hasSource()
|
||||
final var sender = !envelope.isUnidentifiedSender() && envelope.hasSource()
|
||||
? envelope.getSourceAddress()
|
||||
: content.getSender();
|
||||
if (content.getReceiptMessage().isPresent()) {
|
||||
final SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get();
|
||||
final var receiptMessage = content.getReceiptMessage().get();
|
||||
if (receiptMessage.isDeliveryReceipt()) {
|
||||
for (long timestamp : receiptMessage.getTimestamps()) {
|
||||
try {
|
||||
|
@ -63,9 +58,9 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
|||
}
|
||||
}
|
||||
} else if (content.getDataMessage().isPresent()) {
|
||||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
var message = content.getDataMessage().get();
|
||||
|
||||
byte[] groupId = getGroupId(message);
|
||||
var groupId = getGroupId(message);
|
||||
if (!message.isEndSession() && (
|
||||
groupId == null
|
||||
|| message.getGroupContext().get().getGroupV1Type() == null
|
||||
|
@ -83,15 +78,15 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
|||
}
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
SignalServiceSyncMessage sync_message = content.getSyncMessage().get();
|
||||
var sync_message = content.getSyncMessage().get();
|
||||
if (sync_message.getSent().isPresent()) {
|
||||
SentTranscriptMessage transcript = sync_message.getSent().get();
|
||||
var transcript = sync_message.getSent().get();
|
||||
|
||||
if (transcript.getDestination().isPresent() || transcript.getMessage()
|
||||
.getGroupContext()
|
||||
.isPresent()) {
|
||||
SignalServiceDataMessage message = transcript.getMessage();
|
||||
byte[] groupId = getGroupId(message);
|
||||
var message = transcript.getMessage();
|
||||
var groupId = getGroupId(message);
|
||||
|
||||
try {
|
||||
conn.sendMessage(new Signal.SyncMessageReceived(objectPath,
|
||||
|
@ -118,9 +113,9 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler {
|
|||
}
|
||||
|
||||
static private List<String> getAttachments(SignalServiceDataMessage message, Manager m) {
|
||||
List<String> attachments = new ArrayList<>();
|
||||
var attachments = new ArrayList<String>();
|
||||
if (message.getAttachments().isPresent()) {
|
||||
for (SignalServiceAttachment attachment : message.getAttachments().get()) {
|
||||
for (var attachment : message.getAttachments().get()) {
|
||||
if (attachment.isPointer()) {
|
||||
attachments.add(m.getAttachmentFile(attachment.asPointer().getRemoteId()).getAbsolutePath());
|
||||
}
|
||||
|
|
|
@ -1,47 +1,37 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.asamk.signal.json.JsonError;
|
||||
import org.asamk.signal.json.JsonMessageEnvelope;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class JsonReceiveMessageHandler implements Manager.ReceiveMessageHandler {
|
||||
|
||||
final Manager m;
|
||||
private final ObjectMapper jsonProcessor;
|
||||
private final static Logger logger = LoggerFactory.getLogger(JsonReceiveMessageHandler.class);
|
||||
|
||||
protected final Manager m;
|
||||
private final JsonWriter jsonWriter;
|
||||
|
||||
public JsonReceiveMessageHandler(Manager m) {
|
||||
this.m = m;
|
||||
this.jsonProcessor = new ObjectMapper();
|
||||
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect
|
||||
jsonProcessor.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||
jsonWriter = new JsonWriter(System.out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
|
||||
ObjectNode result = jsonProcessor.createObjectNode();
|
||||
final var object = new HashMap<String, Object>();
|
||||
if (exception != null) {
|
||||
result.putPOJO("error", new JsonError(exception));
|
||||
object.put("error", new JsonError(exception));
|
||||
}
|
||||
if (envelope != null) {
|
||||
result.putPOJO("envelope", new JsonMessageEnvelope(envelope, content, m));
|
||||
}
|
||||
try {
|
||||
jsonProcessor.writeValue(System.out, result);
|
||||
System.out.println();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
object.put("envelope", new JsonMessageEnvelope(envelope, content, m));
|
||||
}
|
||||
|
||||
jsonWriter.write(object);
|
||||
}
|
||||
}
|
||||
|
|
43
src/main/java/org/asamk/signal/JsonWriter.java
Normal file
43
src/main/java/org/asamk/signal/JsonWriter.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class JsonWriter {
|
||||
|
||||
private final Writer writer;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonWriter(final OutputStream writer) {
|
||||
this.writer = new BufferedWriter(new OutputStreamWriter(writer, StandardCharsets.UTF_8));
|
||||
|
||||
objectMapper = new ObjectMapper();
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY);
|
||||
objectMapper.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||
}
|
||||
|
||||
public void write(final Object object) {
|
||||
try {
|
||||
try {
|
||||
objectMapper.writeValue(writer, object);
|
||||
} catch (JsonProcessingException e) {
|
||||
// Some issue with json serialization, probably caused by a bug
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
writer.write(System.lineSeparator());
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2015-2020 AsamK and contributors
|
||||
Copyright (C) 2015-2021 AsamK and contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,282 +18,89 @@ package org.asamk.signal;
|
|||
|
||||
import net.sourceforge.argparse4j.ArgumentParsers;
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.ArgumentParser;
|
||||
import net.sourceforge.argparse4j.inf.ArgumentParserException;
|
||||
import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
import net.sourceforge.argparse4j.inf.Subparsers;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.commands.Command;
|
||||
import org.asamk.signal.commands.Commands;
|
||||
import org.asamk.signal.commands.DbusCommand;
|
||||
import org.asamk.signal.commands.ExtendedDbusCommand;
|
||||
import org.asamk.signal.commands.LocalCommand;
|
||||
import org.asamk.signal.commands.ProvisioningCommand;
|
||||
import org.asamk.signal.dbus.DbusSignalImpl;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.ServiceConfig;
|
||||
import org.asamk.signal.util.IOUtils;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.LibSignalLogger;
|
||||
import org.asamk.signal.util.SecurityProvider;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.Security;
|
||||
import java.util.Map;
|
||||
|
||||
public class Main {
|
||||
|
||||
final static Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
installSecurityProviderWorkaround();
|
||||
|
||||
Namespace ns = parseArgs(args);
|
||||
if (ns == null) {
|
||||
System.exit(1);
|
||||
}
|
||||
// Configuring the logger needs to happen before any logger is initialized
|
||||
configureLogging(isVerbose(args));
|
||||
|
||||
int res = init(ns);
|
||||
System.exit(res);
|
||||
var parser = App.buildArgumentParser();
|
||||
|
||||
var ns = parser.parseArgsOrFail(args);
|
||||
|
||||
int status = 0;
|
||||
try {
|
||||
new App(ns).init();
|
||||
} catch (CommandException e) {
|
||||
System.err.println(e.getMessage());
|
||||
status = getStatusForError(e);
|
||||
}
|
||||
System.exit(status);
|
||||
}
|
||||
|
||||
public static void installSecurityProviderWorkaround() {
|
||||
private static void installSecurityProviderWorkaround() {
|
||||
// Register our own security provider
|
||||
Security.insertProviderAt(new SecurityProvider(), 1);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public static int init(Namespace ns) {
|
||||
if (ns.getBoolean("dbus") || ns.getBoolean("dbus_system")) {
|
||||
return initDbusClient(ns, ns.getBoolean("dbus_system"));
|
||||
}
|
||||
|
||||
final String username = ns.getString("username");
|
||||
|
||||
final File dataPath;
|
||||
String config = ns.getString("config");
|
||||
if (config != null) {
|
||||
dataPath = new File(config);
|
||||
} else {
|
||||
dataPath = getDefaultDataPath();
|
||||
}
|
||||
|
||||
final SignalServiceConfiguration serviceConfiguration = ServiceConfig.createDefaultServiceConfiguration(
|
||||
BaseConfig.USER_AGENT);
|
||||
|
||||
if (!ServiceConfig.getCapabilities().isGv2()) {
|
||||
logger.warn("WARNING: Support for new group V2 is disabled,"
|
||||
+ " because the required native library dependency is missing: libzkgroup");
|
||||
}
|
||||
|
||||
if (username == null) {
|
||||
ProvisioningManager pm = new ProvisioningManager(dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
|
||||
return handleCommands(ns, pm);
|
||||
}
|
||||
|
||||
Manager manager;
|
||||
try {
|
||||
manager = Manager.init(username, dataPath, serviceConfiguration, BaseConfig.USER_AGENT);
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error loading state file: {}", e.getMessage());
|
||||
return 2;
|
||||
}
|
||||
|
||||
try (Manager m = manager) {
|
||||
try {
|
||||
m.checkAccountState();
|
||||
} catch (AuthorizationFailedException e) {
|
||||
if (!"register".equals(ns.getString("command"))) {
|
||||
// Register command should still be possible, if current authorization fails
|
||||
System.err.println("Authorization failed, was the number registered elsewhere?");
|
||||
return 2;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while checking account: {}", e.getMessage());
|
||||
return 2;
|
||||
}
|
||||
|
||||
return handleCommands(ns, m);
|
||||
} catch (IOException e) {
|
||||
logger.error("Cleanup failed", e);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private static int initDbusClient(final Namespace ns, final boolean systemBus) {
|
||||
try {
|
||||
DBusConnection.DBusBusType busType;
|
||||
if (systemBus) {
|
||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||
} else {
|
||||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
try (DBusConnection dBusConn = DBusConnection.getConnection(busType)) {
|
||||
Signal ts = dBusConn.getRemoteObject(DbusConfig.SIGNAL_BUSNAME,
|
||||
DbusConfig.SIGNAL_OBJECTPATH,
|
||||
Signal.class);
|
||||
|
||||
return handleCommands(ns, ts, dBusConn);
|
||||
}
|
||||
} catch (DBusException | IOException e) {
|
||||
logger.error("Dbus client failed", e);
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
private static int handleCommands(Namespace ns, Signal ts, DBusConnection dBusConn) {
|
||||
String commandKey = ns.getString("command");
|
||||
final Map<String, Command> commands = Commands.getCommands();
|
||||
if (commands.containsKey(commandKey)) {
|
||||
Command command = commands.get(commandKey);
|
||||
|
||||
if (command instanceof ExtendedDbusCommand) {
|
||||
return ((ExtendedDbusCommand) command).handleCommand(ns, ts, dBusConn);
|
||||
} else if (command instanceof DbusCommand) {
|
||||
return ((DbusCommand) command).handleCommand(ns, ts);
|
||||
} else {
|
||||
System.err.println(commandKey + " is not yet implemented via dbus");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int handleCommands(Namespace ns, ProvisioningManager pm) {
|
||||
String commandKey = ns.getString("command");
|
||||
final Map<String, Command> commands = Commands.getCommands();
|
||||
if (commands.containsKey(commandKey)) {
|
||||
Command command = commands.get(commandKey);
|
||||
|
||||
if (command instanceof ProvisioningCommand) {
|
||||
return ((ProvisioningCommand) command).handleCommand(ns, pm);
|
||||
} else {
|
||||
System.err.println(commandKey + " only works with a username");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int handleCommands(Namespace ns, Manager m) {
|
||||
String commandKey = ns.getString("command");
|
||||
final Map<String, Command> commands = Commands.getCommands();
|
||||
if (commands.containsKey(commandKey)) {
|
||||
Command command = commands.get(commandKey);
|
||||
|
||||
if (command instanceof LocalCommand) {
|
||||
return ((LocalCommand) command).handleCommand(ns, m);
|
||||
} else if (command instanceof DbusCommand) {
|
||||
return ((DbusCommand) command).handleCommand(ns, new DbusSignalImpl(m));
|
||||
} else if (command instanceof ExtendedDbusCommand) {
|
||||
System.err.println(commandKey + " only works via dbus");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses $XDG_DATA_HOME/signal-cli if it exists, or if none of the legacy directories exist:
|
||||
* - $HOME/.config/signal
|
||||
* - $HOME/.config/textsecure
|
||||
*
|
||||
* @return the data directory to be used by signal-cli.
|
||||
*/
|
||||
private static File getDefaultDataPath() {
|
||||
File dataPath = new File(IOUtils.getDataHomeDir(), "signal-cli");
|
||||
if (dataPath.exists()) {
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
File configPath = new File(System.getProperty("user.home"), ".config");
|
||||
|
||||
File legacySettingsPath = new File(configPath, "signal");
|
||||
if (legacySettingsPath.exists()) {
|
||||
return legacySettingsPath;
|
||||
}
|
||||
|
||||
legacySettingsPath = new File(configPath, "textsecure");
|
||||
if (legacySettingsPath.exists()) {
|
||||
return legacySettingsPath;
|
||||
}
|
||||
|
||||
return dataPath;
|
||||
}
|
||||
|
||||
private static Namespace parseArgs(String[] args) {
|
||||
ArgumentParser parser = buildArgumentParser();
|
||||
private static boolean isVerbose(String[] args) {
|
||||
var parser = ArgumentParsers.newFor("signal-cli").build().defaultHelp(false);
|
||||
parser.addArgument("--verbose").action(Arguments.storeTrue());
|
||||
|
||||
Namespace ns;
|
||||
try {
|
||||
ns = parser.parseArgs(args);
|
||||
ns = parser.parseKnownArgs(args, null);
|
||||
} catch (ArgumentParserException e) {
|
||||
parser.handleError(e);
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("link".equals(ns.getString("command"))) {
|
||||
if (ns.getString("username") != null) {
|
||||
parser.printUsage();
|
||||
System.err.println("You cannot specify a username (phone number) when linking");
|
||||
System.exit(2);
|
||||
}
|
||||
} else if (!ns.getBoolean("dbus") && !ns.getBoolean("dbus_system")) {
|
||||
if (ns.getString("username") == null) {
|
||||
parser.printUsage();
|
||||
System.err.println("You need to specify a username (phone number)");
|
||||
System.exit(2);
|
||||
}
|
||||
if (!PhoneNumberFormatter.isValidNumber(ns.getString("username"), null)) {
|
||||
System.err.println("Invalid username (phone number), make sure you include the country code.");
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
if (ns.getList("recipient") != null && !ns.getList("recipient").isEmpty() && ns.getString("group") != null) {
|
||||
System.err.println("You cannot specify recipients by phone number and groups at the same time");
|
||||
System.exit(2);
|
||||
}
|
||||
return ns;
|
||||
return ns.getBoolean("verbose");
|
||||
}
|
||||
|
||||
private static ArgumentParser buildArgumentParser() {
|
||||
ArgumentParser parser = ArgumentParsers.newFor("signal-cli")
|
||||
.build()
|
||||
.defaultHelp(true)
|
||||
.description("Commandline interface for Signal.")
|
||||
.version(BaseConfig.PROJECT_NAME + " " + BaseConfig.PROJECT_VERSION);
|
||||
|
||||
parser.addArgument("-v", "--version").help("Show package version.").action(Arguments.version());
|
||||
parser.addArgument("--config")
|
||||
.help("Set the path, where to store the config (Default: $XDG_DATA_HOME/signal-cli , $HOME/.local/share/signal-cli).");
|
||||
|
||||
MutuallyExclusiveGroup mut = parser.addMutuallyExclusiveGroup();
|
||||
mut.addArgument("-u", "--username").help("Specify your phone number, that will be used for verification.");
|
||||
mut.addArgument("--dbus").help("Make request via user dbus.").action(Arguments.storeTrue());
|
||||
mut.addArgument("--dbus-system").help("Make request via system dbus.").action(Arguments.storeTrue());
|
||||
|
||||
Subparsers subparsers = parser.addSubparsers()
|
||||
.title("subcommands")
|
||||
.dest("command")
|
||||
.description("valid subcommands")
|
||||
.help("additional help");
|
||||
|
||||
final Map<String, Command> commands = Commands.getCommands();
|
||||
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
||||
Subparser subparser = subparsers.addParser(entry.getKey());
|
||||
entry.getValue().attachToSubparser(subparser);
|
||||
private static void configureLogging(final boolean verbose) {
|
||||
if (verbose) {
|
||||
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
|
||||
System.setProperty("org.slf4j.simpleLogger.showThreadName", "true");
|
||||
System.setProperty("org.slf4j.simpleLogger.showShortLogName", "false");
|
||||
System.setProperty("org.slf4j.simpleLogger.showDateTime", "true");
|
||||
System.setProperty("org.slf4j.simpleLogger.dateTimeFormat", "yyyy-MM-dd'T'HH:mm:ss.SSSXX");
|
||||
LibSignalLogger.initLogger();
|
||||
} else {
|
||||
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
|
||||
System.setProperty("org.slf4j.simpleLogger.showThreadName", "false");
|
||||
System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true");
|
||||
System.setProperty("org.slf4j.simpleLogger.showDateTime", "false");
|
||||
}
|
||||
}
|
||||
|
||||
private static int getStatusForError(final CommandException e) {
|
||||
if (e instanceof UserErrorException) {
|
||||
return 1;
|
||||
} else if (e instanceof UnexpectedErrorException) {
|
||||
return 2;
|
||||
} else if (e instanceof IOErrorException) {
|
||||
return 3;
|
||||
} else if (e instanceof UntrustedKeyErrorException) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
|
|
16
src/main/java/org/asamk/signal/OutputType.java
Normal file
16
src/main/java/org/asamk/signal/OutputType.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
public enum OutputType {
|
||||
PLAIN_TEXT {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "plain-text";
|
||||
}
|
||||
},
|
||||
JSON {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "json";
|
||||
}
|
||||
},
|
||||
}
|
21
src/main/java/org/asamk/signal/PlainTextWriter.java
Normal file
21
src/main/java/org/asamk/signal/PlainTextWriter.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
public interface PlainTextWriter {
|
||||
|
||||
void println(String format, Object... args);
|
||||
|
||||
PlainTextWriter indentedWriter();
|
||||
|
||||
default void println() {
|
||||
println("");
|
||||
}
|
||||
|
||||
default void indent(final WriterConsumer subWriter) {
|
||||
subWriter.consume(indentedWriter());
|
||||
}
|
||||
|
||||
interface WriterConsumer {
|
||||
|
||||
void consume(PlainTextWriter writer);
|
||||
}
|
||||
}
|
75
src/main/java/org/asamk/signal/PlainTextWriterImpl.java
Normal file
75
src/main/java/org/asamk/signal/PlainTextWriterImpl.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
package org.asamk.signal;
|
||||
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
public final class PlainTextWriterImpl implements PlainTextWriter {
|
||||
|
||||
private final Writer writer;
|
||||
|
||||
private PlainTextWriter indentedWriter;
|
||||
|
||||
public PlainTextWriterImpl(final OutputStream outputStream) {
|
||||
this.writer = new BufferedWriter(new OutputStreamWriter(outputStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String format, Object... args) {
|
||||
final var message = MessageFormatter.arrayFormat(format, args).getMessage();
|
||||
|
||||
try {
|
||||
writer.write(message);
|
||||
writer.write(System.lineSeparator());
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainTextWriter indentedWriter() {
|
||||
if (indentedWriter == null) {
|
||||
indentedWriter = new IndentedPlainTextWriter(this, writer);
|
||||
}
|
||||
return indentedWriter;
|
||||
}
|
||||
|
||||
private static final class IndentedPlainTextWriter implements PlainTextWriter {
|
||||
|
||||
private final static int INDENTATION = 2;
|
||||
|
||||
private final String spaces = " ".repeat(INDENTATION);
|
||||
private final PlainTextWriter plainTextWriter;
|
||||
private final Writer writer;
|
||||
|
||||
private PlainTextWriter indentedWriter;
|
||||
|
||||
private IndentedPlainTextWriter(final PlainTextWriter plainTextWriter, final Writer writer) {
|
||||
this.plainTextWriter = plainTextWriter;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(final String format, final Object... args) {
|
||||
try {
|
||||
writer.write(spaces);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
plainTextWriter.println(format, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainTextWriter indentedWriter() {
|
||||
if (indentedWriter == null) {
|
||||
indentedWriter = new IndentedPlainTextWriter(this, writer);
|
||||
}
|
||||
return indentedWriter;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,13 @@ 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.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -14,6 +20,8 @@ import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
|||
|
||||
public class AddDeviceCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(AddDeviceCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("--uri")
|
||||
|
@ -22,23 +30,20 @@ public class AddDeviceCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
m.addDeviceLink(new URI(ns.getString("uri")));
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 3;
|
||||
} catch (InvalidKeyException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
return 2;
|
||||
logger.error("Add device link failed", e);
|
||||
throw new IOErrorException("Add device link failed");
|
||||
} catch (URISyntaxException e) {
|
||||
throw new UserErrorException("Device link uri has invalid format: {}" + e.getMessage());
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.error("Add device link failed", e);
|
||||
throw new UnexpectedErrorException("Add device link failed.");
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,18 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
public class BlockCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(BlockCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("contact").help("Contact number").nargs("*");
|
||||
|
@ -20,31 +23,24 @@ public class BlockCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (String contact_number : ns.<String>getList("contact")) {
|
||||
public void handleCommand(final Namespace ns, final Manager m) {
|
||||
for (var contact_number : ns.<String>getList("contact")) {
|
||||
try {
|
||||
m.setContactBlocked(contact_number, true);
|
||||
} catch (InvalidNumberException e) {
|
||||
System.err.println(e.getMessage());
|
||||
logger.warn("Invalid number {}: {}", contact_number, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (ns.<String>getList("group") != null) {
|
||||
for (String groupIdString : ns.<String>getList("group")) {
|
||||
for (var groupIdString : ns.<String>getList("group")) {
|
||||
try {
|
||||
GroupId groupId = Util.decodeGroupId(groupIdString);
|
||||
var groupId = Util.decodeGroupId(groupIdString);
|
||||
m.setGroupBlocked(groupId, true);
|
||||
} catch (GroupIdFormatException | GroupNotFoundException e) {
|
||||
System.err.println(e.getMessage());
|
||||
logger.warn("Invalid group id {}: {}", groupIdString, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,15 @@ package org.asamk.signal.commands;
|
|||
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.OutputType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface Command {
|
||||
|
||||
void attachToSubparser(Subparser subparser);
|
||||
|
||||
default Set<OutputType> getSupportedOutputTypes() {
|
||||
return Set.of(OutputType.PLAIN_TEXT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,11 @@ public class Commands {
|
|||
addCommand("addDevice", new AddDeviceCommand());
|
||||
addCommand("block", new BlockCommand());
|
||||
addCommand("daemon", new DaemonCommand());
|
||||
<<<<<<< HEAD
|
||||
addCommand("stdio", new StdioCommand());
|
||||
=======
|
||||
addCommand("getUserStatus", new GetUserStatusCommand());
|
||||
>>>>>>> upstream/master
|
||||
addCommand("link", new LinkCommand());
|
||||
addCommand("listContacts", new ListContactsCommand());
|
||||
addCommand("listDevices", new ListDevicesCommand());
|
||||
|
@ -22,26 +26,34 @@ public class Commands {
|
|||
addCommand("receive", new ReceiveCommand());
|
||||
addCommand("register", new RegisterCommand());
|
||||
addCommand("removeDevice", new RemoveDeviceCommand());
|
||||
addCommand("remoteDelete", new RemoteDeleteCommand());
|
||||
addCommand("removePin", new RemovePinCommand());
|
||||
addCommand("send", new SendCommand());
|
||||
addCommand("sendReaction", new SendReactionCommand());
|
||||
addCommand("sendContacts", new SendContactsCommand());
|
||||
addCommand("updateContact", new UpdateContactCommand());
|
||||
addCommand("sendReaction", new SendReactionCommand());
|
||||
addCommand("setPin", new SetPinCommand());
|
||||
addCommand("trust", new TrustCommand());
|
||||
addCommand("unblock", new UnblockCommand());
|
||||
addCommand("unregister", new UnregisterCommand());
|
||||
addCommand("updateAccount", new UpdateAccountCommand());
|
||||
addCommand("updateContact", new UpdateContactCommand());
|
||||
addCommand("updateGroup", new UpdateGroupCommand());
|
||||
addCommand("updateProfile", new UpdateProfileCommand());
|
||||
addCommand("verify", new VerifyCommand());
|
||||
addCommand("uploadStickerPack", new UploadStickerPackCommand());
|
||||
addCommand("verify", new VerifyCommand());
|
||||
}
|
||||
|
||||
public static Map<String, Command> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public static Command getCommand(String commandKey) {
|
||||
if (!commands.containsKey(commandKey)) {
|
||||
return null;
|
||||
}
|
||||
return commands.get(commandKey);
|
||||
}
|
||||
|
||||
private static void addCommand(String name, Command command) {
|
||||
commands.put(name, command);
|
||||
}
|
||||
|
|
|
@ -4,21 +4,28 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.DbusConfig;
|
||||
import org.asamk.signal.DbusReceiveMessageHandler;
|
||||
import org.asamk.signal.JsonDbusReceiveMessageHandler;
|
||||
import org.asamk.signal.OutputType;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.dbus.DbusSignalImpl;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.asamk.signal.DbusConfig.SIGNAL_BUSNAME;
|
||||
import static org.asamk.signal.DbusConfig.SIGNAL_OBJECTPATH;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
public class DaemonCommand implements MultiLocalCommand {
|
||||
|
||||
public class DaemonCommand implements LocalCommand {
|
||||
private final static Logger logger = LoggerFactory.getLogger(DaemonCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
|
@ -29,56 +36,114 @@ public class DaemonCommand implements LocalCommand {
|
|||
.help("Don’t download attachments of received messages.")
|
||||
.action(Arguments.storeTrue());
|
||||
subparser.addArgument("--json")
|
||||
.help("Output received messages in json format, one json object per line.")
|
||||
.help("WARNING: This parameter is now deprecated! Please use the global \"--output=json\" option instead.\n\nOutput received messages in json format, one json object per line.")
|
||||
.action(Arguments.storeTrue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public Set<OutputType> getSupportedOutputTypes() {
|
||||
return Set.of(OutputType.PLAIN_TEXT, OutputType.JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
|
||||
|
||||
// TODO delete later when "json" variable is removed
|
||||
if (ns.getBoolean("json")) {
|
||||
logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
|
||||
}
|
||||
DBusConnection conn = null;
|
||||
try {
|
||||
|
||||
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
|
||||
|
||||
DBusConnection.DBusBusType busType;
|
||||
if (ns.getBoolean("system")) {
|
||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||
} else {
|
||||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
|
||||
try (var conn = DBusConnection.getConnection(busType)) {
|
||||
var objectPath = DbusConfig.getObjectPath();
|
||||
var t = run(conn, objectPath, m, ignoreAttachments, inJson);
|
||||
|
||||
conn.requestBusName(DbusConfig.getBusname());
|
||||
|
||||
try {
|
||||
DBusConnection.DBusBusType busType;
|
||||
if (ns.getBoolean("system")) {
|
||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||
} else {
|
||||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
conn = DBusConnection.getConnection(busType);
|
||||
conn.exportObject(SIGNAL_OBJECTPATH, new DbusSignalImpl(m));
|
||||
conn.requestBusName(SIGNAL_BUSNAME);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
|
||||
return 1;
|
||||
} catch (DBusException e) {
|
||||
e.printStackTrace();
|
||||
return 2;
|
||||
}
|
||||
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
|
||||
try {
|
||||
m.receiveMessages(1,
|
||||
TimeUnit.HOURS,
|
||||
false,
|
||||
ignoreAttachments,
|
||||
ns.getBoolean("json")
|
||||
? new JsonDbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH)
|
||||
: new DbusReceiveMessageHandler(m, conn, SIGNAL_OBJECTPATH));
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error while receiving messages: " + e.getMessage());
|
||||
return 3;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
}
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
t.join();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
} catch (DBusException | IOException e) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UnexpectedErrorException("Dbus command failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final List<Manager> managers) throws CommandException {
|
||||
var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
|
||||
|
||||
// TODO delete later when "json" variable is removed
|
||||
if (ns.getBoolean("json")) {
|
||||
logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
|
||||
}
|
||||
|
||||
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
|
||||
|
||||
DBusConnection.DBusBusType busType;
|
||||
if (ns.getBoolean("system")) {
|
||||
busType = DBusConnection.DBusBusType.SYSTEM;
|
||||
} else {
|
||||
busType = DBusConnection.DBusBusType.SESSION;
|
||||
}
|
||||
|
||||
try (var conn = DBusConnection.getConnection(busType)) {
|
||||
var receiveThreads = new ArrayList<Thread>();
|
||||
for (var m : managers) {
|
||||
var objectPath = DbusConfig.getObjectPath(m.getUsername());
|
||||
var thread = run(conn, objectPath, m, ignoreAttachments, inJson);
|
||||
receiveThreads.add(thread);
|
||||
}
|
||||
|
||||
conn.requestBusName(DbusConfig.getBusname());
|
||||
|
||||
for (var t : receiveThreads) {
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (DBusException | IOException e) {
|
||||
logger.error("Dbus command failed", e);
|
||||
throw new UnexpectedErrorException("Dbus command failed");
|
||||
}
|
||||
}
|
||||
|
||||
private Thread run(
|
||||
DBusConnection conn, String objectPath, Manager m, boolean ignoreAttachments, boolean inJson
|
||||
) throws DBusException {
|
||||
conn.exportObject(objectPath, new DbusSignalImpl(m));
|
||||
|
||||
final var thread = new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
m.receiveMessages(1,
|
||||
TimeUnit.HOURS,
|
||||
false,
|
||||
ignoreAttachments,
|
||||
inJson
|
||||
? new JsonDbusReceiveMessageHandler(m, conn, objectPath)
|
||||
: new DbusReceiveMessageHandler(m, conn, objectPath));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Receiving messages failed, retrying", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("Exported dbus object: " + objectPath);
|
||||
|
||||
thread.start();
|
||||
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,15 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.dbus.DbusSignalImpl;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
|
||||
public interface DbusCommand extends Command {
|
||||
public interface DbusCommand extends LocalCommand {
|
||||
|
||||
int handleCommand(Namespace ns, Signal signal);
|
||||
void handleCommand(Namespace ns, Signal signal) throws CommandException;
|
||||
|
||||
default void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
handleCommand(ns, new DbusSignalImpl(m));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
|
||||
public interface ExtendedDbusCommand extends Command {
|
||||
|
||||
int handleCommand(Namespace ns, Signal signal, DBusConnection dbusconnection);
|
||||
void handleCommand(Namespace ns, Signal signal, DBusConnection dbusconnection) throws CommandException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.JsonWriter;
|
||||
import org.asamk.signal.OutputType;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GetUserStatusCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(GetUserStatusCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("number").help("Phone number").nargs("+");
|
||||
subparser.help("Check if the specified phone number/s have been registered");
|
||||
subparser.addArgument("--json")
|
||||
.help("WARNING: This parameter is now deprecated! Please use the global \"--output=json\" option instead.\n\nOutput received messages in json format, one json object per line.")
|
||||
.action(Arguments.storeTrue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OutputType> getSupportedOutputTypes() {
|
||||
return Set.of(OutputType.PLAIN_TEXT, OutputType.JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
// Setup the json object mapper
|
||||
var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
|
||||
|
||||
// TODO delete later when "json" variable is removed
|
||||
if (ns.getBoolean("json")) {
|
||||
logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
|
||||
}
|
||||
|
||||
// Get a map of registration statuses
|
||||
Map<String, Boolean> registered;
|
||||
try {
|
||||
registered = m.areUsersRegistered(new HashSet<>(ns.getList("number")));
|
||||
} catch (IOException e) {
|
||||
logger.debug("Failed to check registered users", e);
|
||||
throw new IOErrorException("Unable to check if users are registered");
|
||||
}
|
||||
|
||||
// Output
|
||||
if (inJson) {
|
||||
final var jsonWriter = new JsonWriter(System.out);
|
||||
|
||||
var jsonUserStatuses = registered.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new JsonUserStatus(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
jsonWriter.write(jsonUserStatuses);
|
||||
} else {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
for (var entry : registered.entrySet()) {
|
||||
writer.println("{}: {}", entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JsonUserStatus {
|
||||
|
||||
public String name;
|
||||
|
||||
public boolean isRegistered;
|
||||
|
||||
public JsonUserStatus(String name, boolean isRegistered) {
|
||||
this.name = name;
|
||||
this.isRegistered = isRegistered;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,21 +3,20 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupInviteLinkUrl;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
|
||||
|
||||
public class JoinGroupCommand implements LocalCommand {
|
||||
|
@ -28,57 +27,43 @@ public class JoinGroupCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
final GroupInviteLinkUrl linkUrl;
|
||||
String uri = ns.getString("uri");
|
||||
var uri = ns.getString("uri");
|
||||
try {
|
||||
linkUrl = GroupInviteLinkUrl.fromUri(uri);
|
||||
} catch (GroupInviteLinkUrl.InvalidGroupLinkException e) {
|
||||
System.err.println("Group link is invalid: " + e.getMessage());
|
||||
return 2;
|
||||
throw new UserErrorException("Group link is invalid: " + e.getMessage());
|
||||
} catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
|
||||
System.err.println("Group link was created with an incompatible version: " + e.getMessage());
|
||||
return 2;
|
||||
throw new UserErrorException("Group link was created with an incompatible version: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (linkUrl == null) {
|
||||
System.err.println("Link is not a signal group invitation link");
|
||||
return 2;
|
||||
throw new UserErrorException("Link is not a signal group invitation link");
|
||||
}
|
||||
|
||||
try {
|
||||
final Pair<GroupId, List<SendMessageResult>> results = m.joinGroup(linkUrl);
|
||||
GroupId newGroupId = results.first();
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
final var results = m.joinGroup(linkUrl);
|
||||
var newGroupId = results.first();
|
||||
if (!m.getGroup(newGroupId).isMember(m.getSelfAddress())) {
|
||||
System.out.println("Requested to join group \"" + newGroupId.toBase64() + "\"");
|
||||
writer.println("Requested to join group \"{}\"", newGroupId.toBase64());
|
||||
} else {
|
||||
System.out.println("Joined group \"" + newGroupId.toBase64() + "\"");
|
||||
writer.println("Joined group \"{}\"", newGroupId.toBase64());
|
||||
}
|
||||
return handleTimestampAndSendMessageResults(0, results.second());
|
||||
handleTimestampAndSendMessageResults(writer, 0, results.second());
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (GroupPatchNotAcceptedException e) {
|
||||
System.err.println("Failed to join group, maybe already a member");
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to join group, maybe already a member");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
handleIOException(e);
|
||||
return 1;
|
||||
} catch (Signal.Error.AttachmentInvalid e) {
|
||||
System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
|
||||
return 1;
|
||||
throw new IOErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
return 1;
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (GroupLinkNotActiveException e) {
|
||||
System.err.println("Group link is not valid: " + e.getMessage());
|
||||
return 2;
|
||||
throw new UserErrorException("Group link is not valid: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,15 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
import org.asamk.signal.manager.UserAlreadyExists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -14,41 +21,42 @@ import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
|||
|
||||
public class LinkCommand implements ProvisioningCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(LinkCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("-n", "--name").help("Specify a name to describe this new device.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final ProvisioningManager m) {
|
||||
String deviceName = ns.getString("name");
|
||||
public void handleCommand(final Namespace ns, final ProvisioningManager m) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
var deviceName = ns.getString("name");
|
||||
if (deviceName == null) {
|
||||
deviceName = "cli";
|
||||
}
|
||||
try {
|
||||
System.out.println(m.getDeviceLinkUri());
|
||||
String username = m.finishDeviceLink(deviceName);
|
||||
System.out.println("Associated with: " + username);
|
||||
writer.println("{}", m.getDeviceLinkUri());
|
||||
try (var manager = m.finishDeviceLink(deviceName)) {
|
||||
writer.println("Associated with: {}", manager.getUsername());
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
System.err.println("Link request timed out, please try again.");
|
||||
return 3;
|
||||
throw new UserErrorException("Link request timed out, please try again.");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Link request error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Link request error: " + e.getMessage());
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
return 2;
|
||||
logger.debug("Finish device link failed", e);
|
||||
throw new UnexpectedErrorException("Invalid key: " + e.getMessage());
|
||||
} catch (UserAlreadyExists e) {
|
||||
System.err.println("The user "
|
||||
throw new UserErrorException("The user "
|
||||
+ e.getUsername()
|
||||
+ " already exists\nDelete \""
|
||||
+ e.getFileName()
|
||||
+ "\" before trying again.");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.storage.contacts.ContactInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ListContactsCommand implements LocalCommand {
|
||||
|
||||
|
@ -15,15 +13,12 @@ public class ListContactsCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public void handleCommand(final Namespace ns, final Manager m) {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
var contacts = m.getContacts();
|
||||
for (var c : contacts) {
|
||||
writer.println("Number: {} Name: {} Blocked: {}", c.number, c.name, c.blocked);
|
||||
}
|
||||
List<ContactInfo> contacts = m.getContacts();
|
||||
for (ContactInfo c : contacts) {
|
||||
System.out.println(String.format("Number: %s Name: %s Blocked: %b", c.number, c.name, c.blocked));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,13 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.util.DateUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -12,31 +17,31 @@ import java.util.List;
|
|||
|
||||
public class ListDevicesCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ListDevicesCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
List<DeviceInfo> devices;
|
||||
try {
|
||||
List<DeviceInfo> devices = m.getLinkedDevices();
|
||||
for (DeviceInfo d : devices) {
|
||||
System.out.println("Device "
|
||||
+ d.getId()
|
||||
+ (d.getId() == m.getDeviceId() ? " (this device)" : "")
|
||||
+ ":");
|
||||
System.out.println(" Name: " + d.getName());
|
||||
System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated()));
|
||||
System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen()));
|
||||
}
|
||||
return 0;
|
||||
devices = m.getLinkedDevices();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 3;
|
||||
logger.debug("Failed to get linked devices", e);
|
||||
throw new IOErrorException("Failed to get linked devices: " + e.getMessage());
|
||||
}
|
||||
|
||||
for (var d : devices) {
|
||||
writer.println("- Device {}{}:", d.getId(), (d.getId() == m.getDeviceId() ? " (this device)" : ""));
|
||||
writer.indent(w -> {
|
||||
w.println("Name: {}", d.getName());
|
||||
w.println("Created: {}", DateUtils.formatTimestamp(d.getCreated()));
|
||||
w.println("Last seen: {}", DateUtils.formatTimestamp(d.getLastSeen()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,77 +4,131 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.GroupInviteLinkUrl;
|
||||
import org.asamk.signal.JsonWriter;
|
||||
import org.asamk.signal.OutputType;
|
||||
import org.asamk.signal.PlainTextWriter;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.storage.groups.GroupInfo;
|
||||
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ListGroupsCommand implements LocalCommand {
|
||||
|
||||
private static void printGroup(Manager m, GroupInfo group, boolean detailed) {
|
||||
private final static Logger logger = LoggerFactory.getLogger(ListGroupsCommand.class);
|
||||
|
||||
private static Set<String> resolveMembers(Manager m, Set<SignalServiceAddress> addresses) {
|
||||
return addresses.stream()
|
||||
.map(m::resolveSignalServiceAddress)
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private static void printGroupPlainText(
|
||||
PlainTextWriter writer, Manager m, GroupInfo group, boolean detailed
|
||||
) {
|
||||
if (detailed) {
|
||||
Set<String> members = group.getMembers()
|
||||
.stream()
|
||||
.map(m::resolveSignalServiceAddress)
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toSet());
|
||||
final var groupInviteLink = group.getGroupInviteLink();
|
||||
|
||||
Set<String> pendingMembers = group.getPendingMembers()
|
||||
.stream()
|
||||
.map(m::resolveSignalServiceAddress)
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> requestingMembers = group.getRequestingMembers()
|
||||
.stream()
|
||||
.map(m::resolveSignalServiceAddress)
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final GroupInviteLinkUrl groupInviteLink = group.getGroupInviteLink();
|
||||
|
||||
System.out.println(String.format(
|
||||
"Id: %s Name: %s Active: %s Blocked: %b Members: %s Pending members: %s Requesting members: %s Link: %s",
|
||||
writer.println(
|
||||
"Id: {} Name: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Link: {}",
|
||||
group.getGroupId().toBase64(),
|
||||
group.getTitle(),
|
||||
group.isMember(m.getSelfAddress()),
|
||||
group.isBlocked(),
|
||||
members,
|
||||
pendingMembers,
|
||||
requestingMembers,
|
||||
groupInviteLink == null ? '-' : groupInviteLink.getUrl()));
|
||||
resolveMembers(m, group.getMembers()),
|
||||
resolveMembers(m, group.getPendingMembers()),
|
||||
resolveMembers(m, group.getRequestingMembers()),
|
||||
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
|
||||
} else {
|
||||
System.out.println(String.format("Id: %s Name: %s Active: %s Blocked: %b",
|
||||
writer.println("Id: {} Name: {} Active: {} Blocked: {}",
|
||||
group.getGroupId().toBase64(),
|
||||
group.getTitle(),
|
||||
group.isMember(m.getSelfAddress()),
|
||||
group.isBlocked()));
|
||||
group.isBlocked());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("-d", "--detailed").action(Arguments.storeTrue()).help("List members of each group");
|
||||
subparser.help("List group name and ids");
|
||||
subparser.addArgument("-d", "--detailed")
|
||||
.action(Arguments.storeTrue())
|
||||
.help("List the members and group invite links of each group. If output=json, then this is always set");
|
||||
|
||||
subparser.help("List group information including names, ids, active status, blocked status and members");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public Set<OutputType> getSupportedOutputTypes() {
|
||||
return Set.of(OutputType.PLAIN_TEXT, OutputType.JSON);
|
||||
}
|
||||
|
||||
List<GroupInfo> groups = m.getGroups();
|
||||
boolean detailed = ns.getBoolean("detailed");
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
if (ns.get("output") == OutputType.JSON) {
|
||||
final var jsonWriter = new JsonWriter(System.out);
|
||||
|
||||
for (GroupInfo group : groups) {
|
||||
printGroup(m, group, detailed);
|
||||
var jsonGroups = new ArrayList<JsonGroup>();
|
||||
for (var group : m.getGroups()) {
|
||||
final var groupInviteLink = group.getGroupInviteLink();
|
||||
|
||||
jsonGroups.add(new JsonGroup(group.getGroupId().toBase64(),
|
||||
group.getTitle(),
|
||||
group.isMember(m.getSelfAddress()),
|
||||
group.isBlocked(),
|
||||
resolveMembers(m, group.getMembers()),
|
||||
resolveMembers(m, group.getPendingMembers()),
|
||||
resolveMembers(m, group.getRequestingMembers()),
|
||||
groupInviteLink == null ? null : groupInviteLink.getUrl()));
|
||||
}
|
||||
|
||||
jsonWriter.write(jsonGroups);
|
||||
} else {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
boolean detailed = ns.getBoolean("detailed");
|
||||
for (var group : m.getGroups()) {
|
||||
printGroupPlainText(writer, m, group, detailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class JsonGroup {
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
public boolean isMember;
|
||||
public boolean isBlocked;
|
||||
|
||||
public Set<String> members;
|
||||
public Set<String> pendingMembers;
|
||||
public Set<String> requestingMembers;
|
||||
public String groupInviteLink;
|
||||
|
||||
public JsonGroup(
|
||||
String id,
|
||||
String name,
|
||||
boolean isMember,
|
||||
boolean isBlocked,
|
||||
Set<String> members,
|
||||
Set<String> pendingMembers,
|
||||
Set<String> requestingMembers,
|
||||
String groupInviteLink
|
||||
) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.isMember = isMember;
|
||||
this.isBlocked = isBlocked;
|
||||
|
||||
this.members = members;
|
||||
this.pendingMembers = pendingMembers;
|
||||
this.requestingMembers = requestingMembers;
|
||||
this.groupInviteLink = groupInviteLink;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,24 +3,32 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.PlainTextWriter;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
|
||||
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
|
||||
import org.asamk.signal.util.Hex;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ListIdentitiesCommand implements LocalCommand {
|
||||
|
||||
private static void printIdentityFingerprint(Manager m, JsonIdentityKeyStore.Identity theirId) {
|
||||
String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey()));
|
||||
System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s",
|
||||
private final static Logger logger = LoggerFactory.getLogger(ListIdentitiesCommand.class);
|
||||
|
||||
private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, IdentityInfo theirId) {
|
||||
var digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirId.getAddress(), theirId.getIdentityKey()));
|
||||
writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
|
||||
theirId.getAddress().getNumber().orNull(),
|
||||
theirId.getTrustLevel(),
|
||||
theirId.getDateAdded(),
|
||||
Hex.toString(theirId.getFingerprint()),
|
||||
digits));
|
||||
digits);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,26 +37,27 @@ public class ListIdentitiesCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
if (ns.get("number") == null) {
|
||||
for (JsonIdentityKeyStore.Identity identity : m.getIdentities()) {
|
||||
printIdentityFingerprint(m, identity);
|
||||
}
|
||||
} else {
|
||||
String number = ns.getString("number");
|
||||
try {
|
||||
List<JsonIdentityKeyStore.Identity> identities = m.getIdentities(number);
|
||||
for (JsonIdentityKeyStore.Identity id : identities) {
|
||||
printIdentityFingerprint(m, id);
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
System.err.println("Invalid number: " + e.getMessage());
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
var number = ns.getString("number");
|
||||
|
||||
if (number == null) {
|
||||
for (var identity : m.getIdentities()) {
|
||||
printIdentityFingerprint(writer, m, identity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<IdentityInfo> identities;
|
||||
try {
|
||||
identities = m.getIdentities(number);
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new UserErrorException("Invalid number: " + e.getMessage());
|
||||
}
|
||||
|
||||
for (var id : identities) {
|
||||
printIdentityFingerprint(writer, m, id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package org.asamk.signal.commands;
|
|||
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
|
||||
public interface LocalCommand extends Command {
|
||||
|
||||
int handleCommand(Namespace ns, Manager m);
|
||||
void handleCommand(Namespace ns, Manager m) throws CommandException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MultiLocalCommand extends LocalCommand {
|
||||
|
||||
void handleCommand(Namespace ns, List<Manager> m) throws CommandException;
|
||||
|
||||
@Override
|
||||
default void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
handleCommand(ns, List.of(m));
|
||||
}
|
||||
}
|
|
@ -2,9 +2,10 @@ package org.asamk.signal.commands;
|
|||
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.ProvisioningManager;
|
||||
|
||||
public interface ProvisioningCommand extends Command {
|
||||
|
||||
int handleCommand(Namespace ns, ProvisioningManager m);
|
||||
void handleCommand(Namespace ns, ProvisioningManager m) throws CommandException;
|
||||
}
|
||||
|
|
|
@ -3,23 +3,20 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
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.NotAGroupMemberException;
|
||||
import org.asamk.signal.manager.groups.GroupId;
|
||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
|
||||
|
||||
public class QuitGroupCommand implements LocalCommand {
|
||||
|
@ -30,31 +27,28 @@ public class QuitGroupCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
final GroupId groupId;
|
||||
try {
|
||||
groupId = Util.decodeGroupId(ns.getString("group"));
|
||||
} catch (GroupIdFormatException e) {
|
||||
throw new UserErrorException("Invalid group id:" + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
final GroupId groupId = Util.decodeGroupId(ns.getString("group"));
|
||||
final Pair<Long, List<SendMessageResult>> results = m.sendQuitGroupMessage(groupId);
|
||||
return handleTimestampAndSendMessageResults(results.first(), results.second());
|
||||
final var results = m.sendQuitGroupMessage(groupId);
|
||||
handleTimestampAndSendMessageResults(writer, results.first(), results.second());
|
||||
} catch (IOException e) {
|
||||
handleIOException(e);
|
||||
return 3;
|
||||
throw new IOErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (GroupNotFoundException e) {
|
||||
handleGroupNotFoundException(e);
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||
} catch (NotAGroupMemberException e) {
|
||||
handleNotAGroupMemberException(e);
|
||||
return 1;
|
||||
} catch (GroupIdFormatException e) {
|
||||
handleGroupIdFormatException(e);
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.JsonReceiveMessageHandler;
|
||||
import org.asamk.signal.JsonWriter;
|
||||
import org.asamk.signal.OutputType;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.ReceiveMessageHandler;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.json.JsonMessageEnvelope;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.util.DateUtils;
|
||||
import org.freedesktop.dbus.connections.impl.DBusConnection;
|
||||
import org.freedesktop.dbus.exceptions.DBusException;
|
||||
import org.whispersystems.util.Base64;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
|
||||
public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ReceiveCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("-t", "--timeout")
|
||||
|
@ -36,146 +42,136 @@ public class ReceiveCommand implements ExtendedDbusCommand, LocalCommand {
|
|||
.help("Don’t download attachments of received messages.")
|
||||
.action(Arguments.storeTrue());
|
||||
subparser.addArgument("--json")
|
||||
.help("Output received messages in json format, one json object per line.")
|
||||
.help("WARNING: This parameter is now deprecated! Please use the global \"--output=json\" option instead.\n\nOutput received messages in json format, one json object per line.")
|
||||
.action(Arguments.storeTrue());
|
||||
}
|
||||
|
||||
public int handleCommand(final Namespace ns, final Signal signal, DBusConnection dbusconnection) {
|
||||
final ObjectMapper jsonProcessor;
|
||||
@Override
|
||||
public Set<OutputType> getSupportedOutputTypes() {
|
||||
return Set.of(OutputType.PLAIN_TEXT, OutputType.JSON);
|
||||
}
|
||||
|
||||
public void handleCommand(
|
||||
final Namespace ns, final Signal signal, DBusConnection dbusconnection
|
||||
) throws CommandException {
|
||||
var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
|
||||
|
||||
// TODO delete later when "json" variable is removed
|
||||
if (ns.getBoolean("json")) {
|
||||
jsonProcessor = new ObjectMapper();
|
||||
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // disable autodetect
|
||||
jsonProcessor.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
|
||||
} else {
|
||||
jsonProcessor = null;
|
||||
logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
|
||||
}
|
||||
|
||||
try {
|
||||
dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> {
|
||||
if (jsonProcessor != null) {
|
||||
JsonMessageEnvelope envelope = new JsonMessageEnvelope(messageReceived);
|
||||
ObjectNode result = jsonProcessor.createObjectNode();
|
||||
result.putPOJO("envelope", envelope);
|
||||
try {
|
||||
jsonProcessor.writeValue(System.out, result);
|
||||
System.out.println();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n",
|
||||
messageReceived.getSender(),
|
||||
DateUtils.formatTimestamp(messageReceived.getTimestamp()),
|
||||
messageReceived.getMessage()));
|
||||
if (inJson) {
|
||||
final var jsonWriter = new JsonWriter(System.out);
|
||||
|
||||
dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> {
|
||||
var envelope = new JsonMessageEnvelope(messageReceived);
|
||||
final var object = Map.of("envelope", envelope);
|
||||
jsonWriter.write(object);
|
||||
});
|
||||
|
||||
dbusconnection.addSigHandler(Signal.ReceiptReceived.class, receiptReceived -> {
|
||||
var envelope = new JsonMessageEnvelope(receiptReceived);
|
||||
final var object = Map.of("envelope", envelope);
|
||||
jsonWriter.write(object);
|
||||
});
|
||||
|
||||
dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> {
|
||||
var envelope = new JsonMessageEnvelope(syncReceived);
|
||||
final var object = Map.of("envelope", envelope);
|
||||
jsonWriter.write(object);
|
||||
});
|
||||
} else {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
dbusconnection.addSigHandler(Signal.MessageReceived.class, messageReceived -> {
|
||||
writer.println("Envelope from: {}", messageReceived.getSender());
|
||||
writer.println("Timestamp: {}", DateUtils.formatTimestamp(messageReceived.getTimestamp()));
|
||||
writer.println("Body: {}", messageReceived.getMessage());
|
||||
if (messageReceived.getGroupId().length > 0) {
|
||||
System.out.println("Group info:");
|
||||
System.out.println(" Id: " + Base64.encodeBytes(messageReceived.getGroupId()));
|
||||
writer.println("Group info:");
|
||||
writer.indentedWriter()
|
||||
.println("Id: {}", Base64.getEncoder().encodeToString(messageReceived.getGroupId()));
|
||||
}
|
||||
if (messageReceived.getAttachments().size() > 0) {
|
||||
System.out.println("Attachments: ");
|
||||
for (String attachment : messageReceived.getAttachments()) {
|
||||
System.out.println("- Stored plaintext in: " + attachment);
|
||||
writer.println("Attachments:");
|
||||
for (var attachment : messageReceived.getAttachments()) {
|
||||
writer.println("- Stored plaintext in: {}", attachment);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
});
|
||||
writer.println();
|
||||
});
|
||||
|
||||
dbusconnection.addSigHandler(Signal.ReceiptReceived.class, receiptReceived -> {
|
||||
if (jsonProcessor != null) {
|
||||
JsonMessageEnvelope envelope = new JsonMessageEnvelope(receiptReceived);
|
||||
ObjectNode result = jsonProcessor.createObjectNode();
|
||||
result.putPOJO("envelope", envelope);
|
||||
try {
|
||||
jsonProcessor.writeValue(System.out, result);
|
||||
System.out.println();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n",
|
||||
receiptReceived.getSender(),
|
||||
DateUtils.formatTimestamp(receiptReceived.getTimestamp())));
|
||||
}
|
||||
});
|
||||
dbusconnection.addSigHandler(Signal.ReceiptReceived.class, receiptReceived -> {
|
||||
writer.println("Receipt from: {}", receiptReceived.getSender());
|
||||
writer.println("Timestamp: {}", DateUtils.formatTimestamp(receiptReceived.getTimestamp()));
|
||||
});
|
||||
|
||||
dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> {
|
||||
if (jsonProcessor != null) {
|
||||
JsonMessageEnvelope envelope = new JsonMessageEnvelope(syncReceived);
|
||||
ObjectNode result = jsonProcessor.createObjectNode();
|
||||
result.putPOJO("envelope", envelope);
|
||||
try {
|
||||
jsonProcessor.writeValue(System.out, result);
|
||||
System.out.println();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.out.print(String.format("Sync Envelope from: %s to: %s\nTimestamp: %s\nBody: %s\n",
|
||||
dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, syncReceived -> {
|
||||
writer.println("Sync Envelope from: {} to: {}",
|
||||
syncReceived.getSource(),
|
||||
syncReceived.getDestination(),
|
||||
DateUtils.formatTimestamp(syncReceived.getTimestamp()),
|
||||
syncReceived.getMessage()));
|
||||
syncReceived.getDestination());
|
||||
writer.println("Timestamp: {}", DateUtils.formatTimestamp(syncReceived.getTimestamp()));
|
||||
writer.println("Body: {}", syncReceived.getMessage());
|
||||
if (syncReceived.getGroupId().length > 0) {
|
||||
System.out.println("Group info:");
|
||||
System.out.println(" Id: " + Base64.encodeBytes(syncReceived.getGroupId()));
|
||||
writer.println("Group info:");
|
||||
writer.indentedWriter()
|
||||
.println("Id: {}", Base64.getEncoder().encodeToString(syncReceived.getGroupId()));
|
||||
}
|
||||
if (syncReceived.getAttachments().size() > 0) {
|
||||
System.out.println("Attachments: ");
|
||||
for (String attachment : syncReceived.getAttachments()) {
|
||||
System.out.println("- Stored plaintext in: " + attachment);
|
||||
writer.println("Attachments:");
|
||||
for (var attachment : syncReceived.getAttachments()) {
|
||||
writer.println("- Stored plaintext in: {}", attachment);
|
||||
}
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
});
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
System.err.println("Missing native library dependency for dbus service: " + e.getMessage());
|
||||
return 1;
|
||||
writer.println();
|
||||
});
|
||||
}
|
||||
} catch (DBusException e) {
|
||||
e.printStackTrace();
|
||||
return 1;
|
||||
logger.error("Dbus client failed", e);
|
||||
throw new UnexpectedErrorException("Dbus client failed");
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
return 0;
|
||||
} catch (InterruptedException ignored) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
var inJson = ns.get("output") == OutputType.JSON || ns.getBoolean("json");
|
||||
|
||||
// TODO delete later when "json" variable is removed
|
||||
if (ns.getBoolean("json")) {
|
||||
logger.warn("\"--json\" option has been deprecated, please use the global \"--output=json\" instead.");
|
||||
}
|
||||
|
||||
double timeout = 5;
|
||||
if (ns.getDouble("timeout") != null) {
|
||||
timeout = ns.getDouble("timeout");
|
||||
}
|
||||
boolean returnOnTimeout = true;
|
||||
var returnOnTimeout = true;
|
||||
if (timeout < 0) {
|
||||
returnOnTimeout = false;
|
||||
timeout = 3600;
|
||||
}
|
||||
boolean ignoreAttachments = ns.getBoolean("ignore_attachments");
|
||||
try {
|
||||
final Manager.ReceiveMessageHandler handler = ns.getBoolean("json")
|
||||
? new JsonReceiveMessageHandler(m)
|
||||
: new ReceiveMessageHandler(m);
|
||||
final var handler = inJson ? new JsonReceiveMessageHandler(m) : new ReceiveMessageHandler(m);
|
||||
m.receiveMessages((long) (timeout * 1000),
|
||||
TimeUnit.MILLISECONDS,
|
||||
returnOnTimeout,
|
||||
ignoreAttachments,
|
||||
handler);
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error while receiving messages: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Error while receiving messages: " + e.getMessage());
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
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.RegistrationManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RegisterCommand implements LocalCommand {
|
||||
public class RegisterCommand implements RegistrationCommand {
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
|
@ -21,18 +24,25 @@ public class RegisterCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException {
|
||||
final boolean voiceVerification = ns.getBoolean("voice");
|
||||
final var captcha = ns.getString("captcha");
|
||||
|
||||
try {
|
||||
final boolean voiceVerification = ns.getBoolean("voice");
|
||||
final String captcha = ns.getString("captcha");
|
||||
m.register(voiceVerification, captcha);
|
||||
return 0;
|
||||
} catch (CaptchaRequiredException e) {
|
||||
System.err.println("Captcha invalid or required for verification (" + e.getMessage() + ")");
|
||||
return 1;
|
||||
String message;
|
||||
if (captcha == null) {
|
||||
message = "Captcha required for verification, use --captcha CAPTCHA\n"
|
||||
+ "To get the token, go to https://signalcaptchas.org/registration/generate.html\n"
|
||||
+ "Check the developer tools (F12) console for a failed redirect to signalcaptcha://\n"
|
||||
+ "Everything after signalcaptcha:// is the captcha token.";
|
||||
} else {
|
||||
message = "Invalid captcha given.";
|
||||
}
|
||||
throw new UserErrorException(message);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Request verify error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Request verify error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.RegistrationManager;
|
||||
|
||||
public interface RegistrationCommand extends Command {
|
||||
|
||||
void handleCommand(Namespace ns, RegistrationManager m) throws CommandException;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
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.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.freedesktop.dbus.errors.UnknownObject;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
|
||||
public class RemoteDeleteCommand implements DbusCommand {
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.help("Remotely delete a previously sent message.");
|
||||
subparser.addArgument("-t", "--target-timestamp")
|
||||
.required(true)
|
||||
.type(long.class)
|
||||
.help("Specify the timestamp of the message to delete.");
|
||||
subparser.addArgument("-g", "--group")
|
||||
.help("Specify the recipient group ID.");
|
||||
subparser.addArgument("recipient")
|
||||
.help("Specify the recipients' phone number.").nargs("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
|
||||
final List<String> recipients = ns.getList("recipient");
|
||||
final var groupIdString = ns.getString("group");
|
||||
|
||||
final var noRecipients = recipients == null || recipients.isEmpty();
|
||||
if (noRecipients && groupIdString == null) {
|
||||
throw new UserErrorException("No recipients given");
|
||||
}
|
||||
if (!noRecipients && groupIdString != null) {
|
||||
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
|
||||
}
|
||||
|
||||
final long targetTimestamp = ns.getLong("target_timestamp");
|
||||
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
byte[] groupId = null;
|
||||
if (groupIdString != null) {
|
||||
try {
|
||||
groupId = Util.decodeGroupId(groupIdString).serialize();
|
||||
} catch (GroupIdFormatException e) {
|
||||
throw new UserErrorException("Invalid group id: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
long timestamp;
|
||||
if (groupId != null) {
|
||||
timestamp = signal.sendGroupRemoteDeleteMessage(targetTimestamp, groupId);
|
||||
} else {
|
||||
timestamp = signal.sendRemoteDeleteMessage(targetTimestamp, recipients);
|
||||
}
|
||||
writer.println("{}", timestamp);
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
throw e;
|
||||
} catch (UnknownObject e) {
|
||||
throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
|
||||
} catch (Signal.Error.InvalidNumber e) {
|
||||
throw new UserErrorException("Invalid number: " + e.getMessage());
|
||||
} catch (Signal.Error.GroupNotFound e) {
|
||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ 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.manager.Manager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -18,18 +20,12 @@ public class RemoveDeviceCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
int deviceId = ns.getInt("deviceId");
|
||||
m.removeLinkedDevices(deviceId);
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 3;
|
||||
throw new IOErrorException("Error while removing device: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@ 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.UnexpectedErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -15,17 +19,13 @@ public class RemovePinCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
m.setRegistrationLockPin(Optional.absent());
|
||||
return 0;
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
throw new UnexpectedErrorException("Remove pin failed with unauthenticated response: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Remove pin error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Remove pin error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,25 +5,38 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
|||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.util.IOUtils;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.freedesktop.dbus.errors.UnknownObject;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||
|
||||
public class SendCommand implements DbusCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SendCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("-g", "--group").help("Specify the recipient group ID.");
|
||||
subparser.addArgument("recipient").help("Specify the recipients' phone number.").nargs("*");
|
||||
final var mutuallyExclusiveGroup = subparser.addMutuallyExclusiveGroup();
|
||||
mutuallyExclusiveGroup.addArgument("-g", "--group").help("Specify the recipient group ID.");
|
||||
mutuallyExclusiveGroup.addArgument("--note-to-self")
|
||||
.help("Send the message to self without notification.")
|
||||
.action(Arguments.storeTrue());
|
||||
|
||||
subparser.addArgument("-m", "--message").help("Specify the message, if missing standard input is used.");
|
||||
subparser.addArgument("-a", "--attachment").nargs("*").help("Add file as attachment");
|
||||
subparser.addArgument("-e", "--endsession")
|
||||
|
@ -32,82 +45,108 @@ public class SendCommand implements DbusCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Signal signal) {
|
||||
if (!signal.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
|
||||
final List<String> recipients = ns.getList("recipient");
|
||||
final var isEndSession = ns.getBoolean("endsession");
|
||||
final var groupIdString = ns.getString("group");
|
||||
final var isNoteToSelf = ns.getBoolean("note_to_self");
|
||||
|
||||
final var noRecipients = recipients == null || recipients.isEmpty();
|
||||
if ((noRecipients && isEndSession) || (noRecipients && groupIdString == null && !isNoteToSelf)) {
|
||||
throw new UserErrorException("No recipients given");
|
||||
}
|
||||
if (!noRecipients && groupIdString != null) {
|
||||
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
|
||||
}
|
||||
if (!noRecipients && isNoteToSelf) {
|
||||
throw new UserErrorException(
|
||||
"You cannot specify recipients by phone number and not to self at the same time");
|
||||
}
|
||||
|
||||
if ((ns.getList("recipient") == null || ns.getList("recipient").size() == 0) && (
|
||||
ns.getBoolean("endsession") || ns.getString("group") == null
|
||||
)) {
|
||||
System.err.println("No recipients given");
|
||||
System.err.println("Aborting sending.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ns.getBoolean("endsession")) {
|
||||
if (isEndSession) {
|
||||
try {
|
||||
signal.sendEndSessionMessage(ns.getList("recipient"));
|
||||
return 0;
|
||||
signal.sendEndSessionMessage(recipients);
|
||||
return;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (Signal.Error.UntrustedIdentity e) {
|
||||
throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
return 1;
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String messageText = ns.getString("message");
|
||||
var messageText = ns.getString("message");
|
||||
if (messageText == null) {
|
||||
try {
|
||||
messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to read message from stdin: " + e.getMessage());
|
||||
System.err.println("Aborting sending.");
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to read message from stdin: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
List<String> attachments = ns.getList("attachment");
|
||||
if (attachments == null) {
|
||||
attachments = new ArrayList<>();
|
||||
attachments = List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
if (ns.getString("group") != null) {
|
||||
byte[] groupId;
|
||||
try {
|
||||
groupId = Util.decodeGroupId(ns.getString("group")).serialize();
|
||||
} catch (GroupIdFormatException e) {
|
||||
handleGroupIdFormatException(e);
|
||||
return 1;
|
||||
}
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
long timestamp = signal.sendGroupMessage(messageText, attachments, groupId);
|
||||
System.out.println(timestamp);
|
||||
return 0;
|
||||
if (groupIdString != null) {
|
||||
byte[] groupId;
|
||||
try {
|
||||
groupId = Util.decodeGroupId(groupIdString).serialize();
|
||||
} catch (GroupIdFormatException e) {
|
||||
throw new UserErrorException("Invalid group id: " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
var timestamp = signal.sendGroupMessage(messageText, attachments, groupId);
|
||||
writer.println("{}", timestamp);
|
||||
return;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
throw e;
|
||||
} catch (DBusExecutionException e) {
|
||||
throw new UnexpectedErrorException("Failed to send group message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (isNoteToSelf) {
|
||||
try {
|
||||
var timestamp = signal.sendNoteToSelfMessage(messageText, attachments);
|
||||
writer.println("{}", timestamp);
|
||||
return;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
throw e;
|
||||
} catch (Signal.Error.UntrustedIdentity e) {
|
||||
throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage());
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
} catch (DBusExecutionException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
<<<<<<< HEAD
|
||||
System.out.println(Arrays.toString(ns.getList("recipient").toArray()));
|
||||
long timestamp = signal.sendMessage(messageText, attachments, ns.getList("recipient"));
|
||||
System.out.println(timestamp);
|
||||
return 0;
|
||||
=======
|
||||
var timestamp = signal.sendMessage(messageText, attachments, recipients);
|
||||
writer.println("{}", timestamp);
|
||||
>>>>>>> upstream/master
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (UnknownObject e) {
|
||||
throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
|
||||
} catch (Signal.Error.UntrustedIdentity e) {
|
||||
throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
return 1;
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ 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.UntrustedKeyErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
|
||||
|
@ -16,17 +19,13 @@ public class SendContactsCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
m.sendContacts();
|
||||
return 0;
|
||||
} catch (IOException | UntrustedIdentityException e) {
|
||||
System.err.println("SendContacts error: " + e.getMessage());
|
||||
return 3;
|
||||
} catch (UntrustedIdentityException e) {
|
||||
throw new UntrustedKeyErrorException("SendContacts error: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new IOErrorException("SendContacts error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,28 +4,21 @@ import net.sourceforge.argparse4j.impl.Arguments;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.NotAGroupMemberException;
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
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.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.freedesktop.dbus.errors.UnknownObject;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupNotFoundException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleIOException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleInvalidNumberException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleNotAGroupMemberException;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResults;
|
||||
|
||||
public class SendReactionCommand implements LocalCommand {
|
||||
public class SendReactionCommand implements DbusCommand {
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
|
@ -46,54 +39,53 @@ public class SendReactionCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
|
||||
final List<String> recipients = ns.getList("recipient");
|
||||
final var groupIdString = ns.getString("group");
|
||||
|
||||
final var noRecipients = recipients == null || recipients.isEmpty();
|
||||
if (noRecipients && groupIdString == null) {
|
||||
throw new UserErrorException("No recipients given");
|
||||
}
|
||||
if (!noRecipients && groupIdString != null) {
|
||||
throw new UserErrorException("You cannot specify recipients by phone number and groups at the same time");
|
||||
}
|
||||
|
||||
if ((ns.getList("recipient") == null || ns.getList("recipient").size() == 0) && ns.getString("group") == null) {
|
||||
System.err.println("No recipients given");
|
||||
System.err.println("Aborting sending.");
|
||||
return 1;
|
||||
}
|
||||
final var emoji = ns.getString("emoji");
|
||||
final boolean isRemove = ns.getBoolean("remove");
|
||||
final var targetAuthor = ns.getString("target_author");
|
||||
final long targetTimestamp = ns.getLong("target_timestamp");
|
||||
|
||||
String emoji = ns.getString("emoji");
|
||||
boolean isRemove = ns.getBoolean("remove");
|
||||
String targetAuthor = ns.getString("target_author");
|
||||
long targetTimestamp = ns.getLong("target_timestamp");
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
|
||||
byte[] groupId = null;
|
||||
if (groupIdString != null) {
|
||||
try {
|
||||
groupId = Util.decodeGroupId(groupIdString).serialize();
|
||||
} catch (GroupIdFormatException e) {
|
||||
throw new UserErrorException("Invalid group id: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final Pair<Long, List<SendMessageResult>> results;
|
||||
if (ns.getString("group") != null) {
|
||||
GroupId groupId = Util.decodeGroupId(ns.getString("group"));
|
||||
results = m.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId);
|
||||
long timestamp;
|
||||
if (groupId != null) {
|
||||
timestamp = signal.sendGroupMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, groupId);
|
||||
} else {
|
||||
results = m.sendMessageReaction(emoji,
|
||||
isRemove,
|
||||
targetAuthor,
|
||||
targetTimestamp,
|
||||
ns.getList("recipient"));
|
||||
timestamp = signal.sendMessageReaction(emoji, isRemove, targetAuthor, targetTimestamp, recipients);
|
||||
}
|
||||
return handleTimestampAndSendMessageResults(results.first(), results.second());
|
||||
} catch (IOException e) {
|
||||
handleIOException(e);
|
||||
return 3;
|
||||
writer.println("{}", timestamp);
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
} catch (GroupNotFoundException e) {
|
||||
handleGroupNotFoundException(e);
|
||||
return 1;
|
||||
} catch (NotAGroupMemberException e) {
|
||||
handleNotAGroupMemberException(e);
|
||||
return 1;
|
||||
} catch (GroupIdFormatException e) {
|
||||
handleGroupIdFormatException(e);
|
||||
return 1;
|
||||
} catch (InvalidNumberException e) {
|
||||
handleInvalidNumberException(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (UnknownObject e) {
|
||||
throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage());
|
||||
} catch (Signal.Error.InvalidNumber e) {
|
||||
throw new UserErrorException("Invalid number: " + e.getMessage());
|
||||
} catch (Signal.Error.GroupNotFound e) {
|
||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@ 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.UnexpectedErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -17,18 +21,14 @@ public class SetPinCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
String registrationLockPin = ns.getString("registrationLockPin");
|
||||
var registrationLockPin = ns.getString("registrationLockPin");
|
||||
m.setRegistrationLockPin(Optional.of(registrationLockPin));
|
||||
return 0;
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
throw new UnexpectedErrorException("Set pin error failed with unauthenticated response: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Set pin error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Set pin error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
|
||||
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.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.util.ErrorUtils;
|
||||
import org.asamk.signal.util.Hex;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
|
@ -17,7 +17,7 @@ public class TrustCommand implements LocalCommand {
|
|||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("number").help("Specify the phone number, for which to set the trust.").required(true);
|
||||
MutuallyExclusiveGroup mutTrust = subparser.addMutuallyExclusiveGroup();
|
||||
var mutTrust = subparser.addMutuallyExclusiveGroup();
|
||||
mutTrust.addArgument("-a", "--trust-all-known-keys")
|
||||
.help("Trust all known keys of this user, only use this for testing.")
|
||||
.action(Arguments.storeTrue());
|
||||
|
@ -26,20 +26,15 @@ public class TrustCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
String number = ns.getString("number");
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
var number = ns.getString("number");
|
||||
if (ns.getBoolean("trust_all_known_keys")) {
|
||||
boolean res = m.trustIdentityAllKeys(number);
|
||||
var res = m.trustIdentityAllKeys(number);
|
||||
if (!res) {
|
||||
System.err.println("Failed to set the trust for this number, make sure the number is correct.");
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to set the trust for this number, make sure the number is correct.");
|
||||
}
|
||||
} else {
|
||||
String safetyNumber = ns.getString("verified_safety_number");
|
||||
var safetyNumber = ns.getString("verified_safety_number");
|
||||
if (safetyNumber != null) {
|
||||
safetyNumber = safetyNumber.replaceAll(" ", "");
|
||||
if (safetyNumber.length() == 66) {
|
||||
|
@ -47,46 +42,38 @@ public class TrustCommand implements LocalCommand {
|
|||
try {
|
||||
fingerprintBytes = Hex.toByteArray(safetyNumber.toLowerCase(Locale.ROOT));
|
||||
} catch (Exception e) {
|
||||
System.err.println(
|
||||
throw new UserErrorException(
|
||||
"Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
|
||||
return 1;
|
||||
}
|
||||
boolean res;
|
||||
try {
|
||||
res = m.trustIdentityVerified(number, fingerprintBytes);
|
||||
} catch (InvalidNumberException e) {
|
||||
ErrorUtils.handleInvalidNumberException(e);
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
|
||||
}
|
||||
if (!res) {
|
||||
System.err.println(
|
||||
throw new UserErrorException(
|
||||
"Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
|
||||
return 1;
|
||||
}
|
||||
} else if (safetyNumber.length() == 60) {
|
||||
boolean res;
|
||||
try {
|
||||
res = m.trustIdentityVerifiedSafetyNumber(number, safetyNumber);
|
||||
} catch (InvalidNumberException e) {
|
||||
ErrorUtils.handleInvalidNumberException(e);
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to parse recipient: " + e.getMessage());
|
||||
}
|
||||
if (!res) {
|
||||
System.err.println(
|
||||
throw new UserErrorException(
|
||||
"Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
System.err.println(
|
||||
throw new UserErrorException(
|
||||
"Safety number has invalid format, either specify the old hex fingerprint or the new safety number");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
System.err.println(
|
||||
throw new UserErrorException(
|
||||
"You need to specify the fingerprint/safety number you have verified with -v SAFETY_NUMBER");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,19 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
public class UnblockCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(UnblockCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("contact").help("Contact number").nargs("*");
|
||||
|
@ -20,31 +24,26 @@ public class UnblockCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (String contact_number : ns.<String>getList("contact")) {
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
for (var contactNumber : ns.<String>getList("contact")) {
|
||||
try {
|
||||
m.setContactBlocked(contact_number, false);
|
||||
m.setContactBlocked(contactNumber, false);
|
||||
} catch (InvalidNumberException e) {
|
||||
System.err.println(e.getMessage());
|
||||
logger.warn("Invalid number: {}", contactNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if (ns.<String>getList("group") != null) {
|
||||
for (String groupIdString : ns.<String>getList("group")) {
|
||||
for (var groupIdString : ns.<String>getList("group")) {
|
||||
try {
|
||||
GroupId groupId = Util.decodeGroupId(groupIdString);
|
||||
var groupId = Util.decodeGroupId(groupIdString);
|
||||
m.setGroupBlocked(groupId, false);
|
||||
} catch (GroupIdFormatException | GroupNotFoundException e) {
|
||||
System.err.println(e.getMessage());
|
||||
} catch (GroupIdFormatException e) {
|
||||
logger.warn("Invalid group id: {}", groupIdString);
|
||||
} catch (GroupNotFoundException e) {
|
||||
logger.warn("Unknown group id: {}", groupIdString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ 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.manager.Manager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -15,17 +17,11 @@ public class UnregisterCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
m.unregister();
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Unregister error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Unregister error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ 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.manager.Manager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -15,17 +17,11 @@ public class UpdateAccountCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
try {
|
||||
m.updateAccountAttributes();
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
System.err.println("UpdateAccount error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("UpdateAccount error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ 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.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
|
@ -22,30 +25,21 @@ public class UpdateContactCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
String number = ns.getString("number");
|
||||
String name = ns.getString("name");
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
var number = ns.getString("number");
|
||||
var name = ns.getString("name");
|
||||
|
||||
try {
|
||||
m.setContactName(number, name);
|
||||
|
||||
Integer expiration = ns.getInt("expiration");
|
||||
var expiration = ns.getInt("expiration");
|
||||
if (expiration != null) {
|
||||
m.setExpirationTimer(number, expiration);
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
System.err.println("Invalid contact number: " + e.getMessage());
|
||||
return 1;
|
||||
throw new UserErrorException("Invalid contact number: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Update contact error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Update contact error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,26 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
|||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
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.manager.groups.GroupIdFormatException;
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.whispersystems.util.Base64;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static org.asamk.signal.util.ErrorUtils.handleAssertionError;
|
||||
import static org.asamk.signal.util.ErrorUtils.handleGroupIdFormatException;
|
||||
|
||||
public class UpdateGroupCommand implements DbusCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(UpdateGroupCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("-g", "--group").help("Specify the recipient group ID.");
|
||||
|
@ -26,26 +33,21 @@ public class UpdateGroupCommand implements DbusCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Signal signal) {
|
||||
if (!signal.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void handleCommand(final Namespace ns, final Signal signal) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
byte[] groupId = null;
|
||||
if (ns.getString("group") != null) {
|
||||
try {
|
||||
groupId = Util.decodeGroupId(ns.getString("group")).serialize();
|
||||
} catch (GroupIdFormatException e) {
|
||||
handleGroupIdFormatException(e);
|
||||
return 1;
|
||||
throw new UserErrorException("Invalid group id:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (groupId == null) {
|
||||
groupId = new byte[0];
|
||||
}
|
||||
|
||||
String groupName = ns.getString("name");
|
||||
var groupName = ns.getString("name");
|
||||
if (groupName == null) {
|
||||
groupName = "";
|
||||
}
|
||||
|
@ -55,26 +57,23 @@ public class UpdateGroupCommand implements DbusCommand {
|
|||
groupMembers = new ArrayList<>();
|
||||
}
|
||||
|
||||
String groupAvatar = ns.getString("avatar");
|
||||
var groupAvatar = ns.getString("avatar");
|
||||
if (groupAvatar == null) {
|
||||
groupAvatar = "";
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] newGroupId = signal.updateGroup(groupId, groupName, groupMembers, groupAvatar);
|
||||
var newGroupId = signal.updateGroup(groupId, groupName, groupMembers, groupAvatar);
|
||||
if (groupId.length != newGroupId.length) {
|
||||
System.out.println("Creating new group \"" + Base64.encodeBytes(newGroupId) + "\" …");
|
||||
writer.println("Created new group: \"{}\"", Base64.getEncoder().encodeToString(newGroupId));
|
||||
}
|
||||
return 0;
|
||||
} catch (AssertionError e) {
|
||||
handleAssertionError(e);
|
||||
return 1;
|
||||
throw e;
|
||||
} catch (Signal.Error.AttachmentInvalid e) {
|
||||
System.err.println("Failed to add avatar attachment for group\": " + e.getMessage());
|
||||
return 1;
|
||||
throw new UserErrorException("Failed to add avatar attachment for group\": " + e.getMessage());
|
||||
} catch (DBusExecutionException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
return 1;
|
||||
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.impl.Arguments;
|
||||
import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
|
||||
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.manager.Manager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -14,34 +16,33 @@ public class UpdateProfileCommand implements LocalCommand {
|
|||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
final MutuallyExclusiveGroup avatarOptions = subparser.addMutuallyExclusiveGroup().required(true);
|
||||
subparser.addArgument("--name").help("New profile name");
|
||||
subparser.addArgument("--about").help("New profile about text");
|
||||
subparser.addArgument("--about-emoji").help("New profile about emoji");
|
||||
|
||||
final var avatarOptions = subparser.addMutuallyExclusiveGroup();
|
||||
avatarOptions.addArgument("--avatar").help("Path to new profile avatar");
|
||||
avatarOptions.addArgument("--remove-avatar").action(Arguments.storeTrue());
|
||||
|
||||
subparser.addArgument("--name").required(true).help("New profile name");
|
||||
|
||||
subparser.help("Set a name and avatar image for the user profile");
|
||||
subparser.help("Set a name, about and avatar image for the user profile");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (!m.isRegistered()) {
|
||||
System.err.println("User is not registered.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
String name = ns.getString("name");
|
||||
String avatarPath = ns.getString("avatar");
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
var name = ns.getString("name");
|
||||
var about = ns.getString("about");
|
||||
var aboutEmoji = ns.getString("about_emoji");
|
||||
var avatarPath = ns.getString("avatar");
|
||||
boolean removeAvatar = ns.getBoolean("remove_avatar");
|
||||
|
||||
try {
|
||||
File avatarFile = removeAvatar ? null : new File(avatarPath);
|
||||
m.setProfile(name, avatarFile);
|
||||
} catch (IOException e) {
|
||||
System.err.println("UpdateAccount error: " + e.getMessage());
|
||||
return 3;
|
||||
}
|
||||
Optional<File> avatarFile = removeAvatar
|
||||
? Optional.absent()
|
||||
: avatarPath == null ? null : Optional.of(new File(avatarPath));
|
||||
|
||||
return 0;
|
||||
try {
|
||||
m.setProfile(name, about, aboutEmoji, avatarFile);
|
||||
} catch (IOException e) {
|
||||
throw new IOErrorException("Update profile error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,22 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.PlainTextWriterImpl;
|
||||
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.StickerPackInvalidException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class UploadStickerPackCommand implements LocalCommand {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(UploadStickerPackCommand.class);
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
subparser.addArgument("path")
|
||||
|
@ -18,18 +26,17 @@ public class UploadStickerPackCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
public void handleCommand(final Namespace ns, final Manager m) throws CommandException {
|
||||
final var writer = new PlainTextWriterImpl(System.out);
|
||||
var path = new File(ns.getString("path"));
|
||||
|
||||
try {
|
||||
File path = new File(ns.getString("path"));
|
||||
String url = m.uploadStickerPack(path);
|
||||
System.out.println(url);
|
||||
return 0;
|
||||
var url = m.uploadStickerPack(path);
|
||||
writer.println("{}", url);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Upload error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Upload error: " + e.getMessage());
|
||||
} catch (StickerPackInvalidException e) {
|
||||
System.err.println("Invalid sticker pack: " + e.getMessage());
|
||||
return 3;
|
||||
throw new UserErrorException("Invalid sticker pack: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,18 @@ package org.asamk.signal.commands;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UnexpectedErrorException;
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.RegistrationManager;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class VerifyCommand implements LocalCommand {
|
||||
public class VerifyCommand implements RegistrationCommand {
|
||||
|
||||
@Override
|
||||
public void attachToSubparser(final Subparser subparser) {
|
||||
|
@ -17,24 +23,24 @@ public class VerifyCommand implements LocalCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int handleCommand(final Namespace ns, final Manager m) {
|
||||
if (m.isRegistered()) {
|
||||
System.err.println("User registration is already verified");
|
||||
return 1;
|
||||
}
|
||||
public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException {
|
||||
var verificationCode = ns.getString("verificationCode");
|
||||
var pin = ns.getString("pin");
|
||||
|
||||
try {
|
||||
String verificationCode = ns.getString("verificationCode");
|
||||
String pin = ns.getString("pin");
|
||||
m.verifyAccount(verificationCode, pin);
|
||||
return 0;
|
||||
final var manager = m.verifyAccount(verificationCode, pin);
|
||||
manager.close();
|
||||
} catch (LockedException e) {
|
||||
System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: "
|
||||
+ (e.getTimeRemaining() / 1000 / 60 / 60));
|
||||
System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN");
|
||||
return 3;
|
||||
throw new UserErrorException(
|
||||
"Verification failed! This number is locked with a pin. Hours remaining until reset: "
|
||||
+ (e.getTimeRemaining() / 1000 / 60 / 60)
|
||||
+ "\nUse '--pin PIN_CODE' to specify the registration lock PIN");
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining());
|
||||
} catch (KeyBackupSystemNoDataException e) {
|
||||
throw new UnexpectedErrorException("Verification failed! No KBS data.");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Verify error: " + e.getMessage());
|
||||
return 3;
|
||||
throw new IOErrorException("Verify error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public class CommandException extends Exception {
|
||||
|
||||
public CommandException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public final class IOErrorException extends CommandException {
|
||||
|
||||
public IOErrorException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public final class UnexpectedErrorException extends CommandException {
|
||||
|
||||
public UnexpectedErrorException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public final class UntrustedKeyErrorException extends CommandException {
|
||||
|
||||
public UntrustedKeyErrorException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.asamk.signal.commands.exceptions;
|
||||
|
||||
public final class UserErrorException extends CommandException {
|
||||
|
||||
public UserErrorException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,24 +1,28 @@
|
|||
package org.asamk.signal.dbus;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.BaseConfig;
|
||||
import org.asamk.signal.manager.AttachmentInvalidException;
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.NotAGroupMemberException;
|
||||
import org.asamk.signal.storage.groups.GroupInfo;
|
||||
import org.asamk.signal.manager.groups.GroupId;
|
||||
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
|
||||
import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||
import org.asamk.signal.util.ErrorUtils;
|
||||
import org.freedesktop.dbus.exceptions.DBusExecutionException;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DbusSignalImpl implements Signal {
|
||||
|
||||
|
@ -40,23 +44,44 @@ public class DbusSignalImpl implements Signal {
|
|||
|
||||
@Override
|
||||
public long sendMessage(final String message, final List<String> attachments, final String recipient) {
|
||||
List<String> recipients = new ArrayList<>(1);
|
||||
var recipients = new ArrayList<String>(1);
|
||||
recipients.add(recipient);
|
||||
return sendMessage(message, attachments, recipients);
|
||||
}
|
||||
|
||||
private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException {
|
||||
var error = ErrorUtils.getErrorMessageFromSendMessageResult(result);
|
||||
|
||||
if (error == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var message = timestamp + "\nFailed to send message:\n" + error + '\n';
|
||||
|
||||
if (result.getIdentityFailure() != null) {
|
||||
throw new Error.UntrustedIdentity(message);
|
||||
} else {
|
||||
throw new Error.Failure(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkSendMessageResults(
|
||||
long timestamp, List<SendMessageResult> results
|
||||
) throws DBusExecutionException {
|
||||
List<String> errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
|
||||
if (results.size() == 1) {
|
||||
checkSendMessageResult(timestamp, results.get(0));
|
||||
return;
|
||||
}
|
||||
|
||||
var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
|
||||
if (errors.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
var message = new StringBuilder();
|
||||
message.append(timestamp).append('\n');
|
||||
message.append("Failed to send (some) messages:\n");
|
||||
for (String error : errors) {
|
||||
for (var error : errors) {
|
||||
message.append(error).append('\n');
|
||||
}
|
||||
|
||||
|
@ -66,7 +91,7 @@ public class DbusSignalImpl implements Signal {
|
|||
@Override
|
||||
public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
|
||||
try {
|
||||
final Pair<Long, List<SendMessageResult>> results = m.sendMessage(message, attachments, recipients);
|
||||
final var results = m.sendMessage(message, attachments, recipients);
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (InvalidNumberException e) {
|
||||
|
@ -78,10 +103,88 @@ public class DbusSignalImpl implements Signal {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendRemoteDeleteMessage(
|
||||
final long targetSentTimestamp, final String recipient
|
||||
) {
|
||||
var recipients = new ArrayList<String>(1);
|
||||
recipients.add(recipient);
|
||||
return sendRemoteDeleteMessage(targetSentTimestamp, recipients);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendRemoteDeleteMessage(
|
||||
final long targetSentTimestamp, final List<String> recipients
|
||||
) {
|
||||
try {
|
||||
final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, recipients);
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new Error.InvalidNumber(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendGroupRemoteDeleteMessage(
|
||||
final long targetSentTimestamp, final byte[] groupId
|
||||
) {
|
||||
try {
|
||||
final var results = m.sendGroupRemoteDeleteMessage(targetSentTimestamp, GroupId.unknownVersion(groupId));
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
} catch (GroupNotFoundException | NotAGroupMemberException e) {
|
||||
throw new Error.GroupNotFound(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendMessageReaction(
|
||||
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final String recipient
|
||||
) {
|
||||
var recipients = new ArrayList<String>(1);
|
||||
recipients.add(recipient);
|
||||
return sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendMessageReaction(
|
||||
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final List<String> recipients
|
||||
) {
|
||||
try {
|
||||
final var results = m.sendMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, recipients);
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new Error.InvalidNumber(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendNoteToSelfMessage(
|
||||
final String message, final List<String> attachments
|
||||
) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
|
||||
try {
|
||||
final var results = m.sendSelfMessage(message, attachments);
|
||||
checkSendMessageResult(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (AttachmentInvalidException e) {
|
||||
throw new Error.AttachmentInvalid(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEndSessionMessage(final List<String> recipients) {
|
||||
try {
|
||||
final Pair<Long, List<SendMessageResult>> results = m.sendEndSessionMessage(recipients);
|
||||
final var results = m.sendEndSessionMessage(recipients);
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
|
@ -93,9 +196,7 @@ public class DbusSignalImpl implements Signal {
|
|||
@Override
|
||||
public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
|
||||
try {
|
||||
Pair<Long, List<SendMessageResult>> results = m.sendGroupMessage(message,
|
||||
attachments,
|
||||
GroupId.unknownVersion(groupId));
|
||||
var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId));
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (IOException e) {
|
||||
|
@ -107,11 +208,30 @@ public class DbusSignalImpl implements Signal {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sendGroupMessageReaction(
|
||||
final String emoji, final boolean remove, final String targetAuthor, final long targetSentTimestamp, final byte[] groupId
|
||||
) {
|
||||
try {
|
||||
final var results = m.sendGroupMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, GroupId.unknownVersion(groupId));
|
||||
checkSendMessageResults(results.first(), results.second());
|
||||
return results.first();
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
} catch (InvalidNumberException e) {
|
||||
throw new Error.InvalidNumber(e.getMessage());
|
||||
} catch (GroupNotFoundException | NotAGroupMemberException e) {
|
||||
throw new Error.GroupNotFound(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Since contact names might be empty if not defined, also potentially return
|
||||
// the profile name
|
||||
@Override
|
||||
public String getContactName(final String number) {
|
||||
try {
|
||||
return m.getContactName(number);
|
||||
} catch (InvalidNumberException e) {
|
||||
return m.getContactOrProfileName(number);
|
||||
} catch (Exception e) {
|
||||
throw new Error.InvalidNumber(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -145,9 +265,9 @@ public class DbusSignalImpl implements Signal {
|
|||
|
||||
@Override
|
||||
public List<byte[]> getGroupIds() {
|
||||
List<GroupInfo> groups = m.getGroups();
|
||||
List<byte[]> ids = new ArrayList<>(groups.size());
|
||||
for (GroupInfo group : groups) {
|
||||
var groups = m.getGroups();
|
||||
var ids = new ArrayList<byte[]>(groups.size());
|
||||
for (var group : groups) {
|
||||
ids.add(group.getGroupId().serialize());
|
||||
}
|
||||
return ids;
|
||||
|
@ -155,7 +275,7 @@ public class DbusSignalImpl implements Signal {
|
|||
|
||||
@Override
|
||||
public String getGroupName(final byte[] groupId) {
|
||||
GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
var group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
if (group == null) {
|
||||
return "";
|
||||
} else {
|
||||
|
@ -165,9 +285,9 @@ public class DbusSignalImpl implements Signal {
|
|||
|
||||
@Override
|
||||
public List<String> getGroupMembers(final byte[] groupId) {
|
||||
GroupInfo group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
var group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
if (group == null) {
|
||||
return Collections.emptyList();
|
||||
return List.of();
|
||||
} else {
|
||||
return group.getMembers()
|
||||
.stream()
|
||||
|
@ -192,9 +312,10 @@ public class DbusSignalImpl implements Signal {
|
|||
if (avatar.isEmpty()) {
|
||||
avatar = null;
|
||||
}
|
||||
final Pair<GroupId, List<SendMessageResult>> results = m.updateGroup(groupId == null
|
||||
? null
|
||||
: GroupId.unknownVersion(groupId), name, members, avatar);
|
||||
final var results = m.updateGroup(groupId == null ? null : GroupId.unknownVersion(groupId),
|
||||
name,
|
||||
members,
|
||||
avatar == null ? null : new File(avatar));
|
||||
checkSendMessageResults(0, results.second());
|
||||
return results.first().serialize();
|
||||
} catch (IOException e) {
|
||||
|
@ -212,4 +333,127 @@ public class DbusSignalImpl implements Signal {
|
|||
public boolean isRegistered() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProfile(
|
||||
final String name,
|
||||
final String about,
|
||||
final String aboutEmoji,
|
||||
String avatarPath,
|
||||
final boolean removeAvatar
|
||||
) {
|
||||
try {
|
||||
if (avatarPath.isEmpty()) {
|
||||
avatarPath = null;
|
||||
}
|
||||
Optional<File> avatarFile = removeAvatar
|
||||
? Optional.absent()
|
||||
: avatarPath == null ? null : Optional.of(new File(avatarPath));
|
||||
m.setProfile(name, about, aboutEmoji, avatarFile);
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Provide option to query a version string in order to react on potential
|
||||
// future interface changes
|
||||
@Override
|
||||
public String version() {
|
||||
return BaseConfig.PROJECT_VERSION;
|
||||
}
|
||||
|
||||
// Create a unique list of Numbers from Identities and Contacts to really get
|
||||
// all numbers the system knows
|
||||
@Override
|
||||
public List<String> listNumbers() {
|
||||
return Stream.concat(m.getIdentities().stream().map(i -> i.getAddress().getNumber().orNull()),
|
||||
m.getContacts().stream().map(c -> c.number))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getContactNumber(final String name) {
|
||||
// Contact names have precedence.
|
||||
var numbers = new ArrayList<String>();
|
||||
var contacts = m.getContacts();
|
||||
for (var c : contacts) {
|
||||
if (c.name != null && c.name.equals(name)) {
|
||||
numbers.add(c.number);
|
||||
}
|
||||
}
|
||||
// Try profiles if no contact name was found
|
||||
for (var identity : m.getIdentities()) {
|
||||
final var address = identity.getAddress();
|
||||
var number = address.getNumber().orNull();
|
||||
if (number != null) {
|
||||
var profile = m.getRecipientProfile(address);
|
||||
if (profile != null && profile.getDisplayName().equals(name)) {
|
||||
numbers.add(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quitGroup(final byte[] groupId) {
|
||||
var group = GroupId.unknownVersion(groupId);
|
||||
try {
|
||||
m.sendQuitGroupMessage(group);
|
||||
} catch (GroupNotFoundException | NotAGroupMemberException e) {
|
||||
throw new Error.GroupNotFound(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(final String groupLink) {
|
||||
try {
|
||||
final var linkUrl = GroupInviteLinkUrl.fromUri(groupLink);
|
||||
if (linkUrl == null) {
|
||||
throw new Error.Failure("Group link is invalid:");
|
||||
}
|
||||
m.joinGroup(linkUrl);
|
||||
} catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupLinkNotActiveException e) {
|
||||
throw new Error.Failure("Group link is invalid: " + e.getMessage());
|
||||
} catch (GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
|
||||
throw new Error.Failure("Group link was created with an incompatible version: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
throw new Error.Failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContactBlocked(final String number) {
|
||||
var contacts = m.getContacts();
|
||||
for (var c : contacts) {
|
||||
if (c.number.equals(number)) {
|
||||
return c.blocked;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGroupBlocked(final byte[] groupId) {
|
||||
var group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
if (group == null) {
|
||||
return false;
|
||||
} else {
|
||||
return group.isBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMember(final byte[] groupId) {
|
||||
var group = m.getGroup(GroupId.unknownVersion(groupId));
|
||||
if (group == null) {
|
||||
return false;
|
||||
} else {
|
||||
return group.isMember(m.getSelfAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,43 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
|
||||
class JsonAttachment {
|
||||
|
||||
String contentType;
|
||||
String filename;
|
||||
String id;
|
||||
int size;
|
||||
@JsonProperty
|
||||
final String contentType;
|
||||
|
||||
@JsonProperty
|
||||
final String filename;
|
||||
|
||||
@JsonProperty
|
||||
final String id;
|
||||
|
||||
@JsonProperty
|
||||
final Long size;
|
||||
|
||||
JsonAttachment(SignalServiceAttachment attachment) {
|
||||
this.contentType = attachment.getContentType();
|
||||
|
||||
final SignalServiceAttachmentPointer pointer = attachment.asPointer();
|
||||
if (attachment.isPointer()) {
|
||||
this.id = String.valueOf(pointer.getRemoteId());
|
||||
if (pointer.getFileName().isPresent()) {
|
||||
this.filename = pointer.getFileName().get();
|
||||
}
|
||||
if (pointer.getSize().isPresent()) {
|
||||
this.size = pointer.getSize().get();
|
||||
}
|
||||
final var pointer = attachment.asPointer();
|
||||
this.id = pointer.getRemoteId().toString();
|
||||
this.filename = pointer.getFileName().orNull();
|
||||
this.size = pointer.getSize().transform(Integer::longValue).orNull();
|
||||
} else {
|
||||
final var stream = attachment.asStream();
|
||||
this.id = null;
|
||||
this.filename = stream.getFileName().orNull();
|
||||
this.size = stream.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
JsonAttachment(String filename) {
|
||||
this.filename = filename;
|
||||
this.contentType = null;
|
||||
this.id = null;
|
||||
this.size = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
|
@ -11,27 +14,31 @@ import java.util.List;
|
|||
|
||||
class JsonCallMessage {
|
||||
|
||||
OfferMessage offerMessage;
|
||||
AnswerMessage answerMessage;
|
||||
BusyMessage busyMessage;
|
||||
HangupMessage hangupMessage;
|
||||
List<IceUpdateMessage> iceUpdateMessages;
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final OfferMessage offerMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final AnswerMessage answerMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final BusyMessage busyMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final HangupMessage hangupMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<IceUpdateMessage> iceUpdateMessages;
|
||||
|
||||
JsonCallMessage(SignalServiceCallMessage callMessage) {
|
||||
if (callMessage.getOfferMessage().isPresent()) {
|
||||
this.offerMessage = callMessage.getOfferMessage().get();
|
||||
}
|
||||
if (callMessage.getAnswerMessage().isPresent()) {
|
||||
this.answerMessage = callMessage.getAnswerMessage().get();
|
||||
}
|
||||
if (callMessage.getBusyMessage().isPresent()) {
|
||||
this.busyMessage = callMessage.getBusyMessage().get();
|
||||
}
|
||||
if (callMessage.getHangupMessage().isPresent()) {
|
||||
this.hangupMessage = callMessage.getHangupMessage().get();
|
||||
}
|
||||
if (callMessage.getIceUpdateMessages().isPresent()) {
|
||||
this.iceUpdateMessages = callMessage.getIceUpdateMessages().get();
|
||||
}
|
||||
this.offerMessage = callMessage.getOfferMessage().orNull();
|
||||
this.answerMessage = callMessage.getAnswerMessage().orNull();
|
||||
this.busyMessage = callMessage.getBusyMessage().orNull();
|
||||
this.hangupMessage = callMessage.getHangupMessage().orNull();
|
||||
this.iceUpdateMessages = callMessage.getIceUpdateMessages().orNull();
|
||||
}
|
||||
}
|
||||
|
|
48
src/main/java/org/asamk/signal/json/JsonContactAddress.java
Normal file
48
src/main/java/org/asamk/signal/json/JsonContactAddress.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
public class JsonContactAddress {
|
||||
|
||||
@JsonProperty
|
||||
private final SharedContact.PostalAddress.Type type;
|
||||
|
||||
@JsonProperty
|
||||
private final String label;
|
||||
|
||||
@JsonProperty
|
||||
private final String street;
|
||||
|
||||
@JsonProperty
|
||||
private final String pobox;
|
||||
|
||||
@JsonProperty
|
||||
private final String neighborhood;
|
||||
|
||||
@JsonProperty
|
||||
private final String city;
|
||||
|
||||
@JsonProperty
|
||||
private final String region;
|
||||
|
||||
@JsonProperty
|
||||
private final String postcode;
|
||||
|
||||
@JsonProperty
|
||||
private final String country;
|
||||
|
||||
public JsonContactAddress(SharedContact.PostalAddress address) {
|
||||
type = address.getType();
|
||||
label = Util.getStringIfNotBlank(address.getLabel());
|
||||
street = Util.getStringIfNotBlank(address.getStreet());
|
||||
pobox = Util.getStringIfNotBlank(address.getPobox());
|
||||
neighborhood = Util.getStringIfNotBlank(address.getNeighborhood());
|
||||
city = Util.getStringIfNotBlank(address.getCity());
|
||||
region = Util.getStringIfNotBlank(address.getRegion());
|
||||
postcode = Util.getStringIfNotBlank(address.getPostcode());
|
||||
country = Util.getStringIfNotBlank(address.getCountry());
|
||||
}
|
||||
}
|
19
src/main/java/org/asamk/signal/json/JsonContactAvatar.java
Normal file
19
src/main/java/org/asamk/signal/json/JsonContactAvatar.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
public class JsonContactAvatar {
|
||||
|
||||
@JsonProperty
|
||||
private final JsonAttachment attachment;
|
||||
|
||||
@JsonProperty
|
||||
private final boolean isProfile;
|
||||
|
||||
public JsonContactAvatar(SharedContact.Avatar avatar) {
|
||||
attachment = new JsonAttachment(avatar.getAttachment());
|
||||
isProfile = avatar.isProfile();
|
||||
}
|
||||
}
|
24
src/main/java/org/asamk/signal/json/JsonContactEmail.java
Normal file
24
src/main/java/org/asamk/signal/json/JsonContactEmail.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
public class JsonContactEmail {
|
||||
|
||||
@JsonProperty
|
||||
private final String value;
|
||||
|
||||
@JsonProperty
|
||||
private final SharedContact.Email.Type type;
|
||||
|
||||
@JsonProperty
|
||||
private final String label;
|
||||
|
||||
public JsonContactEmail(SharedContact.Email email) {
|
||||
value = email.getValue();
|
||||
type = email.getType();
|
||||
label = Util.getStringIfNotBlank(email.getLabel());
|
||||
}
|
||||
}
|
36
src/main/java/org/asamk/signal/json/JsonContactName.java
Normal file
36
src/main/java/org/asamk/signal/json/JsonContactName.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
public class JsonContactName {
|
||||
|
||||
@JsonProperty
|
||||
private final String display;
|
||||
|
||||
@JsonProperty
|
||||
private final String given;
|
||||
|
||||
@JsonProperty
|
||||
private final String family;
|
||||
|
||||
@JsonProperty
|
||||
private final String prefix;
|
||||
|
||||
@JsonProperty
|
||||
private final String suffix;
|
||||
|
||||
@JsonProperty
|
||||
private final String middle;
|
||||
|
||||
public JsonContactName(SharedContact.Name name) {
|
||||
display = Util.getStringIfNotBlank(name.getDisplay());
|
||||
given = Util.getStringIfNotBlank(name.getGiven());
|
||||
family = Util.getStringIfNotBlank(name.getFamily());
|
||||
prefix = Util.getStringIfNotBlank(name.getPrefix());
|
||||
suffix = Util.getStringIfNotBlank(name.getSuffix());
|
||||
middle = Util.getStringIfNotBlank(name.getMiddle());
|
||||
}
|
||||
}
|
24
src/main/java/org/asamk/signal/json/JsonContactPhone.java
Normal file
24
src/main/java/org/asamk/signal/json/JsonContactPhone.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.util.Util;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
public class JsonContactPhone {
|
||||
|
||||
@JsonProperty
|
||||
private final String value;
|
||||
|
||||
@JsonProperty
|
||||
private final SharedContact.Phone.Type type;
|
||||
|
||||
@JsonProperty
|
||||
private final String label;
|
||||
|
||||
public JsonContactPhone(SharedContact.Phone phone) {
|
||||
value = phone.getValue();
|
||||
type = phone.getType();
|
||||
label = Util.getStringIfNotBlank(phone.getLabel());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
@ -27,9 +30,46 @@ class JsonReaction {
|
|||
|
||||
class JsonDataMessage {
|
||||
|
||||
long timestamp;
|
||||
String message;
|
||||
int expiresInSeconds;
|
||||
@JsonProperty
|
||||
final long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
final String message;
|
||||
|
||||
@JsonProperty
|
||||
final Integer expiresInSeconds;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final Boolean viewOnce;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonReaction reaction;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonQuote quote;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonMention> mentions;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonAttachment> attachments;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonSticker sticker;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonRemoteDelete remoteDelete;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonSharedContact> contacts;
|
||||
|
||||
JsonReaction reaction;
|
||||
JsonQuote quote;
|
||||
|
@ -38,28 +78,33 @@ class JsonDataMessage {
|
|||
JsonGroupInfo groupInfo;
|
||||
JsonReaction reaction;
|
||||
SignalServiceDataMessage.Quote quote;
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonGroupInfo groupInfo;
|
||||
|
||||
JsonDataMessage(SignalServiceDataMessage dataMessage, Manager m) {
|
||||
this.timestamp = dataMessage.getTimestamp();
|
||||
if (dataMessage.getGroupContext().isPresent()) {
|
||||
if (dataMessage.getGroupContext().get().getGroupV1().isPresent()) {
|
||||
SignalServiceGroup groupInfo = dataMessage.getGroupContext().get().getGroupV1().get();
|
||||
final var groupContext = dataMessage.getGroupContext().get();
|
||||
if (groupContext.getGroupV1().isPresent()) {
|
||||
var groupInfo = groupContext.getGroupV1().get();
|
||||
this.groupInfo = new JsonGroupInfo(groupInfo);
|
||||
} else if (dataMessage.getGroupContext().get().getGroupV2().isPresent()) {
|
||||
SignalServiceGroupV2 groupInfo = dataMessage.getGroupContext().get().getGroupV2().get();
|
||||
} else if (groupContext.getGroupV2().isPresent()) {
|
||||
var groupInfo = groupContext.getGroupV2().get();
|
||||
this.groupInfo = new JsonGroupInfo(groupInfo);
|
||||
} else {
|
||||
this.groupInfo = null;
|
||||
}
|
||||
} else {
|
||||
this.groupInfo = null;
|
||||
}
|
||||
if (dataMessage.getBody().isPresent()) {
|
||||
this.message = dataMessage.getBody().get();
|
||||
}
|
||||
this.message = dataMessage.getBody().orNull();
|
||||
this.expiresInSeconds = dataMessage.getExpiresInSeconds();
|
||||
if (dataMessage.getReaction().isPresent()) {
|
||||
this.reaction = new JsonReaction(dataMessage.getReaction().get(), m);
|
||||
}
|
||||
if (dataMessage.getQuote().isPresent()) {
|
||||
this.quote = new JsonQuote(dataMessage.getQuote().get(), m);
|
||||
}
|
||||
this.viewOnce = dataMessage.isViewOnce();
|
||||
this.reaction = dataMessage.getReaction().isPresent()
|
||||
? new JsonReaction(dataMessage.getReaction().get(), m)
|
||||
: null;
|
||||
this.quote = dataMessage.getQuote().isPresent() ? new JsonQuote(dataMessage.getQuote().get(), m) : null;
|
||||
if (dataMessage.getMentions().isPresent()) {
|
||||
this.mentions = dataMessage.getMentions()
|
||||
.get()
|
||||
|
@ -69,6 +114,8 @@ class JsonDataMessage {
|
|||
} else {
|
||||
this.mentions = List.of();
|
||||
}
|
||||
remoteDelete = dataMessage.getRemoteDelete().isPresent() ? new JsonRemoteDelete(dataMessage.getRemoteDelete()
|
||||
.get()) : null;
|
||||
if (dataMessage.getAttachments().isPresent()) {
|
||||
this.attachments = dataMessage.getAttachments()
|
||||
.get()
|
||||
|
@ -78,6 +125,7 @@ class JsonDataMessage {
|
|||
} else {
|
||||
this.attachments = List.of();
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
if (dataMessage.getReaction().isPresent()) {
|
||||
final SignalServiceDataMessage.Reaction reaction = dataMessage.getReaction().get();
|
||||
this.reaction = new JsonReaction(reaction);
|
||||
|
@ -89,6 +137,20 @@ class JsonDataMessage {
|
|||
this.emoji = "";
|
||||
this.targetAuthor = "";
|
||||
this.targetTimestamp = 0;
|
||||
=======
|
||||
this.sticker = dataMessage.getSticker().isPresent() ? new JsonSticker(dataMessage.getSticker().get()) : null;
|
||||
|
||||
if (dataMessage.getSharedContacts().isPresent()) {
|
||||
this.contacts = dataMessage.getSharedContacts()
|
||||
.get()
|
||||
.stream()
|
||||
.map(JsonSharedContact::new)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
this.contacts = List.of();
|
||||
}
|
||||
}
|
||||
>>>>>>> upstream/master
|
||||
|
||||
}
|
||||
|
||||
|
@ -107,10 +169,15 @@ class JsonDataMessage {
|
|||
public JsonDataMessage(Signal.MessageReceived messageReceived) {
|
||||
timestamp = messageReceived.getTimestamp();
|
||||
message = messageReceived.getMessage();
|
||||
groupInfo = new JsonGroupInfo(messageReceived.getGroupId());
|
||||
reaction = null; // TODO Replace these 3 with the proper commands
|
||||
groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null;
|
||||
expiresInSeconds = null;
|
||||
viewOnce = null;
|
||||
remoteDelete = null;
|
||||
reaction = null; // TODO Replace these 5 with the proper commands
|
||||
quote = null;
|
||||
mentions = null;
|
||||
sticker = null;
|
||||
contacts = null;
|
||||
attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList());
|
||||
}
|
||||
// i don't understand what SyncMessages are so i'm going to ignore them
|
||||
|
@ -118,10 +185,15 @@ class JsonDataMessage {
|
|||
public JsonDataMessage(Signal.SyncMessageReceived messageReceived) {
|
||||
timestamp = messageReceived.getTimestamp();
|
||||
message = messageReceived.getMessage();
|
||||
groupInfo = new JsonGroupInfo(messageReceived.getGroupId());
|
||||
reaction = null; // TODO Replace these 3 with the proper commands
|
||||
groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null;
|
||||
expiresInSeconds = null;
|
||||
viewOnce = null;
|
||||
remoteDelete = null;
|
||||
reaction = null; // TODO Replace these 5 with the proper commands
|
||||
quote = null;
|
||||
mentions = null;
|
||||
sticker = null;
|
||||
contacts = null;
|
||||
attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class JsonError {
|
||||
|
||||
String message;
|
||||
@JsonProperty
|
||||
final String message;
|
||||
|
||||
public JsonError(Throwable exception) {
|
||||
this.message = exception.getMessage();
|
||||
|
|
|
@ -1,41 +1,59 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import org.asamk.signal.manager.GroupUtils;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.manager.groups.GroupUtils;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class JsonGroupInfo {
|
||||
|
||||
String groupId;
|
||||
List<String> members;
|
||||
String name;
|
||||
String type;
|
||||
@JsonProperty
|
||||
final String groupId;
|
||||
|
||||
@JsonProperty
|
||||
final String type;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final String name;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<String> members;
|
||||
|
||||
JsonGroupInfo(SignalServiceGroup groupInfo) {
|
||||
this.groupId = Base64.encodeBytes(groupInfo.getGroupId());
|
||||
if (groupInfo.getMembers().isPresent()) {
|
||||
this.members = new ArrayList<>(groupInfo.getMembers().get().size());
|
||||
for (SignalServiceAddress address : groupInfo.getMembers().get()) {
|
||||
this.members.add(address.getLegacyIdentifier());
|
||||
}
|
||||
}
|
||||
if (groupInfo.getName().isPresent()) {
|
||||
this.name = groupInfo.getName().get();
|
||||
}
|
||||
this.groupId = Base64.getEncoder().encodeToString(groupInfo.getGroupId());
|
||||
this.type = groupInfo.getType().toString();
|
||||
this.name = groupInfo.getName().orNull();
|
||||
if (groupInfo.getMembers().isPresent()) {
|
||||
this.members = groupInfo.getMembers()
|
||||
.get()
|
||||
.stream()
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
this.members = null;
|
||||
}
|
||||
}
|
||||
|
||||
JsonGroupInfo(SignalServiceGroupV2 groupInfo) {
|
||||
this.groupId = GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64();
|
||||
this.type = groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER";
|
||||
this.members = null;
|
||||
this.name = null;
|
||||
}
|
||||
|
||||
JsonGroupInfo(byte[] groupId) {
|
||||
this.groupId = Base64.encodeBytes(groupId);
|
||||
this.groupId = Base64.getEncoder().encodeToString(groupId);
|
||||
this.type = "DELIVER";
|
||||
this.members = null;
|
||||
this.name = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public class JsonMention {
|
||||
|
||||
String name;
|
||||
int start;
|
||||
int length;
|
||||
@JsonProperty
|
||||
final String name;
|
||||
|
||||
@JsonProperty
|
||||
final int start;
|
||||
|
||||
@JsonProperty
|
||||
final int length;
|
||||
|
||||
JsonMention(SignalServiceDataMessage.Mention mention, Manager m) {
|
||||
this.name = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid(), null))
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.Signal;
|
||||
//import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonMessageEnvelope {
|
||||
<<<<<<< HEAD
|
||||
String source;
|
||||
int sourceDevice;
|
||||
String relay;
|
||||
|
@ -19,18 +22,66 @@ public class JsonMessageEnvelope {
|
|||
JsonCallMessage callMessage;
|
||||
JsonReceiptMessage receiptMessage;
|
||||
// String typingAction;
|
||||
=======
|
||||
|
||||
@JsonProperty
|
||||
final String source;
|
||||
|
||||
@JsonProperty
|
||||
final Integer sourceDevice;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final String relay;
|
||||
|
||||
@JsonProperty
|
||||
final long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonDataMessage dataMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonSyncMessage syncMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonCallMessage callMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonReceiptMessage receiptMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonTypingMessage typingMessage;
|
||||
>>>>>>> upstream/master
|
||||
|
||||
public JsonMessageEnvelope(SignalServiceEnvelope envelope, SignalServiceContent content, Manager m) {
|
||||
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
|
||||
SignalServiceAddress source = envelope.getSourceAddress();
|
||||
var source = envelope.getSourceAddress();
|
||||
this.source = source.getLegacyIdentifier();
|
||||
this.relay = source.getRelay().isPresent() ? source.getRelay().get() : null;
|
||||
this.sourceDevice = envelope.getSourceDevice();
|
||||
this.relay = source.getRelay().orNull();
|
||||
} else if (envelope.isUnidentifiedSender() && content != null) {
|
||||
this.source = content.getSender().getLegacyIdentifier();
|
||||
this.sourceDevice = content.getSenderDevice();
|
||||
this.relay = null;
|
||||
} else {
|
||||
this.source = null;
|
||||
this.sourceDevice = null;
|
||||
this.relay = null;
|
||||
}
|
||||
this.sourceDevice = envelope.getSourceDevice();
|
||||
this.timestamp = envelope.getTimestamp();
|
||||
if (envelope.isReceipt()) {
|
||||
this.receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp));
|
||||
} else if (content != null && content.getReceiptMessage().isPresent()) {
|
||||
this.receiptMessage = new JsonReceiptMessage(content.getReceiptMessage().get());
|
||||
} else {
|
||||
this.receiptMessage = null;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
if (content != null) {
|
||||
if (envelope.isUnidentifiedSender()) {
|
||||
this.source = content.getSender().getLegacyIdentifier();
|
||||
|
@ -53,23 +104,56 @@ public class JsonMessageEnvelope {
|
|||
this.typingAction = content.getTypingMessage().get();
|
||||
}
|
||||
*/ }
|
||||
=======
|
||||
this.typingMessage = content != null && content.getTypingMessage().isPresent()
|
||||
? new JsonTypingMessage(content.getTypingMessage().get())
|
||||
: null;
|
||||
|
||||
this.dataMessage = content != null && content.getDataMessage().isPresent()
|
||||
? new JsonDataMessage(content.getDataMessage().get(), m)
|
||||
: null;
|
||||
this.syncMessage = content != null && content.getSyncMessage().isPresent()
|
||||
? new JsonSyncMessage(content.getSyncMessage().get(), m)
|
||||
: null;
|
||||
this.callMessage = content != null && content.getCallMessage().isPresent()
|
||||
? new JsonCallMessage(content.getCallMessage().get())
|
||||
: null;
|
||||
>>>>>>> upstream/master
|
||||
}
|
||||
|
||||
public JsonMessageEnvelope(Signal.MessageReceived messageReceived) {
|
||||
source = messageReceived.getSender();
|
||||
sourceDevice = null;
|
||||
relay = null;
|
||||
timestamp = messageReceived.getTimestamp();
|
||||
receiptMessage = null;
|
||||
dataMessage = new JsonDataMessage(messageReceived);
|
||||
syncMessage = null;
|
||||
callMessage = null;
|
||||
typingMessage = null;
|
||||
}
|
||||
|
||||
public JsonMessageEnvelope(Signal.ReceiptReceived receiptReceived) {
|
||||
source = receiptReceived.getSender();
|
||||
sourceDevice = null;
|
||||
relay = null;
|
||||
timestamp = receiptReceived.getTimestamp();
|
||||
receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp));
|
||||
dataMessage = null;
|
||||
syncMessage = null;
|
||||
callMessage = null;
|
||||
typingMessage = null;
|
||||
}
|
||||
|
||||
public JsonMessageEnvelope(Signal.SyncMessageReceived messageReceived) {
|
||||
source = messageReceived.getSource();
|
||||
sourceDevice = null;
|
||||
relay = null;
|
||||
timestamp = messageReceived.getTimestamp();
|
||||
receiptMessage = null;
|
||||
dataMessage = null;
|
||||
syncMessage = new JsonSyncMessage(messageReceived);
|
||||
callMessage = null;
|
||||
typingMessage = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
||||
|
@ -9,12 +12,21 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class JsonQuote {
|
||||
|
||||
long id;
|
||||
String author;
|
||||
String text;
|
||||
@JsonProperty
|
||||
final long id;
|
||||
|
||||
List<JsonMention> mentions;
|
||||
List<JsonQuotedAttachment> attachments;
|
||||
@JsonProperty
|
||||
final String author;
|
||||
|
||||
@JsonProperty
|
||||
final String text;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonMention> mentions;
|
||||
|
||||
@JsonProperty
|
||||
final List<JsonQuotedAttachment> attachments;
|
||||
|
||||
JsonQuote(SignalServiceDataMessage.Quote quote, Manager m) {
|
||||
this.id = quote.getId();
|
||||
|
@ -26,6 +38,8 @@ public class JsonQuote {
|
|||
.stream()
|
||||
.map(quotedMention -> new JsonMention(quotedMention, m))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
this.mentions = null;
|
||||
}
|
||||
|
||||
if (quote.getAttachments().size() > 0) {
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
||||
public class JsonQuotedAttachment {
|
||||
|
||||
String contentType;
|
||||
String filename;
|
||||
JsonAttachment thumbnail;
|
||||
@JsonProperty
|
||||
final String contentType;
|
||||
|
||||
@JsonProperty
|
||||
final String filename;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonAttachment thumbnail;
|
||||
|
||||
JsonQuotedAttachment(SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment) {
|
||||
contentType = quotedAttachment.getContentType();
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Reaction;
|
||||
|
||||
public class JsonReaction {
|
||||
|
||||
String emoji;
|
||||
String targetAuthor;
|
||||
long targetSentTimestamp;
|
||||
boolean isRemove;
|
||||
@JsonProperty
|
||||
final String emoji;
|
||||
|
||||
@JsonProperty
|
||||
final String targetAuthor;
|
||||
|
||||
@JsonProperty
|
||||
final long targetSentTimestamp;
|
||||
|
||||
@JsonProperty
|
||||
final boolean isRemove;
|
||||
|
||||
JsonReaction(Reaction reaction, Manager m) {
|
||||
this.emoji = reaction.getEmoji();
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class JsonReceiptMessage {
|
||||
|
||||
long when;
|
||||
boolean isDelivery;
|
||||
boolean isRead;
|
||||
List<Long> timestamps;
|
||||
@JsonProperty
|
||||
final long when;
|
||||
|
||||
@JsonProperty
|
||||
final boolean isDelivery;
|
||||
|
||||
@JsonProperty
|
||||
final boolean isRead;
|
||||
|
||||
@JsonProperty
|
||||
final List<Long> timestamps;
|
||||
|
||||
JsonReceiptMessage(SignalServiceReceiptMessage receiptMessage) {
|
||||
|
||||
this.when = receiptMessage.getWhen();
|
||||
if (receiptMessage.isDeliveryReceipt()) {
|
||||
this.isDelivery = true;
|
||||
}
|
||||
if (receiptMessage.isReadReceipt()) {
|
||||
this.isRead = true;
|
||||
}
|
||||
this.isDelivery = receiptMessage.isDeliveryReceipt();
|
||||
this.isRead = receiptMessage.isReadReceipt();
|
||||
this.timestamps = receiptMessage.getTimestamps();
|
||||
}
|
||||
|
||||
|
|
15
src/main/java/org/asamk/signal/json/JsonRemoteDelete.java
Normal file
15
src/main/java/org/asamk/signal/json/JsonRemoteDelete.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
||||
class JsonRemoteDelete {
|
||||
|
||||
@JsonProperty
|
||||
final long timestamp;
|
||||
|
||||
JsonRemoteDelete(SignalServiceDataMessage.RemoteDelete remoteDelete) {
|
||||
this.timestamp = remoteDelete.getTargetSentTimestamp();
|
||||
}
|
||||
}
|
62
src/main/java/org/asamk/signal/json/JsonSharedContact.java
Normal file
62
src/main/java/org/asamk/signal/json/JsonSharedContact.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class JsonSharedContact {
|
||||
|
||||
@JsonProperty
|
||||
final JsonContactName name;
|
||||
|
||||
@JsonProperty
|
||||
final JsonContactAvatar avatar;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonContactPhone> phone;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonContactEmail> email;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonContactAddress> address;
|
||||
|
||||
@JsonProperty
|
||||
final String organization;
|
||||
|
||||
public JsonSharedContact(SharedContact contact) {
|
||||
name = new JsonContactName(contact.getName());
|
||||
if (contact.getAvatar().isPresent()) {
|
||||
avatar = new JsonContactAvatar(contact.getAvatar().get());
|
||||
} else {
|
||||
avatar = null;
|
||||
}
|
||||
|
||||
if (contact.getPhone().isPresent()) {
|
||||
phone = contact.getPhone().get().stream().map(JsonContactPhone::new).collect(Collectors.toList());
|
||||
} else {
|
||||
phone = null;
|
||||
}
|
||||
|
||||
if (contact.getEmail().isPresent()) {
|
||||
email = contact.getEmail().get().stream().map(JsonContactEmail::new).collect(Collectors.toList());
|
||||
} else {
|
||||
email = null;
|
||||
}
|
||||
|
||||
if (contact.getAddress().isPresent()) {
|
||||
address = contact.getAddress().get().stream().map(JsonContactAddress::new).collect(Collectors.toList());
|
||||
} else {
|
||||
address = null;
|
||||
}
|
||||
|
||||
organization = contact.getOrganization().orNull();
|
||||
}
|
||||
}
|
25
src/main/java/org/asamk/signal/json/JsonSticker.java
Normal file
25
src/main/java/org/asamk/signal/json/JsonSticker.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class JsonSticker {
|
||||
|
||||
@JsonProperty
|
||||
final String packId;
|
||||
|
||||
@JsonProperty
|
||||
final String packKey;
|
||||
|
||||
@JsonProperty
|
||||
final int stickerId;
|
||||
|
||||
public JsonSticker(SignalServiceDataMessage.Sticker sticker) {
|
||||
this.packId = Base64.getEncoder().encodeToString(sticker.getPackId());
|
||||
this.packKey = Base64.getEncoder().encodeToString(sticker.getPackKey());
|
||||
this.stickerId = sticker.getStickerId();
|
||||
}
|
||||
}
|
|
@ -1,18 +1,23 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
class JsonSyncDataMessage extends JsonDataMessage {
|
||||
|
||||
String destination;
|
||||
@JsonProperty
|
||||
final String destination;
|
||||
|
||||
JsonSyncDataMessage(SentTranscriptMessage transcriptMessage, Manager m) {
|
||||
super(transcriptMessage.getMessage(), m);
|
||||
if (transcriptMessage.getDestination().isPresent()) {
|
||||
this.destination = transcriptMessage.getDestination().get().getLegacyIdentifier();
|
||||
}
|
||||
|
||||
this.destination = transcriptMessage.getDestination()
|
||||
.transform(SignalServiceAddress::getLegacyIdentifier)
|
||||
.orNull();
|
||||
}
|
||||
|
||||
JsonSyncDataMessage(Signal.SyncMessageReceived messageReceived) {
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.asamk.Signal;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
enum JsonSyncMessageType {
|
||||
CONTACTS_SYNC,
|
||||
|
@ -17,23 +20,57 @@ enum JsonSyncMessageType {
|
|||
|
||||
class JsonSyncMessage {
|
||||
|
||||
JsonSyncDataMessage sentMessage;
|
||||
List<String> blockedNumbers;
|
||||
List<ReadMessage> readMessages;
|
||||
JsonSyncMessageType type;
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonSyncDataMessage sentMessage;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<String> blockedNumbers;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<String> blockedGroupIds;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final List<JsonSyncReadMessage> readMessages;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final JsonSyncMessageType type;
|
||||
|
||||
JsonSyncMessage(SignalServiceSyncMessage syncMessage, Manager m) {
|
||||
if (syncMessage.getSent().isPresent()) {
|
||||
this.sentMessage = new JsonSyncDataMessage(syncMessage.getSent().get(), m);
|
||||
}
|
||||
this.sentMessage = syncMessage.getSent().isPresent()
|
||||
? new JsonSyncDataMessage(syncMessage.getSent().get(), m)
|
||||
: null;
|
||||
if (syncMessage.getBlockedList().isPresent()) {
|
||||
this.blockedNumbers = new ArrayList<>(syncMessage.getBlockedList().get().getAddresses().size());
|
||||
for (SignalServiceAddress address : syncMessage.getBlockedList().get().getAddresses()) {
|
||||
this.blockedNumbers.add(address.getLegacyIdentifier());
|
||||
}
|
||||
final var base64 = Base64.getEncoder();
|
||||
this.blockedNumbers = syncMessage.getBlockedList()
|
||||
.get()
|
||||
.getAddresses()
|
||||
.stream()
|
||||
.map(SignalServiceAddress::getLegacyIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
this.blockedGroupIds = syncMessage.getBlockedList()
|
||||
.get()
|
||||
.getGroupIds()
|
||||
.stream()
|
||||
.map(base64::encodeToString)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
this.blockedNumbers = null;
|
||||
this.blockedGroupIds = null;
|
||||
}
|
||||
if (syncMessage.getRead().isPresent()) {
|
||||
this.readMessages = syncMessage.getRead().get();
|
||||
this.readMessages = syncMessage.getRead()
|
||||
.get()
|
||||
.stream()
|
||||
.map(message -> new JsonSyncReadMessage(message.getSender().getLegacyIdentifier(),
|
||||
message.getTimestamp()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
this.readMessages = null;
|
||||
}
|
||||
|
||||
if (syncMessage.getContacts().isPresent()) {
|
||||
|
@ -42,10 +79,16 @@ class JsonSyncMessage {
|
|||
this.type = JsonSyncMessageType.GROUPS_SYNC;
|
||||
} else if (syncMessage.getRequest().isPresent()) {
|
||||
this.type = JsonSyncMessageType.REQUEST_SYNC;
|
||||
} else {
|
||||
this.type = null;
|
||||
}
|
||||
}
|
||||
|
||||
JsonSyncMessage(Signal.SyncMessageReceived messageReceived) {
|
||||
sentMessage = new JsonSyncDataMessage(messageReceived);
|
||||
this.sentMessage = new JsonSyncDataMessage(messageReceived);
|
||||
this.blockedNumbers = null;
|
||||
this.blockedGroupIds = null;
|
||||
this.readMessages = null;
|
||||
this.type = null;
|
||||
}
|
||||
}
|
||||
|
|
17
src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java
Normal file
17
src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
class JsonSyncReadMessage {
|
||||
|
||||
@JsonProperty
|
||||
final String sender;
|
||||
|
||||
@JsonProperty
|
||||
final long timestamp;
|
||||
|
||||
public JsonSyncReadMessage(final String sender, final long timestamp) {
|
||||
this.sender = sender;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
28
src/main/java/org/asamk/signal/json/JsonTypingMessage.java
Normal file
28
src/main/java/org/asamk/signal/json/JsonTypingMessage.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package org.asamk.signal.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
class JsonTypingMessage {
|
||||
|
||||
@JsonProperty
|
||||
final String action;
|
||||
|
||||
@JsonProperty
|
||||
final long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
final String groupId;
|
||||
|
||||
JsonTypingMessage(SignalServiceTypingMessage typingMessage) {
|
||||
this.action = typingMessage.getAction().name();
|
||||
this.timestamp = typingMessage.getTimestamp();
|
||||
final var encoder = Base64.getEncoder();
|
||||
this.groupId = typingMessage.getGroupId().transform(encoder::encodeToString).orNull();
|
||||
}
|
||||
}
|
|
@ -13,8 +13,8 @@ public class DateUtils {
|
|||
}
|
||||
|
||||
public static String formatTimestamp(long timestamp) {
|
||||
Date date = new Date(timestamp);
|
||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
|
||||
var date = new Date(timestamp);
|
||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); // Quoted "Z" to indicate UTC, no timezone offset
|
||||
df.setTimeZone(tzUTC);
|
||||
return timestamp + " (" + df.format(date) + ")";
|
||||
}
|
||||
|
|
|
@ -1,83 +1,68 @@
|
|||
package org.asamk.signal.util;
|
||||
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.asamk.signal.manager.GroupNotFoundException;
|
||||
import org.asamk.signal.manager.NotAGroupMemberException;
|
||||
import org.asamk.signal.PlainTextWriter;
|
||||
import org.asamk.signal.commands.exceptions.CommandException;
|
||||
import org.asamk.signal.commands.exceptions.IOErrorException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ErrorUtils {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(ErrorUtils.class);
|
||||
|
||||
private ErrorUtils() {
|
||||
}
|
||||
|
||||
public static void handleAssertionError(AssertionError e) {
|
||||
System.err.println("Failed to send/receive message (Assertion): " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.err.println(
|
||||
"If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
|
||||
logger.warn("If you use an Oracle JRE please check if you have unlimited strength crypto enabled, see README");
|
||||
}
|
||||
|
||||
public static int handleTimestampAndSendMessageResults(long timestamp, List<SendMessageResult> results) {
|
||||
public static void handleTimestampAndSendMessageResults(
|
||||
PlainTextWriter writer, long timestamp, List<SendMessageResult> results
|
||||
) throws CommandException {
|
||||
if (timestamp != 0) {
|
||||
System.out.println(timestamp);
|
||||
writer.println("{}", timestamp);
|
||||
}
|
||||
List<String> errors = getErrorMessagesFromSendMessageResults(results);
|
||||
return handleSendMessageResultErrors(errors);
|
||||
var errors = getErrorMessagesFromSendMessageResults(results);
|
||||
handleSendMessageResultErrors(errors);
|
||||
}
|
||||
|
||||
public static List<String> getErrorMessagesFromSendMessageResults(List<SendMessageResult> results) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
for (SendMessageResult result : results) {
|
||||
if (result.isNetworkFailure()) {
|
||||
errors.add(String.format("Network failure for \"%s\"", result.getAddress().getLegacyIdentifier()));
|
||||
} else if (result.isUnregisteredFailure()) {
|
||||
errors.add(String.format("Unregistered user \"%s\"", result.getAddress().getLegacyIdentifier()));
|
||||
} else if (result.getIdentityFailure() != null) {
|
||||
errors.add(String.format("Untrusted Identity for \"%s\"", result.getAddress().getLegacyIdentifier()));
|
||||
var errors = new ArrayList<String>();
|
||||
for (var result : results) {
|
||||
var error = getErrorMessageFromSendMessageResult(result);
|
||||
if (error != null) {
|
||||
errors.add(error);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private static int handleSendMessageResultErrors(List<String> errors) {
|
||||
public static String getErrorMessageFromSendMessageResult(SendMessageResult result) {
|
||||
if (result.isNetworkFailure()) {
|
||||
return String.format("Network failure for \"%s\"", result.getAddress().getLegacyIdentifier());
|
||||
} else if (result.isUnregisteredFailure()) {
|
||||
return String.format("Unregistered user \"%s\"", result.getAddress().getLegacyIdentifier());
|
||||
} else if (result.getIdentityFailure() != null) {
|
||||
return String.format("Untrusted Identity for \"%s\"", result.getAddress().getLegacyIdentifier());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void handleSendMessageResultErrors(List<String> errors) throws CommandException {
|
||||
if (errors.size() == 0) {
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
System.err.println("Failed to send (some) messages:");
|
||||
for (String error : errors) {
|
||||
System.err.println(error);
|
||||
var message = new StringBuilder();
|
||||
message.append("Failed to send (some) messages:\n");
|
||||
for (var error : errors) {
|
||||
message.append(error).append("\n");
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static void handleIOException(IOException e) {
|
||||
System.err.println("Failed to send message: " + e.getMessage());
|
||||
}
|
||||
|
||||
public static void handleGroupNotFoundException(GroupNotFoundException e) {
|
||||
System.err.println("Failed to send to group: " + e.getMessage());
|
||||
System.err.println("Aborting sending.");
|
||||
}
|
||||
|
||||
public static void handleNotAGroupMemberException(NotAGroupMemberException e) {
|
||||
System.err.println("Failed to send to group: " + e.getMessage());
|
||||
System.err.println("Update the group on another device to readd the user to this group.");
|
||||
System.err.println("Aborting sending.");
|
||||
}
|
||||
|
||||
public static void handleGroupIdFormatException(GroupIdFormatException e) {
|
||||
System.err.println(e.getMessage());
|
||||
System.err.println("Aborting sending.");
|
||||
}
|
||||
|
||||
public static void handleInvalidNumberException(InvalidNumberException e) {
|
||||
System.err.println("Failed to parse recipient: " + e.getMessage());
|
||||
System.err.println("Aborting sending.");
|
||||
throw new IOErrorException(message.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ public class Hex {
|
|||
}
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (final byte aByte : bytes) {
|
||||
var buf = new StringBuffer();
|
||||
for (final var aByte : bytes) {
|
||||
appendHexChar(buf, aByte);
|
||||
buf.append(" ");
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ public class Hex {
|
|||
}
|
||||
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (final byte aByte : bytes) {
|
||||
var buf = new StringBuffer();
|
||||
for (final var aByte : bytes) {
|
||||
appendHexChar(buf, aByte);
|
||||
}
|
||||
return buf.toString();
|
||||
|
@ -30,9 +30,9 @@ public class Hex {
|
|||
}
|
||||
|
||||
public static byte[] toByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
var len = s.length();
|
||||
var data = new byte[len / 2];
|
||||
for (var i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
|
|
|
@ -1,38 +1,19 @@
|
|||
package org.asamk.signal.util;
|
||||
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
|
||||
|
||||
public class IOUtils {
|
||||
|
||||
private IOUtils() {
|
||||
}
|
||||
|
||||
public static File createTempFile() throws IOException {
|
||||
return File.createTempFile("signal_tmp_", ".tmp");
|
||||
}
|
||||
|
||||
public static String readAll(InputStream in, Charset charset) throws IOException {
|
||||
StringWriter output = new StringWriter();
|
||||
byte[] buffer = new byte[4096];
|
||||
var output = new StringWriter();
|
||||
var buffer = new byte[4096];
|
||||
int n;
|
||||
while (-1 != (n = in.read(buffer))) {
|
||||
output.write(new String(buffer, 0, n, charset));
|
||||
|
@ -40,57 +21,12 @@ public class IOUtils {
|
|||
return output.toString();
|
||||
}
|
||||
|
||||
public static byte[] readFully(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Util.copy(in, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static void createPrivateDirectories(File file) throws IOException {
|
||||
if (file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Path path = file.toPath();
|
||||
try {
|
||||
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
|
||||
Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createPrivateFile(File path) throws IOException {
|
||||
final Path file = path.toPath();
|
||||
try {
|
||||
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
|
||||
Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
Files.createFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static File getDataHomeDir() {
|
||||
String dataHome = System.getenv("XDG_DATA_HOME");
|
||||
var dataHome = System.getenv("XDG_DATA_HOME");
|
||||
if (dataHome != null) {
|
||||
return new File(dataHome);
|
||||
}
|
||||
|
||||
return new File(new File(System.getProperty("user.home"), ".local"), "share");
|
||||
}
|
||||
|
||||
public static void copyStreamToFile(InputStream input, File outputFile) throws IOException {
|
||||
copyStreamToFile(input, outputFile, 8192);
|
||||
}
|
||||
|
||||
public static void copyStreamToFile(InputStream input, File outputFile, int bufferSize) throws IOException {
|
||||
try (OutputStream output = new FileOutputStream(outputFile)) {
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int read;
|
||||
|
||||
while ((read = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.security.SecureRandom;
|
|||
public class RandomUtils {
|
||||
|
||||
private static final ThreadLocal<SecureRandom> LOCAL_RANDOM = ThreadLocal.withInitial(() -> {
|
||||
SecureRandom rand = getSecureRandomUnseeded();
|
||||
var rand = getSecureRandomUnseeded();
|
||||
|
||||
// Let the SecureRandom seed it self initially
|
||||
rand.nextBoolean();
|
||||
|
|
|
@ -1,66 +1,33 @@
|
|||
package org.asamk.signal.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import org.asamk.signal.manager.GroupId;
|
||||
import org.asamk.signal.manager.GroupIdFormatException;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import org.asamk.signal.manager.groups.GroupId;
|
||||
import org.asamk.signal.manager.groups.GroupIdFormatException;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public class Util {
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
public static String getStringIfNotBlank(Optional<String> value) {
|
||||
var string = value.orNull();
|
||||
if (string == null || string.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public static String formatSafetyNumber(String digits) {
|
||||
final int partCount = 12;
|
||||
int partSize = digits.length() / partCount;
|
||||
StringBuilder f = new StringBuilder(digits.length() + partCount);
|
||||
for (int i = 0; i < partCount; i++) {
|
||||
final var partCount = 12;
|
||||
var partSize = digits.length() / partCount;
|
||||
var f = new StringBuilder(digits.length() + partCount);
|
||||
for (var i = 0; i < partCount; i++) {
|
||||
f.append(digits, i * partSize, (i * partSize) + partSize).append(" ");
|
||||
}
|
||||
return f.toString();
|
||||
}
|
||||
|
||||
public static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (CharSequence str : list) {
|
||||
if (buf.length() > 0) {
|
||||
buf.append(separator);
|
||||
}
|
||||
buf.append(str);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static JsonNode getNotNullNode(JsonNode parent, String name) throws InvalidObjectException {
|
||||
JsonNode node = parent.get(name);
|
||||
if (node == null) {
|
||||
throw new InvalidObjectException(String.format("Incorrect file format: expected parameter %s not found ",
|
||||
name));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public static GroupId decodeGroupId(String groupId) throws GroupIdFormatException {
|
||||
return GroupId.fromBase64(groupId);
|
||||
}
|
||||
|
||||
public static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
|
||||
return PhoneNumberFormatter.formatNumber(number, localNumber);
|
||||
}
|
||||
|
||||
public static SignalServiceAddress getSignalServiceAddressFromIdentifier(final String identifier) {
|
||||
if (UuidUtil.isUuid(identifier)) {
|
||||
return new SignalServiceAddress(UuidUtil.parseOrNull(identifier), null);
|
||||
} else {
|
||||
return new SignalServiceAddress(null, identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue