signal-cli/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java
John Freed 0691a91dad Dbus methods isMember and isAdmin
create isAdmin method in parallel with isMember
extend both to have the ability to add or remove members or admins

update documentation
2021-08-19 11:19:08 +02:00

1609 lines
58 KiB
Java

package org.asamk.signal.dbus;
import org.asamk.Signal;
import org.asamk.SignalControl;
import org.asamk.SignalControl.Error;
import org.asamk.signal.BaseConfig;
import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.PlainTextWriterImpl;
import org.asamk.signal.commands.GetUserStatusCommand;
import org.asamk.signal.commands.UpdateGroupCommand;
import org.asamk.signal.OutputWriter;
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.AttachmentInvalidException;
import org.asamk.signal.manager.AvatarStore;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.StickerPackInvalidException;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdFormatException;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.ErrorUtils;
import org.asamk.signal.util.Hex;
import org.asamk.signal.util.Util;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
public class DbusSignalImpl implements Signal {
private final Manager m;
private final String objectPath;
private final static Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class);
public DbusSignalImpl(final Manager m, final String objectPath) {
this.m = m;
this.objectPath = objectPath;
}
@Override
public boolean isRemote() {
return false;
}
@Override
public String getObjectPath() {
return objectPath;
}
@Override
public void updateAccount() {
try {
m.updateAccountAttributes();
} catch (IOException | Error.Failure e) {
throw new Error.Failure("UpdateAccount error: " + e.getMessage());
}
}
@Override
public List<String> listIdentity(String number) {
List<IdentityInfo> identities;
IdentityInfo theirId;
try {
identities = m.getIdentities(number);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber("Invalid number: " + e.getMessage());
}
List<String> results = new ArrayList<String>();
if (identities.isEmpty()) {return results;}
theirId = identities.get(0);
final SignalServiceAddress address = m.resolveSignalServiceAddress(theirId.getRecipientId());
var digits = Util.formatSafetyNumber(m.computeSafetyNumber(address, theirId.getIdentityKey()));
results.add(theirId.getTrustLevel().toString());
results.add(theirId.getDateAdded().toString());
results.add(Hex.toString(theirId.getFingerprint()));
results.add(digits);
return results;
}
@Override
public List<String> listDevices() {
List<Device> devices;
List<String> results = new ArrayList<String>();
try {
devices = m.getLinkedDevices();
} catch (IOException | Error.Failure e) {
throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
}
for (var d : devices) {
var name = d.getName();
if (name == null) {name = "null";}
results.add(name);
}
return results;
}
@Override
public long sendMessage(final String message, final List<String> attachments, final String recipient) {
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 {
if (results.size() == 1) {
checkSendMessageResult(timestamp, results.get(0));
return;
}
var errors = ErrorUtils.getErrorMessagesFromSendMessageResults(results);
if (errors.size() == 0) {
return;
}
var message = new StringBuilder();
message.append(timestamp).append('\n');
message.append("Failed to send (some) messages:\n");
for (var error : errors) {
message.append(error).append('\n');
}
throw new Error.Failure(message.toString());
}
@Override
public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
try {
final var results = m.sendMessage(message, attachments, recipients);
checkSendMessageResults(results.first(), results.second());
return results.first();
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
}
@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 {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
final var results = m.sendGroupRemoteDeleteMessage(targetSentTimestamp, group.getGroupId());
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
) {
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 var results = m.sendEndSessionMessage(recipients);
checkSendMessageResults(results.first(), results.second());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
try {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
var results = m.sendGroupMessage(message, attachments, group.getGroupId());
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());
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
}
}
@Override
public long sendGroupMessage(final String message, final List<String> attachments, final String base64GroupId) {
try {
byte[] groupId = GroupId.fromBase64(base64GroupId).serialize();
return sendGroupMessage(message, attachments, groupId);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
}
@Override
public long sendGroupMessageReaction(
final String emoji,
final boolean remove,
final String targetAuthor,
final long targetSentTimestamp,
final byte[] groupId
) {
try {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
final var results = m.sendGroupMessageReaction(emoji,
remove,
targetAuthor,
targetSentTimestamp,
group.getGroupId());
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());
}
}
@Override
public long sendGroupMessageReaction(
final String emoji,
final boolean remove,
final String targetAuthor,
final long targetSentTimestamp,
final String base64GroupId
) {
try {
byte[] groupId = GroupId.fromBase64(base64GroupId).serialize();
return sendGroupMessageReaction(emoji, remove, targetAuthor, targetSentTimestamp, groupId);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
}
@Override
public void sendContacts() {
try {
m.sendContacts();
} catch (UntrustedIdentityException e) {
throw new Error.UntrustedIdentity("SendContacts error: " + e.getMessage());
} catch (IOException e) {
throw new Error.Failure("SendContacts error: " + e.getMessage());
}
}
@Override
public void sendSyncRequest() {
try {
m.requestAllSyncData();
} catch (IOException e) {
throw new Error.Failure("Request sync data error: " + e.getMessage());
}
}
@Override
public void trust(String number, String safetyNumber){
if (safetyNumber != null) {
safetyNumber = safetyNumber.replaceAll(" ", "");
if (safetyNumber.length() == 66) {
byte[] fingerprintBytes;
try {
fingerprintBytes = Hex.toByteArray(safetyNumber.toLowerCase(Locale.ROOT));
} catch (Exception e) {
throw new Error.Failure(
"Failed to parse the fingerprint, make sure the fingerprint is a correctly encoded hex string without additional characters.");
}
boolean res;
try {
res = m.trustIdentityVerified(number, fingerprintBytes);
} catch (InvalidNumberException e) {
throw new Error.Failure("Failed to parse recipient: " + e.getMessage());
}
if (!res) {
throw new Error.Failure(
"Failed to set the trust for the fingerprint of this number, make sure the number and the fingerprint are correct.");
}
} else if (safetyNumber.length() == 60) {
boolean res;
try {
res = m.trustIdentityVerifiedSafetyNumber(number, safetyNumber);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber("Failed to parse recipient: " + e.getMessage());
}
if (!res) {
throw new Error.Failure(
"Failed to set the trust for the safety number of this phone number, make sure the phone number and the safety number are correct.");
}
} else {
throw new Error.Failure(
"Safety number has invalid format, either specify the old hex fingerprint or the new safety number");
}
} else {
throw new Error.Failure(
"You need to specify the fingerprint/safety number you have verified with -v SAFETY_NUMBER");
}
}
@Override
public void sendTyping(boolean typingAction, String base64GroupId, List<String>recipients) {
final var noRecipients = recipients == null || recipients.isEmpty();
final var noGroup = base64GroupId == null || base64GroupId.isEmpty();
if (noRecipients && noGroup) {
throw new Error.Failure("No recipients given");
}
if (!noRecipients && !noGroup) {
throw new Error.Failure("You cannot specify recipients by phone number and groups at the same time");
}
final TypingAction action = typingAction ? TypingAction.START : TypingAction.STOP;
GroupId groupId = null;
if (!noGroup) {
try {
groupId = Util.decodeGroupId(base64GroupId);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id: " + e.getMessage());
}
}
try {
if (groupId != null) {
m.sendGroupTypingMessage(action, groupId);
} else {
m.sendTypingMessage(action, new HashSet<String>(recipients));
}
} catch (UntrustedIdentityException e) {
throw new Error.UntrustedIdentity("Failed to send message: " + e.getMessage());
} catch (IOException e) {
throw new Error.Failure("Failed to send message: " + e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.Failure("Failed to send to group: " + e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.Failure("Invalid number: " + e.getMessage());
}
}
@Override
public void sendTyping(boolean typingAction, byte[] groupId, List<String>recipients) {
String base64GroupId = Base64.getEncoder().encodeToString(groupId);
sendTyping(typingAction, base64GroupId, recipients);
}
// 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.getContactOrProfileName(number);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public void setContactName(final String number, final String name) {
try {
m.setContactName(number, name);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
}
@Override
public void setExpirationTimer(final String number, final int expiration) {
try {
m.setExpirationTimer(number, expiration);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public void setContactBlocked(final String number, final boolean blocked) {
try {
m.setContactBlocked(number, blocked);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
} catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}
}
@Override
public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
try {
m.setGroupBlocked(group.getGroupId(), blocked);
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage());
}
}
@Override
public void setGroupBlocked(final String base64GroupId, final boolean blocked) {
try {
byte[] groupId = GroupId.fromBase64(base64GroupId).serialize();
setGroupBlocked(groupId, blocked);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
}
@Override
public List<byte[]> getGroupIds() {
var groups = m.getGroups();
var ids = new ArrayList<byte[]>(groups.size());
for (var group : groups) {
ids.add(group.getGroupId().serialize());
}
return ids;
}
@Override
public String getGroupName(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getTitle();
}
}
@Override
public List<String> getGroupMembers(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
// To get the group Ids in base 64 format, either use the getBaseGroupIds() method, or
// the getGroupIds(dummy) form, where dummy represents any string
@Override
public List<String> getBase64GroupIds() {
var groups = m.getGroups();
var ids = new ArrayList<String>(groups.size());
for (var group : groups) {
ids.add(group.getGroupId().toBase64());
}
return ids;
}
@Override
public List<String> getGroupIds(String dummy) {
var groups = m.getGroups();
var ids = new ArrayList<String>(groups.size());
for (var group : groups) {
ids.add(group.getGroupId().toBase64());
}
return ids;
}
@Override
public String getGroupName(final String base64GroupId) {
byte[] groupId = null;
GroupInfo group = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure(e.getMessage());
}
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getTitle();
}
}
@Override
public List<String> getGroupMembers(final String base64GroupId) {
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
try {
if (groupId.length == 0) {
groupId = null;
}
if (name.isEmpty()) {
name = null;
}
if (members.isEmpty()) {
members = null;
}
if (avatar.isEmpty()) {
avatar = null;
}
if (groupId == null) {
final var results = m.createGroup(name, members, avatar == null ? null : new File(avatar));
checkSendMessageResults(0, results.second());
return results.first().serialize();
} else {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
final var results = m.updateGroup(group.getGroupId(),
name,
null,
members,
null,
null,
null,
false,
null,
null,
null,
avatar == null ? null : new File(avatar),
null);
if (results != null) {
checkSendMessageResults(results.first(), results.second());
}
return groupId;
}
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
}
}
@Override
public String updateGroup(String base64GroupId, String name, List<String> members, String avatar) {
byte[] groupId = null;
if (!base64GroupId.isEmpty()) {
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
}
groupId = updateGroup(groupId, name, members, avatar);
return Base64.getEncoder().encodeToString(groupId);
}
@Override
public byte[] updateGroup(
byte[] groupId,
String name,
String description,
List<String> addMembers,
List<String> removeMembers,
List<String> addAdmins,
List<String> removeAdmins,
boolean resetGroupLink,
String groupLinkState,
String addMemberPermission,
String editDetailsPermission,
String avatar,
Integer expirationTimer
) {
try {
File avatarFile = null;
if (name.isEmpty()) {
name = null;
}
if (description.isEmpty()) {
description= null;
}
if (addMembers.isEmpty()) {
addMembers = null;
}
if (removeMembers.isEmpty()) {
removeMembers = null;
}
if (addAdmins.isEmpty()) {
addAdmins = null;
}
if (removeAdmins.isEmpty()) {
removeAdmins = null;
}
if (groupLinkState.isEmpty()) {
groupLinkState = null;
}
if (addMemberPermission.isEmpty()) {
addMemberPermission = null;
}
if (editDetailsPermission.isEmpty()) {
editDetailsPermission = null;
}
if (avatar.isEmpty()) {
avatarFile = null;
} else {
avatarFile = new File(avatar);
//check if we are sending an empty file. If so, this tells Signal
// to delete the avatar, and we will delete it from the local AvatarStore
long fileSize = avatarFile.length();
if (fileSize == 0) {
try {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError ae) {
throw new Error.Failure(ae.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
AvatarStore.deleteGroupAvatar(group.getGroupId());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
}
}
if (groupId == null) {
final var results = m.createGroup(name, addMembers, avatar == null ? null : new File(avatar));
checkSendMessageResults(0, results.second());
return results.first().serialize();
} else {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
final var results = m.updateGroup(group.getGroupId(),
name,
description,
addMembers,
removeMembers,
addAdmins,
removeAdmins,
resetGroupLink,
groupLinkState == null ? null : UpdateGroupCommand.getGroupLinkState(groupLinkState),
addMemberPermission == null ? null : UpdateGroupCommand.getGroupPermission(addMemberPermission),
editDetailsPermission == null ? null : UpdateGroupCommand.getGroupPermission(editDetailsPermission),
avatarFile,
expirationTimer
);
if (results != null) {
checkSendMessageResults(results.first(), results.second());
}
return groupId;
}
} catch (UserErrorException | IOException e) {
throw new Error.Failure(e.getMessage());
} catch ( GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
}
}
@Override
public String updateGroup(
String base64GroupId,
String name,
String description,
List<String> addMembers,
List<String> removeMembers,
List<String> addAdmins,
List<String> removeAdmins,
boolean resetGroupLink,
String groupLinkState,
String addMemberPermission,
String editDetailsPermission,
String avatar,
Integer expirationTimer
) {
byte[] groupId = null;
if (!base64GroupId.isEmpty()) {
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Invalid group id format: " + e.getMessage());
}
}
groupId = updateGroup(
groupId,
name,
description,
addMembers,
removeMembers,
addAdmins,
removeAdmins,
resetGroupLink,
groupLinkState,
addMemberPermission,
editDetailsPermission,
avatar,
expirationTimer
);
return Base64.getEncoder().encodeToString(groupId);
}
@Override
public String getGroupInviteUri(byte[] groupId) {
GroupInfo group = null;
GroupInviteLinkUrl groupInviteUri = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
groupInviteUri = group.getGroupInviteLink();
if (groupInviteUri == null) {
return "";
}
return groupInviteUri.getUrl();
}
@Override
public String getGroupInviteUri(String base64GroupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.fromBase64(base64GroupId));
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
GroupInviteLinkUrl groupInviteUri = group.getGroupInviteLink();
return groupInviteUri.getUrl();
}
@Override
public List<String> getGroupPendingMembers(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getPendingMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public List<String> getGroupPendingMembers(final String base64GroupId) {
byte [] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getPendingMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public List<String> getGroupRequestingMembers(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getRequestingMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public List<String> getGroupRequestingMembers(final String base64GroupId) {
byte [] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getRequestingMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public List<String> getGroupAdminMembers(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getAdminMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public List<String> getGroupAdminMembers(final String base64GroupId) {
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("base64GroupId" + " is not a valid Base 64 string " + e.getMessage());
}
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getAdminMembers()
.stream()
.map(m::resolveSignalServiceAddress)
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
}
}
@Override
public boolean isRegistered(String number) {
if (number.isEmpty()) {
return false;
}
try {
Map<String, Boolean> registered;
List<String> numbers = new ArrayList<String>();
numbers.add(number);
registered = m.areUsersRegistered(new HashSet<String>(numbers));
return registered.get(number);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public List<Boolean> isRegistered(List<String> numbers) {
List<Boolean> results = new ArrayList<Boolean> ();
if (numbers.isEmpty()) {
return results;
}
try {
Map<String, Boolean> registered;
registered = m.areUsersRegistered(new HashSet<String>(numbers));
for (String number : numbers) {
results.add(registered.get(number));
}
return results;
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@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, null, about, aboutEmoji, avatarFile);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
}
@Override
public void updateProfile(
final String givenName,
final String familyName,
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(givenName, familyName, about, aboutEmoji, avatarFile);
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
}
@Override
public void removePin() {
try {
m.setRegistrationLockPin(Optional.absent());
} catch (UnauthenticatedResponseException e) {
throw new Error.Failure("Remove pin failed with unauthenticated response: " + e.getMessage());
} catch (IOException e) {
throw new Error.Failure("Remove pin error: " + e.getMessage());
}
}
@Override
public void setPin(String registrationLockPin) {
try {
m.setRegistrationLockPin(Optional.of(registrationLockPin));
} catch (UnauthenticatedResponseException e) {
throw new Error.Failure("Set pin error failed with unauthenticated response: " + e.getMessage());
} catch (IOException e) {
throw new Error.Failure("Set pin error: " + e.getMessage());
}
}
// Provide option to query a version string in order to react on potential
// future interface changes
@Override
public String version() {
return DbusSignalControlImpl.version();
}
@Override
public String link() {
return DbusSignalControlImpl.link();
}
@Override
public String link(String newDeviceName) {
return DbusSignalControlImpl.link(newDeviceName);
}
@Override
public void addDevice(String uri) {
try {
m.addDeviceLink(new URI(uri));
} catch (IOException | InvalidKeyException e) {
throw new Error.Failure(e.getClass().getSimpleName() + "Add device link failed. " + e.getMessage());
} catch (URISyntaxException e) {
throw new Error.Failure(e.getClass().getSimpleName() + "Device link uri has invalid format: " + e.getMessage());
}
}
@Override
public void removeDevice(int deviceId) {
try {
m.removeLinkedDevices(deviceId);
} catch (IOException e) {
throw new Error.Failure(e.getClass().getSimpleName() + "Error while removing device: " + e.getMessage());
}
}
@Override
public void register(
String number, boolean voiceVerification
) {
DbusSignalControlImpl.register(number, voiceVerification);
}
@Override
public void registerWithCaptcha(
String number, boolean voiceVerification, String captcha
) {
DbusSignalControlImpl.registerWithCaptcha(number, voiceVerification, captcha);
}
@Override
public void unregister() {
try {
m.unregister();
logger.info("Unregister succeeded, exiting.\n");
System.exit(0);
} catch (IOException e) {
throw new Error.Failure(e.getClass().getSimpleName() + "Unregister error: " + e.getMessage());
}
}
@Override
public void verify(String number, String verificationCode) {
DbusSignalControlImpl.verify(number, verificationCode);
}
@Override
public void verifyWithPin(String number, String verificationCode, String pin) {
DbusSignalControlImpl.verifyWithPin(number, verificationCode, pin);
}
// 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(IdentityInfo::getRecipientId),
m.getContacts().stream().map(Pair::first))
.map(m::resolveSignalServiceAddress)
.map(a -> a.getNumber().orNull())
.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 (name.equals(c.second().getName())) {
numbers.add(getLegacyIdentifier(m.resolveSignalServiceAddress(c.first())));
}
}
// Try profiles if no contact name was found
for (var identity : m.getIdentities()) {
final var recipientId = identity.getRecipientId();
final var address = m.resolveSignalServiceAddress(recipientId);
var number = address.getNumber().orNull();
if (number != null) {
var profile = m.getRecipientProfile(recipientId);
if (profile != null && profile.getDisplayName().equals(name)) {
numbers.add(number);
}
}
}
return numbers;
}
@Override
public void quitGroup(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
try {
m.sendQuitGroupMessage(group.getGroupId(), Set.of());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (IOException | LastGroupAdminException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public void quitGroup(final String base64GroupId) {
byte [] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
try {
m.sendQuitGroupMessage(group.getGroupId(), Set.of());
} catch (GroupNotFoundException | NotAGroupMemberException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (IOException | LastGroupAdminException e) {
throw new Error.Failure(e.getMessage());
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(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) {
try {
return m.isContactBlocked(number);
} catch (InvalidNumberException e) {
throw new Error.InvalidNumber(e.getMessage());
}
}
@Override
public boolean isGroupBlocked(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.isBlocked();
}
}
@Override
public boolean isGroupBlocked(final String base64GroupId) {
GroupInfo group = null;
byte [] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.isBlocked();
}
}
@Override
public boolean isMember(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.isMember(m.getSelfRecipientId());
}
}
@Override
public boolean isMember(final String base64GroupId) {
GroupInfo group = null;
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.isMember(m.getSelfRecipientId());
}
}
@Override
public List<String> isMember(final byte[] groupId, List<String>members, boolean setMemberStatus) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
if (setMemberStatus) {
var results = m.updateGroup(group.getGroupId(),
null,
null,
members,
null,
null,
null,
false,
null,
null,
null,
null,
null);
} else {
var results = m.updateGroup(group.getGroupId(),
null,
null,
null,
members,
null,
null,
false,
null,
null,
null,
null,
null);
}
return getGroupMembers(groupId);
} catch (NotAGroupMemberException | InvalidNumberException | AssertionError | IOException | AttachmentInvalidException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound("Group not found.");
}
}
@Override
public List<String> isMember(final String base64GroupId, List<String> members, boolean setMemberStatus) {
GroupInfo group = null;
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
return isMember(groupId, members, setMemberStatus);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
}
@Override
public boolean isAdmin(final byte[] groupId) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getAdminMembers().contains(m.getSelfRecipientId());
}
}
@Override
public boolean isAdmin(final String base64GroupId) {
GroupInfo group = null;
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
} catch (AssertionError e) {
throw new Error.Failure(e.getMessage());
}
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
} else {
return group.getAdminMembers().contains(m.getSelfRecipientId());
}
}
@Override
public List<String> isAdmin(final byte[] groupId, List<String>admins, boolean setAdminStatus) {
GroupInfo group = null;
try {
group = m.getGroup(GroupId.unknownVersion(groupId));
if (group == null) {
throw new Error.GroupNotFound("Error finding group");
}
if (setAdminStatus) {
var results = m.updateGroup(group.getGroupId(),
null,
null,
null,
null,
admins,
null,
false,
null,
null,
null,
null,
null);
} else {
var results = m.updateGroup(group.getGroupId(),
null,
null,
null,
null,
null,
admins,
false,
null,
null,
null,
null,
null);
}
return getGroupAdminMembers(groupId);
} catch (NotAGroupMemberException | InvalidNumberException | AssertionError | IOException | AttachmentInvalidException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound("Group not found.");
}
}
@Override
public List<String> isAdmin(final String base64GroupId, List<String> admins, boolean setAdminStatus) {
GroupInfo group = null;
byte[] groupId = null;
try {
groupId = GroupId.fromBase64(base64GroupId).serialize();
return isAdmin(groupId, admins, setAdminStatus);
} catch (GroupIdFormatException e) {
throw new Error.Failure("Incorrect format: " + e.getMessage());
}
}
@Override
public void uploadStickerPack(String stickerPackPath) {
File path = new File(stickerPackPath);
try {
var url = m.uploadStickerPack(path);
} catch (IOException e) {
throw new Error.Failure("Upload error (maybe image size is too large):" + e.getMessage());
} catch (StickerPackInvalidException e) {
throw new Error.Failure("Invalid sticker pack: " + e.getMessage());
}
}
}