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.GroupUtils;
+import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.GroupV2Helper;
import org.asamk.signal.manager.helper.PinHelper;
}
public void checkAccountState() throws IOException {
+ if (account.getLastReceiveTimestamp() == 0) {
+ logger.warn("The Signal protocol expects that incoming messages are regularly received.");
+ } else {
+ var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
+ long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
+ if (days > 7) {
+ logger.warn(
+ "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
+ days);
+ }
+ }
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
refreshPreKeys();
}
) {
var profile = account.getProfileStore().getProfile(recipientId);
- var now = new Date().getTime();
+ var now = System.currentTimeMillis();
// Profiles are cached for 24h before retrieving them again, unless forced
if (!force && profile != null && now - profile.getLastUpdateTimestamp() < 24 * 60 * 60 * 1000) {
return profile;
var profileKey = account.getProfileStore().getProfileKey(recipientId);
if (profileKey == null) {
- profile = new Profile(new Date().getTime(),
+ profile = new Profile(System.currentTimeMillis(),
null,
null,
null,
}
} catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}",
- resolveSignalServiceAddress(recipientId).getLegacyIdentifier());
+ resolveSignalServiceAddress(recipientId).getIdentifier());
}
return profileAndCredential;
}
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
}
- public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+ public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
+ GroupId groupId, Set<String> groupAdmins
+ ) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
SignalServiceDataMessage.Builder messageBuilder;
final var g = getGroupForUpdating(groupId);
account.getGroupStore().updateGroup(groupInfoV1);
} else {
final var groupInfoV2 = (GroupInfoV2) g;
- final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2);
+ final var currentAdmins = g.getAdminMembers();
+ final var newAdmins = getSignalServiceAddresses(groupAdmins);
+ newAdmins.removeAll(currentAdmins);
+ newAdmins.retainAll(g.getMembers());
+ if (currentAdmins.contains(getSelfRecipientId())
+ && currentAdmins.size() == 1
+ && g.getMembers().size() > 1
+ && newAdmins.size() == 0) {
+ // Last admin can't leave the group, unless she's also the last member
+ throw new LastGroupAdminException(g.getGroupId(), g.getTitle());
+ }
+ final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
account.getGroupStore().updateGroup(groupInfoV2);
List<String> removeAdmins,
boolean resetGroupLink,
GroupLinkState groupLinkState,
+ GroupPermission addMemberPermission,
+ GroupPermission editDetailsPermission,
File avatarFile,
Integer expirationTimer
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
resetGroupLink,
groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
avatarFile,
expirationTimer);
}
final Set<RecipientId> removeAdmins,
final boolean resetGroupLink,
final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
final File avatarFile,
- Integer expirationTimer
+ final Integer expirationTimer
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
var group = getGroupForUpdating(groupId);
removeAdmins,
resetGroupLink,
groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
avatarFile,
expirationTimer);
}
final Set<RecipientId> removeAdmins,
final boolean resetGroupLink,
final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
final File avatarFile,
Integer expirationTimer
) throws IOException {
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
+ if (addMemberPermission != null) {
+ var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (editDetailsPermission != null) {
+ var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
if (expirationTimer != null) {
var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
- if (result == null || name != null || description != null || avatarFile != null) {
+ if (name != null || description != null || avatarFile != null) {
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
if (avatarFile != null) {
avatarStore.storeGroupAvatar(group.getGroupId(),
SignalServiceContent content = null;
Exception exception = null;
final CachedMessage[] cachedMessage = {null};
+ account.setLastReceiveTimestamp(System.currentTimeMillis());
try {
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
final var recipientId = envelope1.hasSource()