import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.storage.groups.JsonGroupStore;
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
-import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
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.SignalServiceStickerManifestUpload;
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.ZipEntry;
return username;
}
+ public SignalServiceAddress getSelfAddress() {
+ return account.getSelfAddress();
+ }
+
private SignalServiceAccountManager getSignalServiceAccountManager() {
- return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, null, account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
+ return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
}
private IdentityKey getIdentity() {
accountManager = getSignalServiceAccountManager();
try {
- if (account.isRegistered() && accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
- refreshPreKeys();
- account.save();
+ if (account.isRegistered()) {
+ if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
+ refreshPreKeys();
+ account.save();
+ }
+ if (account.getUuid() == null) {
+ account.setUuid(accountManager.getOwnUuid());
+ account.save();
+ }
}
} catch (AuthorizationFailedException e) {
System.err.println("Authorization failed, was the number registered elsewhere?");
throw new IOException("Received invalid profileKey", e);
}
}
- account = SignalAccount.createLinkedAccount(dataPath, username, account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
+ account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
refreshPreKeys();
verificationCode = verificationCode.replace("-", "");
account.setSignalingKey(KeyUtils.createSignalingKey());
// TODO make unrestricted unidentified access configurable
- accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities);
+ UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, capabilities);
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
account.setRegistered(true);
+ account.setUuid(uuid);
account.setRegistrationLockPin(pin);
refreshPreKeys();
if (g == null) {
throw new GroupNotFoundException(groupId);
}
- for (String member : g.members) {
- if (member.equals(account.getUsername())) {
- return g;
- }
+ if (!g.isMember(account.getSelfAddress())) {
+ throw new NotAGroupMemberException(groupId, g.name);
}
- throw new NotAGroupMemberException(groupId, g.name);
+ return g;
}
public List<GroupInfo> getGroups() {
@Override
public void sendGroupMessage(String messageText, List<String> attachments,
byte[] groupId)
- throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) {
messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
.build();
messageBuilder.asGroupMessage(group);
}
- ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
- if (thread != null) {
- messageBuilder.withExpiration(thread.messageExpirationTime);
- }
final GroupInfo g = getGroupForSending(groupId);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ messageBuilder.withExpiration(g.messageExpirationTime);
+
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
public void sendGroupMessageReaction(String emoji, boolean remove, SignalServiceAddress targetAuthor,
long targetSentTimestamp, byte[] groupId)
- throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, targetAuthor, targetSentTimestamp);
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withReaction(reaction)
messageBuilder.asGroupMessage(group);
}
final GroupInfo g = getGroupForSending(groupId);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, InvalidNumberException {
+ public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
.withId(groupId)
.build();
.asGroupMessage(group);
final GroupInfo g = getGroupForSending(groupId);
- g.members.remove(account.getUsername());
+ g.removeMember(account.getSelfAddress());
account.getGroupStore().updateGroup(g);
- sendMessageLegacy(messageBuilder, getSignalServiceAddresses(g.members));
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+ private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
GroupInfo g;
if (groupId == null) {
// Create new group
g = new GroupInfo(KeyUtils.createGroupId());
- g.members.add(account.getUsername());
+ g.addMembers(Collections.singleton(account.getSelfAddress()));
} else {
g = getGroupForSending(groupId);
}
}
if (members != null) {
- Set<String> newMembers = new HashSet<>();
- for (String member : members) {
- member = Utils.canonicalizeNumber(member, account.getUsername());
- if (g.members.contains(member)) {
+ final Set<String> newE164Members = new HashSet<>();
+ for (SignalServiceAddress member : members) {
+ if (g.isMember(member) || !member.getNumber().isPresent()) {
continue;
}
- newMembers.add(member);
- g.members.add(member);
+ newE164Members.add(member.getNumber().get());
}
- final List<ContactTokenDetails> contacts = accountManager.getContacts(newMembers);
- if (contacts.size() != newMembers.size()) {
+
+ final List<ContactTokenDetails> contacts = accountManager.getContacts(newE164Members);
+ if (contacts.size() != newE164Members.size()) {
// Some of the new members are not registered on Signal
for (ContactTokenDetails contact : contacts) {
- newMembers.remove(contact.getNumber());
+ newE164Members.remove(contact.getNumber());
}
- System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
+ System.err.println("Failed to add members " + Util.join(", ", newE164Members) + " to group: Not registered on Signal");
System.err.println("Aborting…");
System.exit(1);
}
+
+ g.addMembers(members);
}
if (avatarFile != null) {
SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
- final Collection<SignalServiceAddress> membersSend = getSignalServiceAddresses(g.members);
- // Don't send group message to ourself
- membersSend.remove(account.getSelfAddress());
- sendMessageLegacy(messageBuilder, membersSend);
+ sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
return g.groupId;
}
}
GroupInfo g = getGroupForSending(groupId);
- if (!g.members.contains(recipient.getNumber().get())) {
+ if (!g.isMember(recipient)) {
return;
}
}
}
- SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
- .asGroupMessage(group.build());
-
- ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(g.groupId));
- if (thread != null) {
- messageBuilder.withExpiration(thread.messageExpirationTime);
- }
-
- return messageBuilder;
+ return SignalServiceDataMessage.newBuilder()
+ .asGroupMessage(group.build())
+ .withExpiration(g.messageExpirationTime);
}
private void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build());
- ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
- if (thread != null) {
- messageBuilder.withExpiration(thread.messageExpirationTime);
- }
-
// Send group info request message to the recipient who sent us a message with this groupId
sendMessageLegacy(messageBuilder, Collections.singleton(recipient));
}
+ private void sendReceipt(SignalServiceAddress remoteAddress, long messageId) throws IOException, UntrustedIdentityException {
+ SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
+ Collections.singletonList(messageId),
+ System.currentTimeMillis());
+
+ getMessageSender().sendReceipt(remoteAddress, getAccessFor(remoteAddress), receiptMessage);
+ }
+
@Override
public void sendMessage(String message, List<String> attachments, String recipient)
throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException {
public List<String> getGroupMembers(byte[] groupId) {
GroupInfo group = getGroup(groupId);
if (group == null) {
- return new ArrayList<>();
+ return Collections.emptyList();
} else {
- return new ArrayList<>(group.members);
+ return new ArrayList<>(group.getMembersE164());
}
}
if (avatar.isEmpty()) {
avatar = null;
}
- return sendUpdateGroupMessage(groupId, name, members, avatar);
+ return sendUpdateGroupMessage(groupId, name, members == null ? null : getSignalServiceAddresses(members), avatar);
}
/**
- * Change the expiration timer for a thread (number of groupId)
+ * Change the expiration timer for a contact
*/
- public void setExpirationTimer(String numberOrGroupId, int messageExpirationTimer) {
- ThreadInfo thread = account.getThreadStore().getThread(numberOrGroupId);
- thread.messageExpirationTime = messageExpirationTimer;
- account.getThreadStore().updateThread(thread);
+ public void setExpirationTimer(SignalServiceAddress address, int messageExpirationTimer) {
+ ContactInfo c = account.getContactStore().getContact(address);
+ c.messageExpirationTime = messageExpirationTimer;
+ account.getContactStore().updateContact(c);
+ }
+
+ /**
+ * Change the expiration timer for a group
+ */
+ public void setExpirationTimer(byte[] groupId, int messageExpirationTimer) {
+ GroupInfo g = account.getGroupStore().getGroup(groupId);
+ g.messageExpirationTime = messageExpirationTimer;
+ account.getGroupStore().updateGroup(g);
}
/**
// Send to all individually, so sync messages are sent correctly
List<SendMessageResult> results = new ArrayList<>(recipients.size());
for (SignalServiceAddress address : recipients) {
- ThreadInfo thread = account.getThreadStore().getThread(address.getNumber().get());
- if (thread != null) {
- messageBuilder.withExpiration(thread.messageExpirationTime);
+ ContactInfo contact = account.getContactStore().getContact(address);
+ if (contact != null) {
+ messageBuilder.withExpiration(contact.messageExpirationTime);
} else {
messageBuilder.withExpiration(0);
}
}
private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, SignalServiceAddress source, SignalServiceAddress destination, boolean ignoreAttachments) {
- String threadId;
if (message.getGroupInfo().isPresent()) {
SignalServiceGroup groupInfo = message.getGroupInfo().get();
- threadId = Base64.encodeBytes(groupInfo.getGroupId());
GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
switch (groupInfo.getType()) {
case UPDATE:
e.printStackTrace();
}
} else {
- group.members.remove(source.getNumber().get());
+ group.removeMember(source);
account.getGroupStore().updateGroup(group);
}
break;
}
break;
}
- } else {
- if (isSync) {
- threadId = destination.getNumber().get();
- } else {
- threadId = source.getNumber().get();
- }
}
if (message.isEndSession()) {
handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get());
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
- ThreadInfo thread = account.getThreadStore().getThread(threadId);
- if (thread == null) {
- thread = new ThreadInfo();
- thread.id = threadId;
- }
- if (thread.messageExpirationTime != message.getExpiresInSeconds()) {
- thread.messageExpirationTime = message.getExpiresInSeconds();
- account.getThreadStore().updateThread(thread);
+ if (message.getGroupInfo().isPresent()) {
+ SignalServiceGroup groupInfo = message.getGroupInfo().get();
+ GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
+ if (group == null) {
+ group = new GroupInfo(groupInfo.getGroupId());
+ }
+ if (group.messageExpirationTime != message.getExpiresInSeconds()) {
+ group.messageExpirationTime = message.getExpiresInSeconds();
+ account.getGroupStore().updateGroup(group);
+ }
+ } else {
+ ContactInfo contact = account.getContactStore().getContact(isSync ? destination : source);
+ if (contact == null) {
+ contact = new ContactInfo(isSync ? destination : source);
+ }
+ if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
+ contact.messageExpirationTime = message.getExpiresInSeconds();
+ account.getContactStore().updateContact(contact);
+ }
}
}
if (message.getAttachments().isPresent() && !ignoreAttachments) {
}
if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
+
+ if (content.isNeedsReceipt()) {
+ try {
+ sendReceipt(sender, message.getTimestamp());
+ } catch (IOException | UntrustedIdentityException e) {
+ e.printStackTrace();
+ }
+ }
+
handleSignalServiceDataMessage(message, false, sender, account.getSelfAddress(), ignoreAttachments);
}
if (content.getSyncMessage().isPresent()) {
}
syncGroup.addMembers(g.getMembers());
if (!g.isActive()) {
- syncGroup.members.remove(account.getUsername());
+ syncGroup.removeMember(account.getSelfAddress());
} else {
// Add ourself to the member set as it's marked as active
- syncGroup.members.add(account.getUsername());
+ syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
}
syncGroup.blocked = g.isBlocked();
if (g.getColor().isPresent()) {
account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (c.getExpirationTimer().isPresent()) {
- ThreadInfo thread = account.getThreadStore().getThread(c.getAddress().getNumber().get());
- if (thread == null) {
- thread = new ThreadInfo();
- thread.id = c.getAddress().getNumber().get();
- }
- thread.messageExpirationTime = c.getExpirationTimer().get();
- account.getThreadStore().updateThread(thread);
+ contact.messageExpirationTime = c.getExpirationTimer().get();
}
contact.blocked = c.isBlocked();
contact.inboxPosition = c.getInboxPosition().orNull();
try (OutputStream fos = new FileOutputStream(groupsFile)) {
DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos);
for (GroupInfo record : account.getGroupStore().getGroups()) {
- ThreadInfo info = account.getThreadStore().getThread(Base64.encodeBytes(record.groupId));
out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
- record.members.contains(account.getUsername()), Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+ record.isMember(account.getSelfAddress()), Optional.of(record.messageExpirationTime),
Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived));
}
}
DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos);
for (ContactInfo record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null;
- ThreadInfo info = account.getThreadStore().getThread(record.number);
if (getIdentities().containsKey(record.number)) {
JsonIdentityKeyStore.Identity currentIdentity = null;
for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) {
out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name),
createContactAvatarAttachment(record.number), Optional.fromNullable(record.color),
Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked,
- Optional.fromNullable(info != null ? info.messageExpirationTime : null),
+ Optional.of(record.messageExpirationTime),
Optional.fromNullable(record.inboxPosition), record.archived));
}