diff --git a/client/src/cli.rs b/client/src/cli.rs index fdf31e6f..d7598b9a 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -598,6 +598,12 @@ pub enum CliCommands { #[arg(short = 'e', long)] expiration: Option, + + #[arg(long = "member-label-emoji")] + member_label_emoji: Option, + + #[arg(long = "member-label")] + member_label: Option, }, UpdateProfile { #[arg(long = "given-name")] diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index ad072878..3ebe710b 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -460,6 +460,8 @@ pub trait Rpc { #[allow(non_snake_case)] setPermissionEditDetails: Option, #[allow(non_snake_case)] setPermissionSendMessages: Option, expiration: Option, + #[allow(non_snake_case)] memberLabelEmoji: Option, + #[allow(non_snake_case)] memberLabel: Option, ) -> Result; #[method(name = "updateProfile", param_kind = map)] diff --git a/client/src/main.rs b/client/src/main.rs index f389630e..e025c870 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -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 } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java index c1ba4270..9a79e397 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/UpdateGroup.java @@ -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); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index c4cb0a57..07a9ef3c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -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) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index b5b76c16..437ddb67 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -533,6 +533,18 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } + Pair 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; diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index c3471ed2..8e5ba747 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -633,7 +633,9 @@ public class ManagerImpl implements Manager { updateGroup.getEditDetailsPermission(), updateGroup.getAvatarFile(), updateGroup.getExpirationTimer(), - updateGroup.getIsAnnouncementGroup()); + updateGroup.getIsAnnouncementGroup(), + updateGroup.getLabelEmoji(), + updateGroup.getLabelString()); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java index b6cf8688..25142c26 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java @@ -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 getBannedMembers() { return Set.of(); } diff --git a/man/signal-cli-jsonrpc.5.adoc b/man/signal-cli-jsonrpc.5.adoc index 8273a442..1c035af8 100644 --- a/man/signal-cli-jsonrpc.5.adoc +++ b/man/signal-cli-jsonrpc.5.adoc @@ -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"}` diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 969171c3..787cdd9f 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -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. diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index 1231d3d4..7f93cf04 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -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) { diff --git a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json index c3108727..126bed47 100644 --- a/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json +++ b/src/main/resources/META-INF/native-image/org.asamk/signal-cli/reachability-metadata.json @@ -7806,6 +7806,18 @@ { "type": "org.whispersystems.signalservice.internal.push.GroupMismatchedDevices[]" }, + { + "type": "org.whispersystems.signalservice.internal.push.GroupPatchResponse", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Integer", + "java.lang.String" + ] + } + ] + }, { "type": "org.whispersystems.signalservice.internal.push.GroupStaleDevices", "allDeclaredFields": true,