mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 02:20:39 +00:00
Implement accepting and declining group invitations
This commit is contained in:
parent
c49b05cd75
commit
17608ce522
6 changed files with 139 additions and 68 deletions
|
@ -1,6 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Accept group invitation with `updateGroup -g GROUP_ID`
|
||||||
|
- Decline group invitation with `quitGroup -g GROUP_ID`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Include group ids for v2 groups in json output
|
- Include group ids for v2 groups in json output
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,7 @@ Output received messages in json format, one object per line.
|
||||||
=== updateGroup
|
=== updateGroup
|
||||||
|
|
||||||
Create or update a group.
|
Create or update a group.
|
||||||
|
If the user is a pending member, this command will accept the group invitation.
|
||||||
|
|
||||||
*-g* GROUP, *--group* GROUP::
|
*-g* GROUP, *--group* GROUP::
|
||||||
Specify the recipient group ID in base64 encoding.
|
Specify the recipient group ID in base64 encoding.
|
||||||
|
@ -198,6 +199,7 @@ Specify one or more members to add to the group.
|
||||||
=== quitGroup
|
=== quitGroup
|
||||||
|
|
||||||
Send a quit group message to all group members and remove self from member list.
|
Send a quit group message to all group members and remove self from member list.
|
||||||
|
If the user is a pending member, this command will decline the group invitation.
|
||||||
|
|
||||||
*-g* GROUP, *--group* GROUP::
|
*-g* GROUP, *--group* GROUP::
|
||||||
Specify the recipient group ID in base64 encoding.
|
Specify the recipient group ID in base64 encoding.
|
||||||
|
@ -235,7 +237,7 @@ Specify the safety number of the key, only use this option if you have verified
|
||||||
|
|
||||||
Update the name and avatar image visible by message recipients for the current users.
|
Update the name and avatar image visible by message recipients for the current users.
|
||||||
The profile is stored encrypted on the Signal servers.
|
The profile is stored encrypted on the Signal servers.
|
||||||
The decryption key is sent with every outgoing messages (excluding group messages).
|
The decryption key is sent with every outgoing messages to contacts.
|
||||||
|
|
||||||
*--name*::
|
*--name*::
|
||||||
New name visible by message recipients.
|
New name visible by message recipients.
|
||||||
|
|
|
@ -322,6 +322,8 @@ public class Manager implements Closeable {
|
||||||
contact.profileKey = null;
|
contact.profileKey = null;
|
||||||
account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
|
account.getProfileStore().storeProfileKey(contact.getAddress(), profileKey);
|
||||||
}
|
}
|
||||||
|
// Ensure our profile key is stored in profile store
|
||||||
|
account.getProfileStore().storeProfileKey(getSelfAddress(), account.getProfileKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkAccountState() throws IOException {
|
public void checkAccountState() throws IOException {
|
||||||
|
@ -705,6 +707,17 @@ public class Manager implements Closeable {
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private GroupInfo getGroupForUpdating(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException {
|
||||||
|
GroupInfo g = account.getGroupStore().getGroup(groupId);
|
||||||
|
if (g == null) {
|
||||||
|
throw new GroupNotFoundException(groupId);
|
||||||
|
}
|
||||||
|
if (!g.isMember(account.getSelfAddress()) && !g.isPendingMember(account.getSelfAddress())) {
|
||||||
|
throw new NotAGroupMemberException(groupId, g.getTitle());
|
||||||
|
}
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
public List<GroupInfo> getGroups() {
|
public List<GroupInfo> getGroups() {
|
||||||
return account.getGroupStore().getGroups();
|
return account.getGroupStore().getGroups();
|
||||||
}
|
}
|
||||||
|
@ -749,7 +762,7 @@ public class Manager implements Closeable {
|
||||||
|
|
||||||
SignalServiceDataMessage.Builder messageBuilder;
|
SignalServiceDataMessage.Builder messageBuilder;
|
||||||
|
|
||||||
final GroupInfo g = getGroupForSending(groupId);
|
final GroupInfo g = getGroupForUpdating(groupId);
|
||||||
if (g instanceof GroupInfoV1) {
|
if (g instanceof GroupInfoV1) {
|
||||||
GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
|
GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
|
||||||
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
|
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
|
||||||
|
@ -788,31 +801,39 @@ public class Manager implements Closeable {
|
||||||
g = gv2;
|
g = gv2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GroupInfo group = getGroupForSending(groupId);
|
GroupInfo group = getGroupForUpdating(groupId);
|
||||||
if (group instanceof GroupInfoV2) {
|
if (group instanceof GroupInfoV2) {
|
||||||
Pair<DecryptedGroup, GroupChange> groupGroupChangePair = null;
|
final GroupInfoV2 groupInfoV2 = (GroupInfoV2) group;
|
||||||
|
|
||||||
|
Pair<Long, List<SendMessageResult>> result = null;
|
||||||
|
if (groupInfoV2.isPendingMember(getSelfAddress())) {
|
||||||
|
Pair<DecryptedGroup, GroupChange> groupGroupChangePair = groupHelper.acceptInvite(groupInfoV2);
|
||||||
|
result = sendUpdateGroupMessage(groupInfoV2,
|
||||||
|
groupGroupChangePair.first(),
|
||||||
|
groupGroupChangePair.second());
|
||||||
|
}
|
||||||
|
|
||||||
if (members != null) {
|
if (members != null) {
|
||||||
final Set<SignalServiceAddress> newMembers = new HashSet<>(members);
|
final Set<SignalServiceAddress> newMembers = new HashSet<>(members);
|
||||||
newMembers.removeAll(group.getMembers());
|
newMembers.removeAll(group.getMembers());
|
||||||
if (newMembers.size() > 0) {
|
if (newMembers.size() > 0) {
|
||||||
groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, newMembers);
|
Pair<DecryptedGroup, GroupChange> groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2,
|
||||||
|
newMembers);
|
||||||
|
result = sendUpdateGroupMessage(groupInfoV2,
|
||||||
|
groupGroupChangePair.first(),
|
||||||
|
groupGroupChangePair.second());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (groupGroupChangePair == null || name != null || avatarFile != null) {
|
if (result == null || name != null || avatarFile != null) {
|
||||||
if (groupGroupChangePair != null) {
|
Pair<DecryptedGroup, GroupChange> groupGroupChangePair = groupHelper.updateGroupV2(groupInfoV2,
|
||||||
((GroupInfoV2) group).setGroup(groupGroupChangePair.first());
|
name,
|
||||||
messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group,
|
avatarFile);
|
||||||
groupGroupChangePair.second().toByteArray());
|
result = sendUpdateGroupMessage(groupInfoV2,
|
||||||
sendMessage(messageBuilder, group.getMembersWithout(account.getSelfAddress()));
|
groupGroupChangePair.first(),
|
||||||
}
|
groupGroupChangePair.second());
|
||||||
|
|
||||||
groupGroupChangePair = groupHelper.updateGroupV2((GroupInfoV2) group, name, avatarFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
((GroupInfoV2) group).setGroup(groupGroupChangePair.first());
|
return new Pair<>(group.groupId, result.second());
|
||||||
messageBuilder = getGroupUpdateMessageBuilder((GroupInfoV2) group,
|
|
||||||
groupGroupChangePair.second().toByteArray());
|
|
||||||
g = group;
|
|
||||||
} else {
|
} else {
|
||||||
GroupInfoV1 gv1 = (GroupInfoV1) group;
|
GroupInfoV1 gv1 = (GroupInfoV1) group;
|
||||||
updateGroupV1(gv1, name, members, avatarFile);
|
updateGroupV1(gv1, name, members, avatarFile);
|
||||||
|
@ -824,10 +845,20 @@ public class Manager implements Closeable {
|
||||||
account.getGroupStore().updateGroup(g);
|
account.getGroupStore().updateGroup(g);
|
||||||
|
|
||||||
final Pair<Long, List<SendMessageResult>> result = sendMessage(messageBuilder,
|
final Pair<Long, List<SendMessageResult>> result = sendMessage(messageBuilder,
|
||||||
g.getMembersWithout(account.getSelfAddress()));
|
g.getMembersIncludingPendingWithout(account.getSelfAddress()));
|
||||||
return new Pair<>(g.groupId, result.second());
|
return new Pair<>(g.groupId, result.second());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
|
||||||
|
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
|
||||||
|
) throws IOException {
|
||||||
|
group.setGroup(newDecryptedGroup);
|
||||||
|
final SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(group,
|
||||||
|
groupChange.toByteArray());
|
||||||
|
account.getGroupStore().updateGroup(group);
|
||||||
|
return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
private void updateGroupV1(
|
private void updateGroupV1(
|
||||||
final GroupInfoV1 g,
|
final GroupInfoV1 g,
|
||||||
final String name,
|
final String name,
|
||||||
|
@ -1582,6 +1613,9 @@ public class Manager implements Closeable {
|
||||||
group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
|
group = groupHelper.getUpdatedDecryptedGroup(groupInfoV2.getGroup(),
|
||||||
groupContext.getSignedGroupChange(),
|
groupContext.getSignedGroupChange(),
|
||||||
groupMasterKey);
|
groupMasterKey);
|
||||||
|
if (group != null) {
|
||||||
|
storeProfileKeysFromMembers(group);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
group = getDecryptedGroup(groupSecretParams);
|
group = getDecryptedGroup(groupSecretParams);
|
||||||
|
@ -1678,15 +1712,7 @@ public class Manager implements Closeable {
|
||||||
try {
|
try {
|
||||||
final GroupsV2AuthorizationString groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
|
final GroupsV2AuthorizationString groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams);
|
||||||
DecryptedGroup group = groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString);
|
DecryptedGroup group = groupsV2Api.getGroup(groupSecretParams, groupsV2AuthorizationString);
|
||||||
for (DecryptedMember member : group.getMembersList()) {
|
storeProfileKeysFromMembers(group);
|
||||||
final SignalServiceAddress address = resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil.parseOrThrow(
|
|
||||||
member.getUuid().toByteArray()), null));
|
|
||||||
try {
|
|
||||||
account.getProfileStore()
|
|
||||||
.storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray()));
|
|
||||||
} catch (InvalidInputException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return group;
|
return group;
|
||||||
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
|
} catch (IOException | VerificationFailedException | InvalidGroupStateException e) {
|
||||||
System.err.println("Failed to retrieve Group V2 info, ignoring ...");
|
System.err.println("Failed to retrieve Group V2 info, ignoring ...");
|
||||||
|
@ -1694,6 +1720,18 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void storeProfileKeysFromMembers(final DecryptedGroup group) {
|
||||||
|
for (DecryptedMember member : group.getMembersList()) {
|
||||||
|
final SignalServiceAddress address = resolveSignalServiceAddress(new SignalServiceAddress(UuidUtil.parseOrThrow(
|
||||||
|
member.getUuid().toByteArray()), null));
|
||||||
|
try {
|
||||||
|
account.getProfileStore()
|
||||||
|
.storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray()));
|
||||||
|
} catch (InvalidInputException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void retryFailedReceivedMessages(
|
private void retryFailedReceivedMessages(
|
||||||
ReceiveMessageHandler handler, boolean ignoreAttachments
|
ReceiveMessageHandler handler, boolean ignoreAttachments
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -118,24 +118,7 @@ public class GroupHelper {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int noUuidCapability = members.stream()
|
if (!areMembersValid(members)) return null;
|
||||||
.filter(address -> !address.getUuid().isPresent())
|
|
||||||
.collect(Collectors.toUnmodifiableSet())
|
|
||||||
.size();
|
|
||||||
if (noUuidCapability > 0) {
|
|
||||||
System.err.println("Cannot create a V2 group as " + noUuidCapability + " members don't have a UUID.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int noGv2Capability = members.stream()
|
|
||||||
.map(profileProvider::getProfile)
|
|
||||||
.filter(profile -> !profile.getCapabilities().gv2)
|
|
||||||
.collect(Collectors.toUnmodifiableSet())
|
|
||||||
.size();
|
|
||||||
if (noGv2Capability > 0) {
|
|
||||||
System.err.println("Cannot create a V2 group as " + noGv2Capability + " members don't support Groups V2.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupCandidate self = new GroupCandidate(selfAddressProvider.getSelfAddress().getUuid().orNull(),
|
GroupCandidate self = new GroupCandidate(selfAddressProvider.getSelfAddress().getUuid().orNull(),
|
||||||
Optional.fromNullable(profileKeyCredential));
|
Optional.fromNullable(profileKeyCredential));
|
||||||
|
@ -154,6 +137,29 @@ public class GroupHelper {
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean areMembersValid(final Collection<SignalServiceAddress> members) {
|
||||||
|
final int noUuidCapability = members.stream()
|
||||||
|
.filter(address -> !address.getUuid().isPresent())
|
||||||
|
.collect(Collectors.toUnmodifiableSet())
|
||||||
|
.size();
|
||||||
|
if (noUuidCapability > 0) {
|
||||||
|
System.err.println("Cannot create a V2 group as " + noUuidCapability + " members don't have a UUID.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int noGv2Capability = members.stream()
|
||||||
|
.map(profileProvider::getProfile)
|
||||||
|
.filter(profile -> profile != null && !profile.getCapabilities().gv2)
|
||||||
|
.collect(Collectors.toUnmodifiableSet())
|
||||||
|
.size();
|
||||||
|
if (noGv2Capability > 0) {
|
||||||
|
System.err.println("Cannot create a V2 group as " + noGv2Capability + " members don't support Groups V2.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public Pair<DecryptedGroup, GroupChange> updateGroupV2(
|
public Pair<DecryptedGroup, GroupChange> updateGroupV2(
|
||||||
GroupInfoV2 groupInfoV2, String name, String avatarFile
|
GroupInfoV2 groupInfoV2, String name, String avatarFile
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
@ -186,6 +192,8 @@ public class GroupHelper {
|
||||||
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
||||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
||||||
|
|
||||||
|
if (!areMembersValid(newMembers)) return null;
|
||||||
|
|
||||||
Set<GroupCandidate> candidates = newMembers.stream()
|
Set<GroupCandidate> candidates = newMembers.stream()
|
||||||
.map(member -> new GroupCandidate(member.getUuid().get(),
|
.map(member -> new GroupCandidate(member.getUuid().get(),
|
||||||
Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
|
Optional.fromNullable(profileKeyCredentialProvider.getProfileKeyCredential(member))))
|
||||||
|
@ -215,6 +223,27 @@ public class GroupHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
|
||||||
|
final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
||||||
|
final GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
||||||
|
|
||||||
|
final SignalServiceAddress selfAddress = this.selfAddressProvider.getSelfAddress();
|
||||||
|
final ProfileKeyCredential profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(
|
||||||
|
selfAddress);
|
||||||
|
if (profileKeyCredential == null) {
|
||||||
|
throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
final GroupChange.Actions.Builder change = groupOperations.createAcceptInviteChange(profileKeyCredential);
|
||||||
|
|
||||||
|
final Optional<UUID> uuid = selfAddress.getUuid();
|
||||||
|
if (uuid.isPresent()) {
|
||||||
|
change.setSourceUuid(UuidUtil.toByteString(uuid.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitChange(groupInfoV2, change);
|
||||||
|
}
|
||||||
|
|
||||||
public Pair<DecryptedGroup, GroupChange> revokeInvites(
|
public Pair<DecryptedGroup, GroupChange> revokeInvites(
|
||||||
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
|
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
|
@ -4,8 +4,6 @@ import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
|
||||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
|
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
|
||||||
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||||
|
@ -15,7 +13,6 @@ import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||||
import org.whispersystems.signalservice.internal.util.concurrent.CascadingFuture;
|
import org.whispersystems.signalservice.internal.util.concurrent.CascadingFuture;
|
||||||
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
|
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
|
||||||
import org.whispersystems.util.Base64;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -87,17 +84,6 @@ public final class ProfileHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String decryptName(
|
|
||||||
ProfileKey profileKey, String encryptedName
|
|
||||||
) throws InvalidCiphertextException, IOException {
|
|
||||||
if (encryptedName == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileCipher profileCipher = new ProfileCipher(profileKey);
|
|
||||||
return new String(profileCipher.decryptName(Base64.decode(encryptedName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ListenableFuture<ProfileAndCredential> getPipeRetrievalFuture(
|
private ListenableFuture<ProfileAndCredential> getPipeRetrievalFuture(
|
||||||
SignalServiceAddress address,
|
SignalServiceAddress address,
|
||||||
Optional<ProfileKey> profileKey,
|
Optional<ProfileKey> profileKey,
|
||||||
|
|
|
@ -5,8 +5,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class GroupInfo {
|
public abstract class GroupInfo {
|
||||||
|
|
||||||
|
@ -44,13 +45,14 @@ public abstract class GroupInfo {
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public Set<SignalServiceAddress> getMembersWithout(SignalServiceAddress address) {
|
public Set<SignalServiceAddress> getMembersWithout(SignalServiceAddress address) {
|
||||||
Set<SignalServiceAddress> members = new HashSet<>();
|
return getMembers().stream().filter(member -> !member.matches(address)).collect(Collectors.toSet());
|
||||||
for (SignalServiceAddress member : getMembers()) {
|
}
|
||||||
if (!member.matches(address)) {
|
|
||||||
members.add(member);
|
@JsonIgnore
|
||||||
}
|
public Set<SignalServiceAddress> getMembersIncludingPendingWithout(SignalServiceAddress address) {
|
||||||
}
|
return Stream.concat(getMembers().stream(), getPendingMembers().stream())
|
||||||
return members;
|
.filter(member -> !member.matches(address))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@ -62,4 +64,14 @@ public abstract class GroupInfo {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isPendingMember(SignalServiceAddress address) {
|
||||||
|
for (SignalServiceAddress member : getPendingMembers()) {
|
||||||
|
if (member.matches(address)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue