import org.asamk.Signal;
import org.asamk.signal.DbusConfig;
-import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager;
-import org.asamk.signal.manager.NotMasterDeviceException;
-import org.asamk.signal.manager.StickerPackInvalidException;
+import org.asamk.signal.manager.api.AttachmentInvalidException;
import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Group;
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.NotMasterDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
+import org.asamk.signal.manager.api.StickerPack;
+import org.asamk.signal.manager.api.StickerPackInvalidException;
+import org.asamk.signal.manager.api.StickerPackUrl;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.api.UpdateGroup;
+import org.asamk.signal.manager.api.UserStatus;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
}
@Override
- public void checkAccountState() throws IOException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Map<String, Pair<String, UUID>> areUsersRegistered(final Set<String> numbers) throws IOException {
+ public Map<String, UserStatus> getUserStatus(final Set<String> numbers) throws IOException {
final var numbersList = new ArrayList<>(numbers);
final var registered = signal.isRegistered(numbersList);
- final var result = new HashMap<String, Pair<String, UUID>>();
+ final var result = new HashMap<String, UserStatus>();
for (var i = 0; i < numbersList.size(); i++) {
result.put(numbersList.get(i),
- new Pair<>(numbersList.get(i), registered.get(i) ? RecipientAddress.UNKNOWN_UUID : null));
+ new UserStatus(numbersList.get(i),
+ registered.get(i) ? RecipientAddress.UNKNOWN_UUID : null,
+ false));
}
return result;
}
@Override
public Configuration getConfiguration() {
- throw new UnsupportedOperationException();
+ final var configuration = getRemoteObject(new DBusPath(signal.getObjectPath() + "/Configuration"),
+ Signal.Configuration.class).GetAll("org.asamk.Signal.Configuration");
+ return new Configuration(Optional.of((Boolean) configuration.get("ReadReceipts").getValue()),
+ Optional.of((Boolean) configuration.get("UnidentifiedDeliveryIndicators").getValue()),
+ Optional.of((Boolean) configuration.get("TypingIndicators").getValue()),
+ Optional.of((Boolean) configuration.get("LinkPreviews").getValue()));
}
@Override
- public void updateConfiguration(Configuration configuration) throws IOException {
- throw new UnsupportedOperationException();
+ public void updateConfiguration(Configuration newConfiguration) throws IOException {
+ final var configuration = getRemoteObject(new DBusPath(signal.getObjectPath() + "/Configuration"),
+ Signal.Configuration.class);
+ newConfiguration.readReceipts()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "ReadReceipts", v));
+ newConfiguration.unidentifiedDeliveryIndicators()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration",
+ "UnidentifiedDeliveryIndicators",
+ v));
+ newConfiguration.typingIndicators()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "TypingIndicators", v));
+ newConfiguration.linkPreviews()
+ .ifPresent(v -> configuration.Set("org.asamk.Signal.Configuration", "LinkPreviews", v));
}
@Override
emptyIfNull(about),
emptyIfNull(aboutEmoji),
avatar == null ? "" : avatar.map(File::getPath).orElse(""),
- avatar != null && !avatar.isPresent());
+ avatar != null && avatar.isEmpty());
}
@Override
public void unregister() throws IOException {
- throw new UnsupportedOperationException();
+ signal.unregister();
}
@Override
public void deleteAccount() throws IOException {
- throw new UnsupportedOperationException();
+ signal.deleteAccount();
}
@Override
return signal.listDevices().stream().map(d -> {
final var device = getRemoteObject(d.getObjectPath(),
Signal.Device.class).GetAll("org.asamk.Signal.Device");
- return new Device((long) device.get("Id").getValue(),
+ return new Device((Integer) device.get("Id").getValue(),
(String) device.get("Name").getValue(),
(long) device.get("Created").getValue(),
(long) device.get("LastSeen").getValue(),
thisDevice.equals(d.getObjectPath()));
- }).collect(Collectors.toList());
+ }).toList();
}
@Override
- public void removeLinkedDevices(final long deviceId) throws IOException {
+ public void removeLinkedDevices(final int deviceId) throws IOException {
final var devicePath = signal.getDevice(deviceId);
getRemoteObject(devicePath, Signal.Device.class).removeDevice();
}
@Override
public List<Group> getGroups() {
final var groups = signal.listGroups();
- return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).collect(Collectors.toList());
+ return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).toList();
}
@Override
@Override
public void deleteGroup(final GroupId groupId) throws IOException {
- throw new UnsupportedOperationException();
+ final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
+ group.deleteGroup();
}
@Override
final String name, final Set<RecipientIdentifier.Single> members, final File avatarFile
) throws IOException, AttachmentInvalidException {
final var newGroupId = signal.createGroup(emptyIfNull(name),
- members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
+ members.stream().map(RecipientIdentifier.Single::getIdentifier).toList(),
avatarFile == null ? "" : avatarFile.getPath());
return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
}
: GroupPermission.EVERY_MEMBER.name());
}
if (updateGroup.getMembers() != null) {
- group.addMembers(updateGroup.getMembers()
- .stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ group.addMembers(updateGroup.getMembers().stream().map(RecipientIdentifier.Single::getIdentifier).toList());
}
if (updateGroup.getRemoveMembers() != null) {
group.removeMembers(updateGroup.getRemoveMembers()
.stream()
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ .toList());
}
if (updateGroup.getAdmins() != null) {
- group.addAdmins(updateGroup.getAdmins()
- .stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ group.addAdmins(updateGroup.getAdmins().stream().map(RecipientIdentifier.Single::getIdentifier).toList());
}
if (updateGroup.getRemoveAdmins() != null) {
group.removeAdmins(updateGroup.getRemoveAdmins()
.stream()
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ .toList());
}
if (updateGroup.isResetGroupLink()) {
group.resetLink();
signal.sendTyping(signal.getSelfNumber(), action == TypingAction.STOP);
return 0L;
}, groupId -> {
- throw new UnsupportedOperationException();
+ signal.sendGroupTyping(groupId, action == TypingAction.STOP);
+ return 0L;
});
}
@Override
public SendMessageResults sendEndSessionMessage(final Set<RecipientIdentifier.Single> recipients) throws IOException {
- signal.sendEndSessionMessage(recipients.stream()
- .map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList()));
+ signal.sendEndSessionMessage(recipients.stream().map(RecipientIdentifier.Single::getIdentifier).toList());
return new SendMessageResults(0, Map.of());
}
+ @Override
+ public void deleteRecipient(final RecipientIdentifier.Single recipient) {
+ signal.deleteRecipient(recipient.getIdentifier());
+ }
+
+ @Override
+ public void deleteContact(final RecipientIdentifier.Single recipient) {
+ signal.deleteContact(recipient.getIdentifier());
+ }
+
@Override
public void setContactName(
final RecipientIdentifier.Single recipient, final String name
}
@Override
- public URI uploadStickerPack(final File path) throws IOException, StickerPackInvalidException {
+ public StickerPackUrl uploadStickerPack(final File path) throws IOException, StickerPackInvalidException {
try {
- return new URI(signal.uploadStickerPack(path.getPath()));
- } catch (URISyntaxException e) {
+ return StickerPackUrl.fromUri(new URI(signal.uploadStickerPack(path.getPath())));
+ } catch (URISyntaxException | StickerPackUrl.InvalidStickerPackLinkException e) {
throw new AssertionError(e);
}
}
+ @Override
+ public List<StickerPack> getStickerPacks() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void requestAllSyncData() throws IOException {
signal.sendSyncRequest();
@Override
public void receiveMessages(
- final long timeout, final TimeUnit unit, final ReceiveMessageHandler handler
+ final Duration timeout, final ReceiveMessageHandler handler
) throws IOException {
- addReceiveHandler(handler);
- try {
- Thread.sleep(unit.toMillis(timeout));
- } catch (InterruptedException ignored) {
+ final var lastMessage = new AtomicLong(System.currentTimeMillis());
+
+ final ReceiveMessageHandler receiveHandler = (envelope, e) -> {
+ lastMessage.set(System.currentTimeMillis());
+ handler.handleMessage(envelope, e);
+ };
+ addReceiveHandler(receiveHandler);
+ while (true) {
+ try {
+ final var sleepTimeRemaining = timeout.toMillis() - (System.currentTimeMillis() - lastMessage.get());
+ if (sleepTimeRemaining < 0) {
+ break;
+ }
+ Thread.sleep(sleepTimeRemaining);
+ } catch (InterruptedException ignored) {
+ }
}
- removeReceiveHandler(handler);
+ removeReceiveHandler(receiveHandler);
}
@Override
@Override
public List<Pair<RecipientAddress, Contact>> getContacts() {
- throw new UnsupportedOperationException();
+ return signal.listNumbers().stream().map(n -> {
+ final var contactName = signal.getContactName(n);
+ if (contactName.length() == 0) {
+ return null;
+ }
+ return new Pair<>(new RecipientAddress(null, n),
+ new Contact(contactName, null, 0, signal.isContactBlocked(n), false));
+ }).filter(Objects::nonNull).toList();
}
@Override
((List<String>) group.get("Admins").getValue()).stream()
.map(m -> new RecipientAddress(null, m))
.collect(Collectors.toSet()),
+ ((List<String>) group.get("Banned").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
(boolean) group.get("IsBlocked").getValue(),
(int) group.get("MessageExpirationTimer").getValue(),
GroupPermission.valueOf((String) group.get("PermissionAddMember").getValue()),
throw new UnsupportedOperationException();
}
+ @Override
+ public void addAddressChangedListener(final Runnable listener) {
+ }
+
@Override
public void addClosedListener(final Runnable listener) {
synchronized (closedListeners) {
}
@Override
- public void close() throws IOException {
+ public void close() {
synchronized (this) {
this.notify();
}
.filter(r -> r instanceof RecipientIdentifier.Single)
.map(RecipientIdentifier.Single.class::cast)
.map(RecipientIdentifier.Single::getIdentifier)
- .collect(Collectors.toList());
+ .toList();
if (singleRecipients.size() > 0) {
timestamp = recipientsHandler.apply(singleRecipients);
}
.filter(r -> r instanceof RecipientIdentifier.Group)
.map(RecipientIdentifier.Group.class::cast)
.map(RecipientIdentifier.Group::groupId)
- .collect(Collectors.toList());
+ .toList();
for (final var groupId : groupRecipients) {
timestamp = groupHandler.apply(groupId.serialize());
}
return string == null ? "" : string;
}
- private <T extends DBusInterface> T getRemoteObject(final DBusPath devicePath, final Class<T> type) {
+ private <T extends DBusInterface> T getRemoteObject(final DBusPath path, final Class<T> type) {
try {
- return connection.getRemoteObject(DbusConfig.getBusname(), devicePath.getPath(), type);
+ return connection.getRemoteObject(DbusConfig.getBusname(), path.getPath(), type);
} catch (DBusException e) {
throw new AssertionError(e);
}
? Optional.empty()
: Optional.of(new RecipientAddress(null, syncReceived.getDestination())),
Set.of(),
- new MessageEnvelope.Data(syncReceived.getTimestamp(),
+ Optional.of(new MessageEnvelope.Data(syncReceived.getTimestamp(),
syncReceived.getGroupId().length > 0
? Optional.of(new MessageEnvelope.Data.GroupContext(GroupId.unknownVersion(
syncReceived.getGroupId()), false, 0))
Optional.empty(),
List.of(),
List.of(),
- List.of()))),
+ List.of())))),
Optional.empty(),
List.of(),
List.of(),
private void notifyMessageHandlers(final MessageEnvelope envelope) {
synchronized (messageHandlers) {
- Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> {
- h.handleMessage(envelope, null);
- });
+ Stream.concat(messageHandlers.stream(), weakHandlers.stream())
+ .forEach(h -> h.handleMessage(envelope, null));
}
}
getValue(a, "isVoiceNote"),
getValue(a, "isGif"),
getValue(a, "isBorderless"));
- }).collect(Collectors.toList());
+ }).toList();
}
@SuppressWarnings("unchecked")