import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdV1;
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.GroupHelper;
+import org.asamk.signal.manager.helper.GroupV2Helper;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
- private final GroupHelper groupHelper;
+ private final GroupV2Helper groupV2Helper;
private final PinHelper pinHelper;
private final AvatarStore avatarStore;
private final AttachmentStore attachmentStore;
unidentified -> unidentified ? getOrCreateUnidentifiedMessagePipe() : getOrCreateMessagePipe(),
() -> messageReceiver,
this::resolveSignalServiceAddress);
- this.groupHelper = new GroupHelper(this::getRecipientProfileKeyCredential,
+ this.groupV2Helper = new GroupV2Helper(this::getRecipientProfileKeyCredential,
this::getRecipientProfile,
account::getSelfRecipientId,
groupsV2Operations,
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 = groupHelper.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);
members.remove(selfRecipientId);
}
- var gv2Pair = groupHelper.createGroupV2(name == null ? "" : name,
+ var gv2Pair = groupV2Helper.createGroup(name == null ? "" : name,
members == null ? Set.of() : members,
avatarFile);
}
public Pair<Long, List<SendMessageResult>> updateGroup(
- GroupId groupId, String name, String description, List<String> members, File avatarFile
+ GroupId groupId,
+ String name,
+ String description,
+ List<String> members,
+ List<String> removeMembers,
+ List<String> admins,
+ List<String> removeAdmins,
+ boolean resetGroupLink,
+ GroupLinkState groupLinkState,
+ GroupPermission addMemberPermission,
+ GroupPermission editDetailsPermission,
+ File avatarFile,
+ Integer expirationTimer
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
return updateGroup(groupId,
name,
description,
members == null ? null : getSignalServiceAddresses(members),
- avatarFile);
+ removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
+ admins == null ? null : getSignalServiceAddresses(admins),
+ removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
+ resetGroupLink,
+ groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
+ avatarFile,
+ expirationTimer);
}
private Pair<Long, List<SendMessageResult>> updateGroup(
- GroupId groupId, String name, String description, Set<RecipientId> members, File avatarFile
+ final GroupId groupId,
+ final String name,
+ final String description,
+ final Set<RecipientId> members,
+ final Set<RecipientId> removeMembers,
+ final Set<RecipientId> admins,
+ final Set<RecipientId> removeAdmins,
+ final boolean resetGroupLink,
+ final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
+ final File avatarFile,
+ final Integer expirationTimer
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
var group = getGroupForUpdating(groupId);
if (group instanceof GroupInfoV2) {
- return updateGroupV2((GroupInfoV2) group, name, description, members, avatarFile);
+ return updateGroupV2((GroupInfoV2) group,
+ name,
+ description,
+ members,
+ removeMembers,
+ admins,
+ removeAdmins,
+ resetGroupLink,
+ groupLinkState,
+ addMemberPermission,
+ editDetailsPermission,
+ avatarFile,
+ expirationTimer);
+ }
+
+ final var gv1 = (GroupInfoV1) group;
+ final var result = updateGroupV1(gv1, name, members, avatarFile);
+ if (expirationTimer != null) {
+ setExpirationTimer(gv1, expirationTimer);
}
-
- return updateGroupV1((GroupInfoV1) group, name, members, avatarFile);
+ return result;
}
private Pair<Long, List<SendMessageResult>> updateGroupV1(
final String name,
final String description,
final Set<RecipientId> members,
- final File avatarFile
+ final Set<RecipientId> removeMembers,
+ final Set<RecipientId> admins,
+ final Set<RecipientId> removeAdmins,
+ final boolean resetGroupLink,
+ final GroupLinkState groupLinkState,
+ final GroupPermission addMemberPermission,
+ final GroupPermission editDetailsPermission,
+ final File avatarFile,
+ Integer expirationTimer
) throws IOException {
Pair<Long, List<SendMessageResult>> result = null;
if (group.isPendingMember(account.getSelfRecipientId())) {
- var groupGroupChangePair = groupHelper.acceptInvite(group);
+ var groupGroupChangePair = groupV2Helper.acceptInvite(group);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
final var newMembers = new HashSet<>(members);
newMembers.removeAll(group.getMembers());
if (newMembers.size() > 0) {
- var groupGroupChangePair = groupHelper.updateGroupV2(group, newMembers);
+ var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers);
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
}
- if (result == null || name != null || description != null || avatarFile != null) {
- var groupGroupChangePair = groupHelper.updateGroupV2(group, name, description, avatarFile);
+
+ if (removeMembers != null) {
+ var existingRemoveMembers = new HashSet<>(removeMembers);
+ existingRemoveMembers.retainAll(group.getMembers());
+ existingRemoveMembers.remove(getSelfRecipientId());// self can be removed with sendQuitGroupMessage
+ if (existingRemoveMembers.size() > 0) {
+ var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ var pendingRemoveMembers = new HashSet<>(removeMembers);
+ pendingRemoveMembers.retainAll(group.getPendingMembers());
+ if (pendingRemoveMembers.size() > 0) {
+ var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+ }
+
+ if (admins != null) {
+ final var newAdmins = new HashSet<>(admins);
+ newAdmins.retainAll(group.getMembers());
+ newAdmins.removeAll(group.getAdminMembers());
+ if (newAdmins.size() > 0) {
+ for (var admin : newAdmins) {
+ var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ groupGroupChangePair.second());
+ }
+ }
+ }
+
+ if (removeAdmins != null) {
+ final var existingRemoveAdmins = new HashSet<>(removeAdmins);
+ existingRemoveAdmins.retainAll(group.getAdminMembers());
+ if (existingRemoveAdmins.size() > 0) {
+ for (var admin : existingRemoveAdmins) {
+ var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
+ result = sendUpdateGroupV2Message(group,
+ groupGroupChangePair.first(),
+ groupGroupChangePair.second());
+ }
+ }
+ }
+
+ if (resetGroupLink) {
+ var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group);
+ result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
+ }
+
+ if (groupLinkState != null) {
+ var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState);
+ 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 (name != null || description != null || avatarFile != null) {
+ var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
if (avatarFile != null) {
avatarStore.storeGroupAvatar(group.getGroupId(),
outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
public Pair<GroupId, List<SendMessageResult>> joinGroup(
GroupInviteLinkUrl inviteLinkUrl
) throws IOException, GroupLinkNotActiveException {
- final var groupJoinInfo = groupHelper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
+ final var groupJoinInfo = groupV2Helper.getDecryptedGroupJoinInfo(inviteLinkUrl.getGroupMasterKey(),
inviteLinkUrl.getPassword());
- final var groupChange = groupHelper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
+ final var groupChange = groupV2Helper.joinGroup(inviteLinkUrl.getGroupMasterKey(),
inviteLinkUrl.getPassword(),
groupJoinInfo);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
private Pair<Long, List<SendMessageResult>> sendUpdateGroupV2Message(
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
) throws IOException {
+ final var selfRecipientId = account.getSelfRecipientId();
+ final var members = group.getMembersIncludingPendingWithout(selfRecipientId);
group.setGroup(newDecryptedGroup, this::resolveRecipient);
+ members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
+
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
account.getGroupStore().updateGroup(group);
- return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
+ return sendMessage(messageBuilder, members);
}
private static int currentTimeDays() {
/**
* Change the expiration timer for a group
*/
- public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
- var g = getGroup(groupId);
- if (g instanceof GroupInfoV1) {
- var groupInfoV1 = (GroupInfoV1) g;
- groupInfoV1.messageExpirationTime = messageExpirationTimer;
- account.getGroupStore().updateGroup(groupInfoV1);
- } else {
- throw new RuntimeException("TODO Not implemented!");
- }
+ private void setExpirationTimer(
+ GroupInfoV1 groupInfoV1, int messageExpirationTimer
+ ) throws NotAGroupMemberException, GroupNotFoundException, IOException {
+ groupInfoV1.messageExpirationTime = messageExpirationTimer;
+ account.getGroupStore().updateGroup(groupInfoV1);
+ sendExpirationTimerUpdate(groupInfoV1.getGroupId());
+ }
+
+ private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
+ final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
+ sendGroupMessage(messageBuilder, groupId);
}
/**
if (signedGroupChange != null
&& groupInfoV2.getGroup() != null
&& groupInfoV2.getGroup().getRevision() + 1 == revision) {
- group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(), signedGroupChange, groupMasterKey);
+ group = groupV2Helper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
+ signedGroupChange,
+ groupMasterKey);
}
if (group == null) {
- group = groupHelper.getDecryptedGroup(groupSecretParams);
+ group = groupV2Helper.getDecryptedGroup(groupSecretParams);
}
if (group != null) {
storeProfileKeysFromMembers(group);
final var group = account.getGroupStore().getGroup(groupId);
if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(((GroupInfoV2) group).getMasterKey());
- ((GroupInfoV2) group).setGroup(groupHelper.getDecryptedGroup(groupSecretParams), this::resolveRecipient);
+ ((GroupInfoV2) group).setGroup(groupV2Helper.getDecryptedGroup(groupSecretParams), this::resolveRecipient);
account.getGroupStore().updateGroup(group);
}
return group;