mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
parent
d1e760f447
commit
87406e2cdb
6 changed files with 82 additions and 32 deletions
|
@ -6,6 +6,7 @@
|
||||||
### Added
|
### Added
|
||||||
- Added new parameters to `updateGroup` for group v2 features:
|
- Added new parameters to `updateGroup` for group v2 features:
|
||||||
`--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
|
`--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration`
|
||||||
|
- Added new `--delete` parameter to `quitGroup`, to delete the local group data
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Prevent last admin of a group from leaving the group
|
- Prevent last admin of a group from leaving the group
|
||||||
|
|
|
@ -49,6 +49,10 @@ public class AvatarStore {
|
||||||
deleteAvatar(getProfileAvatarFile(address));
|
deleteAvatar(getProfileAvatarFile(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteGroupAvatar(GroupId groupId) throws IOException {
|
||||||
|
deleteAvatar(getGroupAvatarFile(groupId));
|
||||||
|
}
|
||||||
|
|
||||||
private StreamDetails retrieveAvatar(final File avatarFile) throws IOException {
|
private StreamDetails retrieveAvatar(final File avatarFile) throws IOException {
|
||||||
if (!avatarFile.exists()) {
|
if (!avatarFile.exists()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -779,35 +779,55 @@ public class Manager implements Closeable {
|
||||||
public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
|
public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
|
||||||
GroupId groupId, Set<String> groupAdmins
|
GroupId groupId, Set<String> groupAdmins
|
||||||
) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
|
) throws GroupNotFoundException, IOException, NotAGroupMemberException, InvalidNumberException, LastGroupAdminException {
|
||||||
SignalServiceDataMessage.Builder messageBuilder;
|
var group = getGroupForUpdating(groupId);
|
||||||
|
if (group instanceof GroupInfoV1) {
|
||||||
|
return quitGroupV1((GroupInfoV1) group);
|
||||||
|
}
|
||||||
|
|
||||||
final var g = getGroupForUpdating(groupId);
|
final var newAdmins = getSignalServiceAddresses(groupAdmins);
|
||||||
if (g instanceof GroupInfoV1) {
|
try {
|
||||||
var groupInfoV1 = (GroupInfoV1) g;
|
return quitGroupV2((GroupInfoV2) group, newAdmins);
|
||||||
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT).withId(groupId.serialize()).build();
|
} catch (ConflictException e) {
|
||||||
messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
|
// Detected conflicting update, refreshing group and trying again
|
||||||
|
group = getGroup(groupId, true);
|
||||||
|
return quitGroupV2((GroupInfoV2) group, newAdmins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pair<Long, List<SendMessageResult>> quitGroupV1(final GroupInfoV1 groupInfoV1) throws IOException {
|
||||||
|
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
|
||||||
|
.withId(groupInfoV1.getGroupId().serialize())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group);
|
||||||
groupInfoV1.removeMember(account.getSelfRecipientId());
|
groupInfoV1.removeMember(account.getSelfRecipientId());
|
||||||
account.getGroupStore().updateGroup(groupInfoV1);
|
account.getGroupStore().updateGroup(groupInfoV1);
|
||||||
} else {
|
return sendMessage(messageBuilder, groupInfoV1.getMembersWithout(account.getSelfRecipientId()));
|
||||||
final var groupInfoV2 = (GroupInfoV2) g;
|
}
|
||||||
final var currentAdmins = g.getAdminMembers();
|
|
||||||
final var newAdmins = getSignalServiceAddresses(groupAdmins);
|
private Pair<Long, List<SendMessageResult>> quitGroupV2(
|
||||||
|
final GroupInfoV2 groupInfoV2, final Set<RecipientId> newAdmins
|
||||||
|
) throws LastGroupAdminException, IOException {
|
||||||
|
final var currentAdmins = groupInfoV2.getAdminMembers();
|
||||||
newAdmins.removeAll(currentAdmins);
|
newAdmins.removeAll(currentAdmins);
|
||||||
newAdmins.retainAll(g.getMembers());
|
newAdmins.retainAll(groupInfoV2.getMembers());
|
||||||
if (currentAdmins.contains(getSelfRecipientId())
|
if (currentAdmins.contains(getSelfRecipientId())
|
||||||
&& currentAdmins.size() == 1
|
&& currentAdmins.size() == 1
|
||||||
&& g.getMembers().size() > 1
|
&& groupInfoV2.getMembers().size() > 1
|
||||||
&& newAdmins.size() == 0) {
|
&& newAdmins.size() == 0) {
|
||||||
// Last admin can't leave the group, unless she's also the last member
|
// Last admin can't leave the group, unless she's also the last member
|
||||||
throw new LastGroupAdminException(g.getGroupId(), g.getTitle());
|
throw new LastGroupAdminException(groupInfoV2.getGroupId(), groupInfoV2.getTitle());
|
||||||
}
|
}
|
||||||
final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
|
final var groupGroupChangePair = groupV2Helper.leaveGroup(groupInfoV2, newAdmins);
|
||||||
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
|
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
|
||||||
messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
|
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
|
||||||
account.getGroupStore().updateGroup(groupInfoV2);
|
account.getGroupStore().updateGroup(groupInfoV2);
|
||||||
|
return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
|
public void deleteGroup(GroupId groupId) throws IOException {
|
||||||
|
account.getGroupStore().deleteGroup(groupId);
|
||||||
|
avatarStore.deleteGroupAvatar(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<GroupId, List<SendMessageResult>> createGroup(
|
public Pair<GroupId, List<SendMessageResult>> createGroup(
|
||||||
|
@ -2013,6 +2033,7 @@ public class Manager implements Closeable {
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
final CachedMessage[] cachedMessage = {null};
|
final CachedMessage[] cachedMessage = {null};
|
||||||
account.setLastReceiveTimestamp(System.currentTimeMillis());
|
account.setLastReceiveTimestamp(System.currentTimeMillis());
|
||||||
|
logger.debug("Checking for new message from server");
|
||||||
try {
|
try {
|
||||||
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
|
var result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
|
||||||
final var recipientId = envelope1.hasSource()
|
final var recipientId = envelope1.hasSource()
|
||||||
|
@ -2021,6 +2042,7 @@ public class Manager implements Closeable {
|
||||||
// store message on disk, before acknowledging receipt to the server
|
// store message on disk, before acknowledging receipt to the server
|
||||||
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
|
||||||
});
|
});
|
||||||
|
logger.debug("New message received from server");
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
envelope = result.get();
|
envelope = result.get();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -133,7 +133,11 @@ public class GroupStore {
|
||||||
saver.save(storage);
|
saver.save(storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteGroupV1(GroupIdV1 groupId) {
|
public void deleteGroupV1(GroupIdV1 groupIdV1) {
|
||||||
|
deleteGroup(groupIdV1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteGroup(GroupId groupId) {
|
||||||
final Storage storage;
|
final Storage storage;
|
||||||
synchronized (groups) {
|
synchronized (groups) {
|
||||||
groups.remove(groupId);
|
groups.remove(groupId);
|
||||||
|
|
|
@ -285,6 +285,9 @@ 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.
|
||||||
|
|
||||||
|
*--delete*::
|
||||||
|
Delete local group data completely after quitting group.
|
||||||
|
|
||||||
=== listGroups
|
=== listGroups
|
||||||
|
|
||||||
Show a list of known groups and related information.
|
Show a list of known groups and related information.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.asamk.signal.commands;
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import net.sourceforge.argparse4j.impl.Arguments;
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
|
|
||||||
|
@ -14,6 +15,8 @@ import org.asamk.signal.manager.groups.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.groups.LastGroupAdminException;
|
import org.asamk.signal.manager.groups.LastGroupAdminException;
|
||||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||||
import org.asamk.signal.util.Util;
|
import org.asamk.signal.util.Util;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -24,10 +27,15 @@ import static org.asamk.signal.util.ErrorUtils.handleTimestampAndSendMessageResu
|
||||||
|
|
||||||
public class QuitGroupCommand implements LocalCommand {
|
public class QuitGroupCommand implements LocalCommand {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(QuitGroupCommand.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachToSubparser(final Subparser subparser) {
|
public void attachToSubparser(final Subparser subparser) {
|
||||||
subparser.help("Send a quit group message to all group members and remove self from member list.");
|
subparser.help("Send a quit group message to all group members and remove self from member list.");
|
||||||
subparser.addArgument("-g", "--group").required(true).help("Specify the recipient group ID.");
|
subparser.addArgument("-g", "--group").required(true).help("Specify the recipient group ID.");
|
||||||
|
subparser.addArgument("--delete")
|
||||||
|
.action(Arguments.storeTrue())
|
||||||
|
.help("Delete local group data completely after quitting group.");
|
||||||
subparser.addArgument("--admin")
|
subparser.addArgument("--admin")
|
||||||
.nargs("*")
|
.nargs("*")
|
||||||
.help("Specify one or more members to make a group admin, required if you're currently the only admin.");
|
.help("Specify one or more members to make a group admin, required if you're currently the only admin.");
|
||||||
|
@ -46,13 +54,21 @@ public class QuitGroupCommand implements LocalCommand {
|
||||||
|
|
||||||
var groupAdmins = ns.<String>getList("admin");
|
var groupAdmins = ns.<String>getList("admin");
|
||||||
|
|
||||||
|
try {
|
||||||
try {
|
try {
|
||||||
final var results = m.sendQuitGroupMessage(groupId,
|
final var results = m.sendQuitGroupMessage(groupId,
|
||||||
groupAdmins == null ? Set.of() : new HashSet<>(groupAdmins));
|
groupAdmins == null ? Set.of() : new HashSet<>(groupAdmins));
|
||||||
handleTimestampAndSendMessageResults(writer, results.first(), results.second());
|
handleTimestampAndSendMessageResults(writer, results.first(), results.second());
|
||||||
|
} catch (NotAGroupMemberException e) {
|
||||||
|
logger.info("User is not a group member");
|
||||||
|
}
|
||||||
|
if (ns.getBoolean("delete")) {
|
||||||
|
logger.debug("Deleting group {}", groupId);
|
||||||
|
m.deleteGroup(groupId);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IOErrorException("Failed to send message: " + e.getMessage());
|
throw new IOErrorException("Failed to send message: " + e.getMessage());
|
||||||
} catch (GroupNotFoundException | NotAGroupMemberException e) {
|
} catch (GroupNotFoundException e) {
|
||||||
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
throw new UserErrorException("Failed to send to group: " + e.getMessage());
|
||||||
} catch (InvalidNumberException e) {
|
} catch (InvalidNumberException e) {
|
||||||
throw new UserErrorException("Failed to parse admin number: " + e.getMessage());
|
throw new UserErrorException("Failed to parse admin number: " + e.getMessage());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue