Add flags to update group member labels

This commit is contained in:
AsamK 2026-03-08 12:23:26 +01:00
parent 7014f629fe
commit 27a722dc75
12 changed files with 118 additions and 10 deletions

View File

@ -598,6 +598,12 @@ pub enum CliCommands {
#[arg(short = 'e', long)]
expiration: Option<u32>,
#[arg(long = "member-label-emoji")]
member_label_emoji: Option<String>,
#[arg(long = "member-label")]
member_label: Option<String>,
},
UpdateProfile {
#[arg(long = "given-name")]

View File

@ -460,6 +460,8 @@ pub trait Rpc {
#[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
#[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
expiration: Option<u32>,
#[allow(non_snake_case)] memberLabelEmoji: Option<String>,
#[allow(non_snake_case)] memberLabel: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[method(name = "updateProfile", param_kind = map)]

View File

@ -535,6 +535,8 @@ async fn handle_command(
set_permission_edit_details,
set_permission_send_messages,
expiration,
member_label_emoji,
member_label,
} => {
client
.update_group(
@ -568,6 +570,8 @@ async fn handle_command(
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
}),
expiration,
member_label_emoji,
member_label,
)
.await
}

View File

@ -19,6 +19,8 @@ public class UpdateGroup {
private final String avatarFile;
private final Integer expirationTimer;
private final Boolean isAnnouncementGroup;
private final String labelEmoji;
private final String labelString;
private UpdateGroup(final Builder builder) {
name = builder.name;
@ -36,6 +38,8 @@ public class UpdateGroup {
avatarFile = builder.avatarFile;
expirationTimer = builder.expirationTimer;
isAnnouncementGroup = builder.isAnnouncementGroup;
labelEmoji = builder.labelEmoji;
labelString = builder.labelString;
}
public static Builder newBuilder() {
@ -57,7 +61,9 @@ public class UpdateGroup {
copy.editDetailsPermission,
copy.avatarFile,
copy.expirationTimer,
copy.isAnnouncementGroup);
copy.isAnnouncementGroup,
copy.labelEmoji,
copy.labelString);
}
public static Builder newBuilder(
@ -75,7 +81,9 @@ public class UpdateGroup {
final GroupPermission editDetailsPermission,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
final Boolean isAnnouncementGroup,
final String labelEmoji,
final String labelString
) {
return new Builder(name,
description,
@ -91,7 +99,9 @@ public class UpdateGroup {
editDetailsPermission,
avatarFile,
expirationTimer,
isAnnouncementGroup);
isAnnouncementGroup,
labelEmoji,
labelString);
}
public String getName() {
@ -154,6 +164,14 @@ public class UpdateGroup {
return isAnnouncementGroup;
}
public String getLabelEmoji() {
return labelEmoji;
}
public String getLabelString() {
return labelString;
}
public static final class Builder {
private String name;
@ -171,6 +189,8 @@ public class UpdateGroup {
private String avatarFile;
private Integer expirationTimer;
private Boolean isAnnouncementGroup;
private String labelEmoji;
private String labelString;
private Builder() {
}
@ -190,7 +210,9 @@ public class UpdateGroup {
final GroupPermission editDetailsPermission,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
final Boolean isAnnouncementGroup,
final String labelEmoji,
final String labelString
) {
this.name = name;
this.description = description;
@ -207,6 +229,8 @@ public class UpdateGroup {
this.avatarFile = avatarFile;
this.expirationTimer = expirationTimer;
this.isAnnouncementGroup = isAnnouncementGroup;
this.labelEmoji = labelEmoji;
this.labelString = labelString;
}
public Builder withName(final String val) {
@ -284,6 +308,16 @@ public class UpdateGroup {
return this;
}
public Builder withLabelEmoji(final String val) {
labelEmoji = val;
return this;
}
public Builder withLabelString(final String val) {
labelString = val;
return this;
}
public UpdateGroup build() {
return new UpdateGroup(this);
}

View File

@ -299,7 +299,9 @@ public class GroupHelper {
final GroupPermission editDetailsPermission,
final String avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
final Boolean isAnnouncementGroup,
final String labelEmoji,
final String labelString
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
var group = getGroupForUpdating(groupId);
final var avatarBytes = readAvatarBytes(avatarFile);
@ -323,7 +325,9 @@ public class GroupHelper {
editDetailsPermission,
avatarBytes,
expirationTimer,
isAnnouncementGroup);
isAnnouncementGroup,
labelEmoji,
labelString);
} catch (ConflictException e) {
// Detected conflicting update, refreshing group and trying again
group = getGroup(groupId, true);
@ -342,7 +346,9 @@ public class GroupHelper {
editDetailsPermission,
avatarBytes,
expirationTimer,
isAnnouncementGroup);
isAnnouncementGroup,
labelEmoji,
labelString);
}
}
@ -701,7 +707,9 @@ public class GroupHelper {
final GroupPermission editDetailsPermission,
final byte[] avatarFile,
final Integer expirationTimer,
final Boolean isAnnouncementGroup
final Boolean isAnnouncementGroup,
final String labelEmoji,
final String labelString
) throws IOException {
SendGroupMessageResults result = null;
final var groupV2Helper = context.getGroupV2Helper();
@ -830,6 +838,15 @@ public class GroupHelper {
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
}
if (labelString != null || labelEmoji != null) {
final var selfRecipientId = account.getSelfRecipientId();
final var selfMember = group.getMember(selfRecipientId);
var groupGroupChangePair = groupV2Helper.setMemberLabels(group,
labelEmoji != null ? labelEmoji : selfMember.labelEmoji(),
labelString != null ? labelString : selfMember.labelString());
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) {

View File

@ -533,6 +533,18 @@ class GroupV2Helper {
return commitChange(groupInfoV2, change);
}
Pair<DecryptedGroup, GroupChangeResponse> setMemberLabels(
GroupInfoV2 groupInfoV2,
String labelEmoji,
String labelString
) throws IOException {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
final var change = groupOperations.createChangeMemberLabel(getSelfAci(),
labelString == null ? "" : labelString,
labelEmoji);
return commitChange(groupInfoV2, change);
}
private AccessControl.AccessRequired toAccessControl(final GroupLinkState state) {
return switch (state) {
case DISABLED -> AccessControl.AccessRequired.UNSATISFIABLE;

View File

@ -633,7 +633,9 @@ public class ManagerImpl implements Manager {
updateGroup.getEditDetailsPermission(),
updateGroup.getAvatarFile(),
updateGroup.getExpirationTimer(),
updateGroup.getIsAnnouncementGroup());
updateGroup.getIsAnnouncementGroup(),
updateGroup.getLabelEmoji(),
updateGroup.getLabelString());
}
@Override

View File

@ -31,6 +31,13 @@ public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 {
return getMembers().stream().map(GroupMemberInfo::getRecipientId).collect(Collectors.toSet());
}
public GroupMemberInfo getMember(RecipientId recipientId) {
return getMembers().stream()
.filter(member -> member.getRecipientId().equals(recipientId))
.findFirst()
.orElseThrow();
}
public Set<RecipientId> getBannedMembers() {
return Set.of();
}

View File

@ -154,7 +154,7 @@ RESPONSE: `{"jsonrpc":"2.0","result":{"timestamp":999},"id":4}`
---
REQUEST: `{"jsonrpc":"2.0","method":"updateGroup","params":{"groupId":"GROUP_ID=","name":"new group name","members":["+ZZZ"],"link":"enabledWithApproval","setPermissionEditDetails":"only-admins"},"id":"someId"}`
REQUEST: `{"jsonrpc":"2.0","method":"updateGroup","params":{"groupId":"GROUP_ID=","name":"new group name","members":["+ZZZ"],"link":"enabledWithApproval","setPermissionEditDetails":"only-admins","memberLabelEmoji":"😀","memberLabel":"My Label"},"id":"someId"}`
RESPONSE: `{"jsonrpc":"2.0","result":{"timestamp":9999},"id":"someId"}`

View File

@ -754,6 +754,12 @@ Groups where only admins can send messages are also called announcement groups
Set expiration time of messages (seconds).
To disable expiration set expiration time to 0.
*--member-label-emoji* EMOJI::
Specify the emoji for the member label.
*--member-label* STRING::
Specify the string for the member label.
=== quitGroup
Send a quit group message to all group members and remove self from member list.

View File

@ -75,6 +75,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
.choices("every-member", "only-admins");
subparser.addArgument("-e", "--expiration").type(int.class).help("Set expiration time of messages (seconds)");
subparser.addArgument("--member-label-emoji").help("Specify the emoji for the member label.");
subparser.addArgument("--member-label").help("Specify the string for the member label.");
}
GroupLinkState getGroupLinkState(String value) throws UserErrorException {
@ -126,6 +128,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
var groupAddMemberPermission = getGroupPermission(ns.getString("set-permission-add-member"));
var groupEditDetailsPermission = getGroupPermission(ns.getString("set-permission-edit-details"));
var groupSendMessagesPermission = getGroupPermission(ns.getString("set-permission-send-messages"));
var memberLabelEmoji = ns.getString("member-label-emoji");
var memberLabelString = ns.getString("member-label");
try {
boolean isNewGroup = false;
@ -159,6 +163,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
.withIsAnnouncementGroup(groupSendMessagesPermission == null
? null
: groupSendMessagesPermission == GroupPermission.ONLY_ADMINS)
.withLabelEmoji(memberLabelEmoji)
.withLabelString(memberLabelString)
.build());
if (results != null) {
if (groupMessageResults == null) {

View File

@ -7806,6 +7806,18 @@
{
"type": "org.whispersystems.signalservice.internal.push.GroupMismatchedDevices[]"
},
{
"type": "org.whispersystems.signalservice.internal.push.GroupPatchResponse",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.Integer",
"java.lang.String"
]
}
]
},
{
"type": "org.whispersystems.signalservice.internal.push.GroupStaleDevices",
"allDeclaredFields": true,