Merge 0938262814a70af91f7daba84e868586c1c6bc4a into ad5d3b76dbf87f601e1e9cbfa998a114a54a1c75

This commit is contained in:
Joakim Berglund 2026-05-17 22:29:09 +02:00 committed by GitHub
commit 74ece93bbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 244 additions and 48 deletions

View File

@ -115,6 +115,16 @@ e.g:
`curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291'` `curl -X GET -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/groups/+431212131491291'`
- Update your group member label
Updates the member label for the registered number in the given group. The group id can be obtained via the "List groups" REST call.
`curl -X PUT -H "Content-Type: application/json" -d '{"member_label": "<label>", "member_label_emoji": "<emoji>"}' 'http://127.0.0.1:8080/v1/groups/<number>/<group id>'`
e.g:
`curl -X PUT -H "Content-Type: application/json" -d '{"member_label": "Dad", "member_label_emoji": "👨‍👧"}' 'http://127.0.0.1:8080/v1/groups/+431212131491291/<group id>'`
- Delete a group - Delete a group
Delete the group with the given group id. The group id can be obtained via the "List groups" REST call. Delete the group with the given group id. The group id can be obtained via the "List groups" REST call.

View File

@ -48,12 +48,14 @@ type CreateGroupRequest struct {
} }
type UpdateGroupRequest struct { type UpdateGroupRequest struct {
Base64Avatar *string `json:"base64_avatar,omitempty"` Base64Avatar *string `json:"base64_avatar,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
ExpirationTime *int `json:"expiration_time,omitempty"` MemberLabel *string `json:"member_label,omitempty"`
GroupLinkState *string `json:"group_link,omitempty" enums:"disabled,enabled,enabled-with-approval"` MemberLabelEmoji *string `json:"member_label_emoji,omitempty"`
Permissions *ds.GroupPermissions `json:"permissions,omitempty"` ExpirationTime *int `json:"expiration_time,omitempty"`
GroupLinkState *string `json:"group_link,omitempty" enums:"disabled,enabled,enabled-with-approval"`
Permissions *ds.GroupPermissions `json:"permissions,omitempty"`
} }
type PinMessageInGroupRequest struct { type PinMessageInGroupRequest struct {
@ -1860,7 +1862,7 @@ func (a *Api) UpdateGroup(c *gin.Context) {
} }
err = a.signalClient.UpdateGroup(number, internalGroupId, req.Base64Avatar, req.Description, req.Name, req.ExpirationTime, groupLinkState, err = a.signalClient.UpdateGroup(number, internalGroupId, req.Base64Avatar, req.Description, req.Name, req.ExpirationTime, groupLinkState,
editGroupPermission, addMembersPermission, sendMessagesPermission) editGroupPermission, addMembersPermission, sendMessagesPermission, req.MemberLabel, req.MemberLabelEmoji)
if err != nil { if err != nil {
c.JSON(400, Error{Msg: err.Error()}) c.JSON(400, Error{Msg: err.Error()})
return return

View File

@ -113,22 +113,26 @@ func (g GroupLinkState) FromString(input string) GroupLinkState {
} }
type GroupEntry struct { type GroupEntry struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Id string `json:"id"` Id string `json:"id"`
InternalId string `json:"internal_id"` InternalId string `json:"internal_id"`
Members []string `json:"members"` MemberLabel string `json:"member_label"`
Blocked bool `json:"blocked"` MemberLabelEmoji string `json:"member_label_emoji"`
PendingInvites []string `json:"pending_invites"` Members []string `json:"members"`
PendingRequests []string `json:"pending_requests"` Blocked bool `json:"blocked"`
InviteLink string `json:"invite_link"` PendingInvites []string `json:"pending_invites"`
Admins []string `json:"admins"` PendingRequests []string `json:"pending_requests"`
Permissions ds.GroupPermissions `json:"permissions"` InviteLink string `json:"invite_link"`
Admins []string `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
} }
type GroupMember struct { type GroupMember struct {
Number string `json:"number"` Number string `json:"number"`
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
LabelEmoji *string `json:"label_emoji,omitempty"`
Label *string `json:"label,omitempty"`
} }
type GroupAdmin struct { type GroupAdmin struct {
@ -136,18 +140,27 @@ type GroupAdmin struct {
Uuid string `json:"uuid"` Uuid string `json:"uuid"`
} }
type SignalCliGroupMember struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
LabelEmoji *string `json:"labelEmoji"`
Label *string `json:"label"`
}
type ExpandedGroupEntry struct { type ExpandedGroupEntry struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Id string `json:"id"` Id string `json:"id"`
InternalId string `json:"internal_id"` InternalId string `json:"internal_id"`
Members []GroupMember `json:"members"` MemberLabel string `json:"member_label"`
Blocked bool `json:"blocked"` MemberLabelEmoji string `json:"member_label_emoji"`
PendingInvites []GroupMember `json:"pending_invites"` Members []GroupMember `json:"members"`
PendingRequests []GroupMember `json:"pending_requests"` Blocked bool `json:"blocked"`
InviteLink string `json:"invite_link"` PendingInvites []GroupMember `json:"pending_invites"`
Admins []GroupAdmin `json:"admins"` PendingRequests []GroupMember `json:"pending_requests"`
Permissions ds.GroupPermissions `json:"permissions"` InviteLink string `json:"invite_link"`
Admins []GroupAdmin `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
} }
type IdentityEntry struct { type IdentityEntry struct {
@ -160,20 +173,22 @@ type IdentityEntry struct {
} }
type SignalCliGroupEntry struct { type SignalCliGroupEntry struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Id string `json:"id"` Id string `json:"id"`
IsMember bool `json:"isMember"` IsMember bool `json:"isMember"`
IsBlocked bool `json:"isBlocked"` IsBlocked bool `json:"isBlocked"`
Members []GroupMember `json:"members"` MemberLabel *string `json:"memberLabel"`
PendingMembers []GroupMember `json:"pendingMembers"` MemberLabelEmoji *string `json:"memberLabelEmoji"`
RequestingMembers []GroupMember `json:"requestingMembers"` Members []SignalCliGroupMember `json:"members"`
GroupInviteLink string `json:"groupInviteLink"` PendingMembers []GroupMember `json:"pendingMembers"`
Admins []GroupAdmin `json:"admins"` RequestingMembers []GroupMember `json:"requestingMembers"`
Uuid string `json:"uuid"` GroupInviteLink string `json:"groupInviteLink"`
PermissionEditDetails string `json:"permissionEditDetails"` Admins []GroupAdmin `json:"admins"`
PermissionAddMember string `json:"permissionAddMember"` Uuid string `json:"uuid"`
PermissionSendMessage string `json:"permissionSendMessage"` PermissionEditDetails string `json:"permissionEditDetails"`
PermissionAddMember string `json:"permissionAddMember"`
PermissionSendMessage string `json:"permissionSendMessage"`
} }
type SignalCliIdentityEntry struct { type SignalCliIdentityEntry struct {
@ -1205,6 +1220,35 @@ func prefixUsernameMembers(members []string) []string {
return res return res
} }
func signalCliGroupMembersToGroupMembers(signalCliGroupMembers []SignalCliGroupMember) []GroupMember {
groupMembers := []GroupMember{}
for _, signalCliGroupMember := range signalCliGroupMembers {
groupMembers = append(groupMembers, GroupMember{
Number: signalCliGroupMember.Number,
Uuid: signalCliGroupMember.Uuid,
LabelEmoji: signalCliGroupMember.LabelEmoji,
Label: signalCliGroupMember.Label,
})
}
return groupMembers
}
func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
func findOwnGroupMemberLabel(members []SignalCliGroupMember, number string) (string, string) {
for _, member := range members {
if member.Number == number {
return stringValue(member.Label), stringValue(member.LabelEmoji)
}
}
return "", ""
}
func (s *SignalClient) updateGroupMembers(number string, groupId string, members []string, add bool) error { func (s *SignalClient) updateGroupMembers(number string, groupId string, members []string, add bool) error {
if len(members) == 0 { if len(members) == 0 {
@ -1366,10 +1410,15 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id) groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
groupEntry.Blocked = signalCliGroupEntry.IsBlocked groupEntry.Blocked = signalCliGroupEntry.IsBlocked
groupEntry.Description = signalCliGroupEntry.Description groupEntry.Description = signalCliGroupEntry.Description
groupEntry.MemberLabel = stringValue(signalCliGroupEntry.MemberLabel)
groupEntry.MemberLabelEmoji = stringValue(signalCliGroupEntry.MemberLabelEmoji)
if groupEntry.MemberLabel == "" && groupEntry.MemberLabelEmoji == "" {
groupEntry.MemberLabel, groupEntry.MemberLabelEmoji = findOwnGroupMemberLabel(signalCliGroupEntry.Members, number)
}
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage) groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage) groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember) groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
groupEntry.Members = signalCliGroupEntry.Members groupEntry.Members = signalCliGroupMembersToGroupMembers(signalCliGroupEntry.Members)
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
groupEntry.Admins = signalCliGroupEntry.Admins groupEntry.Admins = signalCliGroupEntry.Admins
@ -1391,6 +1440,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
for _, expandedGroupEntry := range expandedGroupEntries { for _, expandedGroupEntry := range expandedGroupEntries {
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name, groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description, Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
MemberLabel: expandedGroupEntry.MemberLabel, MemberLabelEmoji: expandedGroupEntry.MemberLabelEmoji,
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink} Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
members := []string{} members := []string{}
@ -2053,7 +2103,8 @@ func (s *SignalClient) QuitGroup(number string, groupId string) error {
} }
func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *string, groupDescription *string, groupName *string, expirationTime *int, func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *string, groupDescription *string, groupName *string, expirationTime *int,
groupLinkState *GroupLinkState, editGroupPermission GroupPermission, addMembersPermission GroupPermission, sendMessagesPermission GroupPermission) error { groupLinkState *GroupLinkState, editGroupPermission GroupPermission, addMembersPermission GroupPermission, sendMessagesPermission GroupPermission,
memberLabel *string, memberLabelEmoji *string) error {
var err error var err error
var avatarTmpPath string = "" var avatarTmpPath string = ""
if base64Avatar != nil { if base64Avatar != nil {
@ -2102,6 +2153,8 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
EditGroupPermissions string `json:"setPermissionEditDetails,omitempty"` EditGroupPermissions string `json:"setPermissionEditDetails,omitempty"`
AddMembersPermissions string `json:"setPermissionAddMember,omitempty"` AddMembersPermissions string `json:"setPermissionAddMember,omitempty"`
SendMessagesPermissions string `json:"setPermissionSendMessages,omitempty"` SendMessagesPermissions string `json:"setPermissionSendMessages,omitempty"`
MemberLabel *string `json:"memberLabel,omitempty"`
MemberLabelEmoji *string `json:"memberLabelEmoji,omitempty"`
} }
request := Request{GroupId: groupId} request := Request{GroupId: groupId}
@ -2132,6 +2185,9 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
request.SendMessagesPermissions = sendMessagesPermission.String() request.SendMessagesPermissions = sendMessagesPermission.String()
} }
request.MemberLabel = memberLabel
request.MemberLabelEmoji = memberLabelEmoji
jsonRpc2Client, err := s.getJsonRpc2Client() jsonRpc2Client, err := s.getJsonRpc2Client()
if err != nil { if err != nil {
return err return err
@ -2171,6 +2227,14 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
cmd = append(cmd, []string{"--set-permission-send-messages", sendMessagesPermission.String()}...) cmd = append(cmd, []string{"--set-permission-send-messages", sendMessagesPermission.String()}...)
} }
if memberLabelEmoji != nil {
cmd = append(cmd, []string{"--member-label-emoji", *memberLabelEmoji}...)
}
if memberLabel != nil {
cmd = append(cmd, []string{"--member-label", *memberLabel}...)
}
_, err = s.cliClient.Execute(true, cmd, "") _, err = s.cliClient.Execute(true, cmd, "")
} }

82
src/client/group_test.go Normal file
View File

@ -0,0 +1,82 @@
package client
import (
"encoding/json"
"testing"
)
func TestSignalCliGroupEntryMemberLabelFields(t *testing.T) {
var signalCliGroupEntry SignalCliGroupEntry
rawGroup := []byte(`{"id":"abc","members":[{"number":"+1234","uuid":"uuid-1","labelEmoji":"\ud83d\udc4b","label":"Dad"}]}`)
if err := json.Unmarshal(rawGroup, &signalCliGroupEntry); err != nil {
t.Fatalf("json.Unmarshal() error: %v", err)
}
memberLabel, memberLabelEmoji := findOwnGroupMemberLabel(signalCliGroupEntry.Members, "+1234")
if memberLabel != "Dad" {
t.Fatalf("memberLabel got %v, want Dad", memberLabel)
}
if memberLabelEmoji != "\U0001F44B" {
t.Fatalf("memberLabelEmoji got %v, want \\U0001F44B", memberLabelEmoji)
}
groupEntry := GroupEntry{
MemberLabel: memberLabel,
MemberLabelEmoji: memberLabelEmoji,
}
encodedGroup, err := json.Marshal(groupEntry)
if err != nil {
t.Fatalf("json.Marshal() error: %v", err)
}
var response map[string]any
if err := json.Unmarshal(encodedGroup, &response); err != nil {
t.Fatalf("json.Unmarshal() response error: %v", err)
}
if response["member_label"] != "Dad" {
t.Fatalf("member_label got %v, want Dad", response["member_label"])
}
if response["member_label_emoji"] != "\U0001F44B" {
t.Fatalf("member_label_emoji got %v, want \\U0001F44B", response["member_label_emoji"])
}
}
func TestSignalCliGroupEntryMissingMemberLabelFields(t *testing.T) {
var signalCliGroupEntry SignalCliGroupEntry
rawGroup := []byte(`{"id":"abc","members":[{"number":"+1234","uuid":"uuid-1"}]}`)
if err := json.Unmarshal(rawGroup, &signalCliGroupEntry); err != nil {
t.Fatalf("json.Unmarshal() error: %v", err)
}
memberLabel, memberLabelEmoji := findOwnGroupMemberLabel(signalCliGroupEntry.Members, "+1234")
groupEntry := GroupEntry{
MemberLabel: memberLabel,
MemberLabelEmoji: memberLabelEmoji,
}
encodedGroup, err := json.Marshal(groupEntry)
if err != nil {
t.Fatalf("json.Marshal() error: %v", err)
}
var response map[string]any
if err := json.Unmarshal(encodedGroup, &response); err != nil {
t.Fatalf("json.Unmarshal() response error: %v", err)
}
if response["member_label"] != "" {
t.Fatalf("member_label got %v, want empty string", response["member_label"])
}
if response["member_label_emoji"] != "" {
t.Fatalf("member_label_emoji got %v, want empty string", response["member_label_emoji"])
}
}

View File

@ -3353,6 +3353,12 @@ const docTemplate = `{
"enabled-with-approval" "enabled-with-approval"
] ]
}, },
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -3490,6 +3496,8 @@ const docTemplate = `{
"id", "id",
"internal_id", "internal_id",
"invite_link", "invite_link",
"member_label",
"member_label_emoji",
"members", "members",
"name", "name",
"pending_invites", "pending_invites",
@ -3518,6 +3526,12 @@ const docTemplate = `{
"invite_link": { "invite_link": {
"type": "string" "type": "string"
}, },
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"members": { "members": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -3350,6 +3350,12 @@
"enabled-with-approval" "enabled-with-approval"
] ]
}, },
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"name": { "name": {
"type": "string" "type": "string"
}, },
@ -3487,6 +3493,8 @@
"id", "id",
"internal_id", "internal_id",
"invite_link", "invite_link",
"member_label",
"member_label_emoji",
"members", "members",
"name", "name",
"pending_invites", "pending_invites",
@ -3515,6 +3523,12 @@
"invite_link": { "invite_link": {
"type": "string" "type": "string"
}, },
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"members": { "members": {
"type": "array", "type": "array",
"items": { "items": {
@ -3835,4 +3849,4 @@
"name": "Sticker Packs" "name": "Sticker Packs"
} }
] ]
} }

View File

@ -438,6 +438,10 @@ definitions:
- enabled - enabled
- enabled-with-approval - enabled-with-approval
type: string type: string
member_label:
type: string
member_label_emoji:
type: string
name: name:
type: string type: string
permissions: permissions:
@ -542,6 +546,10 @@ definitions:
type: string type: string
invite_link: invite_link:
type: string type: string
member_label:
type: string
member_label_emoji:
type: string
members: members:
items: items:
type: string type: string
@ -565,6 +573,8 @@ definitions:
- id - id
- internal_id - internal_id
- invite_link - invite_link
- member_label
- member_label_emoji
- members - members
- name - name
- pending_invites - pending_invites