import org.asamk.signal.BaseConfig;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.AttachmentInvalidException;
+import org.asamk.signal.manager.api.CaptchaRejectedException;
import org.asamk.signal.manager.api.DeviceLinkUrl;
import org.asamk.signal.manager.api.GroupId;
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
import org.asamk.signal.manager.api.NotAGroupMemberException;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.PendingAdminApprovalException;
+import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.api.UpdateProfile;
import org.asamk.signal.manager.api.UserStatus;
+import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.SendMessageResultUtils;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import static org.asamk.signal.dbus.DbusUtils.makeValidObjectPathElement;
-public class DbusSignalImpl implements Signal {
+public class DbusSignalImpl implements Signal, AutoCloseable {
private final Manager m;
private final DBusConnection connection;
private DbusReceiveMessageHandler dbusMessageHandler;
private int subscriberCount;
- private final static Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class);
+ private static final Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class);
public DbusSignalImpl(
final Manager m, DBusConnection connection, final String objectPath, final boolean noReceiveOnStart
updateIdentities();
}
+ @Override
public void close() {
if (dbusMessageHandler != null) {
m.removeReceiveHandler(dbusMessageHandler);
m.submitRateLimitRecaptchaChallenge(challenge, captcha);
} catch (IOException e) {
throw new Error.Failure("Submit challenge error: " + e.getMessage());
+ } catch (CaptchaRejectedException e) {
+ throw new Error.Failure(
+ "Captcha rejected, it may be outdated, already used or solved from a different IP address.");
}
-
}
@Override
m.addDeviceLink(deviceLinkUrl);
} catch (IOException | InvalidDeviceLinkException e) {
throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage());
+ } catch (NotPrimaryDeviceException e) {
+ throw new Error.Failure("This command doesn't work on linked devices.");
} catch (URISyntaxException e) {
throw new Error.InvalidUri(e.getClass().getSimpleName()
+ " Device link uri has invalid format: "
}
@Override
- public long sendMessage(final String message, final List<String> attachments, final List<String> recipients) {
+ public long sendMessage(final String messageText, final List<String> attachments, final List<String> recipients) {
try {
- final var results = m.sendMessage(new Message(message,
- attachments,
- List.of(),
- Optional.empty(),
- Optional.empty(),
- List.of(),
- Optional.empty(),
- List.of()),
- getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
- .map(RecipientIdentifier.class::cast)
- .collect(Collectors.toSet()));
+ final var message = new Message(messageText,
+ attachments,
+ List.of(),
+ Optional.empty(),
+ Optional.empty(),
+ List.of(),
+ Optional.empty(),
+ List.of());
+ final var recipientIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
+ .map(RecipientIdentifier.class::cast)
+ .collect(Collectors.toSet());
+ final var results = m.sendMessage(message, recipientIdentifiers, false);
checkSendMessageResults(results);
return results.timestamp();
public void sendReadReceipt(
final String recipient, final List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity {
- try {
- final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
- messageIds);
- checkSendMessageResults(results);
- } catch (IOException e) {
- throw new Error.Failure(e.getMessage());
- }
+ final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
+ checkSendMessageResults(results);
}
@Override
public void sendViewedReceipt(
final String recipient, final List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity {
- try {
- final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
- messageIds);
- checkSendMessageResults(results);
- } catch (IOException e) {
- throw new Error.Failure(e.getMessage());
- }
+ final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds);
+ checkSendMessageResults(results);
}
@Override
@Override
public long sendNoteToSelfMessage(
- final String message, final List<String> attachments
+ final String messageText, final List<String> attachments
) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity {
try {
- final var results = m.sendMessage(new Message(message,
+ final var message = new Message(messageText,
attachments,
List.of(),
Optional.empty(),
Optional.empty(),
List.of(),
Optional.empty(),
- List.of()), Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
+ List.of());
+ final var results = m.sendMessage(message, Set.of(RecipientIdentifier.NoteToSelf.INSTANCE), false);
checkSendMessageResults(results);
return results.timestamp();
} catch (AttachmentInvalidException e) {
}
@Override
- public long sendGroupMessage(final String message, final List<String> attachments, final byte[] groupId) {
+ public long sendGroupMessage(final String messageText, final List<String> attachments, final byte[] groupId) {
try {
- var results = m.sendMessage(new Message(message,
+ final var message = new Message(messageText,
attachments,
List.of(),
Optional.empty(),
Optional.empty(),
List.of(),
Optional.empty(),
- List.of()), Set.of(getGroupRecipientIdentifier(groupId)));
+ List.of());
+ var results = m.sendMessage(message, Set.of(getGroupRecipientIdentifier(groupId)), false);
checkSendMessageResults(results);
return results.timestamp();
} catch (IOException | InvalidStickerException e) {
m.setContactName(getSingleRecipientIdentifier(number, m.getSelfNumber()), name, "");
} catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
- } catch (IOException e) {
- throw new Error.Failure("Contact is not registered.");
} catch (UnregisteredRecipientException e) {
throw new Error.UntrustedIdentity(e.getSender().getIdentifier() + " is not registered.");
}
}
@Override
+ @Deprecated
public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
try {
m.setGroupsBlocked(List.of(getGroupId(groupId)), blocked);
}
@Override
+ @Deprecated
public List<byte[]> getGroupIds() {
var groups = m.getGroups();
return groups.stream().map(g -> g.groupId().serialize()).toList();
}
@Override
+ @Deprecated
public String getGroupName(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
if (group == null || group.title() == null) {
}
@Override
+ @Deprecated
public List<String> getGroupMembers(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
if (group == null) {
public byte[] createGroup(
final String name, final List<String> members, final String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber {
- return updateGroup(new byte[0], name, members, avatar);
+ return updateGroupInternal(new byte[0], name, members, avatar);
}
@Override
+ @Deprecated
public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
+ return updateGroupInternal(groupId, name, members, avatar);
+ }
+
+ public byte[] updateGroupInternal(byte[] groupId, String name, List<String> members, String avatar) {
try {
groupId = nullIfEmpty(groupId);
name = nullIfEmpty(name);
}
@Override
+ @Deprecated
public boolean isRegistered() {
return true;
}
@Override
public boolean isRegistered(String number) {
var result = isRegistered(List.of(number));
- return result.get(0);
+ return result.getFirst();
}
@Override
registered = m.getUserStatus(new HashSet<>(numbers));
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
+ } catch (RateLimitException e) {
+ throw new Error.Failure(e.getMessage()
+ + ", retry at "
+ + DateUtils.formatTimestamp(e.getNextAttemptTimestamp()));
}
return numbers.stream().map(number -> registered.get(number).uuid() != null).toList();
}
@Override
+ @Deprecated
public void quitGroup(final byte[] groupId) {
var group = getGroupId(groupId);
try {
}
@Override
+ @Deprecated
public boolean isGroupBlocked(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
if (group == null) {
}
@Override
+ @Deprecated
public boolean isMember(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
if (group == null) {
}
var errors = SendMessageResultUtils.getErrorMessagesFromSendMessageResults(results);
- if (errors.size() == 0 || errors.size() < results.size()) {
+ if (errors.isEmpty() || errors.size() < results.size()) {
return;
}
connection.exportObject(object);
logger.debug("Exported dbus object: " + object.getObjectPath());
} catch (DBusException e) {
- e.printStackTrace();
+ logger.warn("Failed to export dbus object (" + object.getObjectPath() + "): " + e.getMessage());
}
}
final var object = new DbusSignalIdentityImpl(i);
exportObject(object);
this.identities.add(new StructIdentity(new DBusPath(object.getObjectPath()),
- emptyIfNull(i.recipient().getIdentifier()),
- i.recipient().getLegacyIdentifier()));
+ i.recipient().uuid().map(UUID::toString).orElse(""),
+ i.recipient().number().orElse("")));
});
}
@Override
public DBusPath getIdentity(String number) throws Error.Failure {
-
- final var found = identities.stream().filter(identity -> identity.getName().equals(number)).findFirst();
+ final var found = identities.stream()
+ .filter(identity -> identity.getNumber().equals(number) || identity.getUuid().equals(number))
+ .findFirst();
if (found.isEmpty()) {
- throw new Error.Failure("Identity for " + number + " unkown");
+ throw new Error.Failure("Identity for " + number + " unknown");
}
return found.get().getObjectPath();
}
List.of(new DbusProperty<>("Number", () -> identity.recipient().number().orElse("")),
new DbusProperty<>("Uuid",
() -> identity.recipient().uuid().map(UUID::toString).orElse("")),
- new DbusProperty<>("Fingerprint", () -> identity.getFingerprint()),
+ new DbusProperty<>("Fingerprint", identity::getFingerprint),
new DbusProperty<>("SafetyNumber", identity::safetyNumber),
new DbusProperty<>("ScannableSafetyNumber", identity::scannableSafetyNumber),
new DbusProperty<>("TrustLevel", identity::trustLevel),
@Override
public String getObjectPath() {
- return getIdentityObjectPath(objectPath, identity.recipient().getLegacyIdentifier());
+ return getIdentityObjectPath(objectPath,
+ identity.recipient().getLegacyIdentifier() + "_" + identity.recipient().getIdentifier());
}
@Override
} catch (UnregisteredRecipientException e) {
throw new Error.Failure("The user " + e.getSender().getIdentifier() + " is not registered.");
}
+ updateIdentities();
}
@Override
} catch (UnregisteredRecipientException e) {
throw new Error.Failure("The user " + e.getSender().getIdentifier() + " is not registered.");
}
+ updateIdentities();
}
}
try {
m.removeLinkedDevices(device.id());
updateDevices();
+ } catch (NotPrimaryDeviceException e) {
+ throw new Error.Failure("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
throw new Error.Failure("Only the name of this device can be changed");
}
try {
- m.updateAccountAttributes(name);
+ m.updateAccountAttributes(name, null, null, null);
// update device list
updateDevices();
} catch (IOException e) {
public class DbusSignalConfigurationImpl extends DbusProperties implements Signal.Configuration {
- public DbusSignalConfigurationImpl(
- ) {
+ public DbusSignalConfigurationImpl() {
super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Configuration",
List.of(new DbusProperty<>("ReadReceipts", this::getReadReceipts, this::setReadReceipts),
new DbusProperty<>("UnidentifiedDeliveryIndicators",
Optional.ofNullable(unidentifiedDeliveryIndicators),
Optional.ofNullable(typingIndicators),
Optional.ofNullable(linkPreviews)));
- } catch (IOException e) {
- throw new Error.Failure("UpdateAccount error: " + e.getMessage());
} catch (NotPrimaryDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices.");
}