Merge fc9a90cb7704e3729a966730ebc6eb018c9d3b37 into 54507eba20904238dd4db8056b4529bd3199dbd2

This commit is contained in:
Joakim Berglund 2026-07-25 01:09:11 +02:00 committed by GitHub
commit b7068f947d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 285 additions and 44 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'`
- 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": {"name": "<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": {"name": "Dad", "emoji": "👨‍👧"}}' 'http://127.0.0.1:8080/v1/groups/+431212131491291/<group id>'`
- Delete a group
Delete the group with the given group id. The group id can be obtained via the "List groups" REST call.

View File

@ -47,10 +47,16 @@ type CreateGroupRequest struct {
ExpirationTime *int `json:"expiration_time,omitempty"`
}
type MemberLabel struct {
Name *string `json:"name,omitempty"`
Emoji *string `json:"emoji,omitempty"`
}
type UpdateGroupRequest struct {
Base64Avatar *string `json:"base64_avatar,omitempty"`
Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
MemberLabel *MemberLabel `json:"member_label,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"`
@ -1854,8 +1860,15 @@ func (a *Api) UpdateGroup(c *gin.Context) {
}
}
var memberLabel *string
var memberLabelEmoji *string
if req.MemberLabel != nil {
memberLabel = req.MemberLabel.Name
memberLabelEmoji = req.MemberLabel.Emoji
}
err = a.signalClient.UpdateGroup(number, internalGroupId, req.Base64Avatar, req.Description, req.Name, req.ExpirationTime, groupLinkState,
editGroupPermission, addMembersPermission, sendMessagesPermission)
editGroupPermission, addMembersPermission, sendMessagesPermission, memberLabel, memberLabelEmoji)
if err != nil {
c.JSON(400, Error{Msg: err.Error()})
return

27
src/api/api_test.go Normal file
View File

@ -0,0 +1,27 @@
package api
import (
"encoding/json"
"testing"
)
func TestUpdateGroupRequestMemberLabel(t *testing.T) {
var req UpdateGroupRequest
rawRequest := []byte(`{"member_label":{"name":"Dad","emoji":"\ud83d\udc4b"}}`)
if err := json.Unmarshal(rawRequest, &req); err != nil {
t.Fatalf("json.Unmarshal() error: %v", err)
}
if req.MemberLabel == nil {
t.Fatal("member_label got nil, want value")
}
if req.MemberLabel.Name == nil || *req.MemberLabel.Name != "Dad" {
t.Fatalf("member_label.name got %v, want Dad", req.MemberLabel.Name)
}
if req.MemberLabel.Emoji == nil || *req.MemberLabel.Emoji != "\U0001F44B" {
t.Fatalf("member_label.emoji got %v, want \\U0001F44B", req.MemberLabel.Emoji)
}
}

View File

@ -113,23 +113,27 @@ func (g GroupLinkState) FromString(input string) GroupLinkState {
}
type GroupEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
InternalId string `json:"internal_id"`
Members []string `json:"members"`
Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []string `json:"pending_invites"`
PendingRequests []string `json:"pending_requests"`
InviteLink string `json:"invite_link"`
Admins []string `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
InternalId string `json:"internal_id"`
MemberLabel string `json:"member_label"`
MemberLabelEmoji string `json:"member_label_emoji"`
Members []string `json:"members"`
Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []string `json:"pending_invites"`
PendingRequests []string `json:"pending_requests"`
InviteLink string `json:"invite_link"`
Admins []string `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
}
type GroupMember struct {
Number string `json:"number"`
Uuid string `json:"uuid"`
Number string `json:"number"`
Uuid string `json:"uuid"`
LabelEmoji *string `json:"label_emoji,omitempty"`
Label *string `json:"label,omitempty"`
}
type GroupAdmin struct {
@ -137,19 +141,28 @@ type GroupAdmin struct {
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 {
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
InternalId string `json:"internal_id"`
Members []GroupMember `json:"members"`
Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []GroupMember `json:"pending_invites"`
PendingRequests []GroupMember `json:"pending_requests"`
InviteLink string `json:"invite_link"`
Admins []GroupAdmin `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
InternalId string `json:"internal_id"`
MemberLabel string `json:"member_label"`
MemberLabelEmoji string `json:"member_label_emoji"`
Members []GroupMember `json:"members"`
Blocked bool `json:"blocked"`
Member bool `json:"member"`
PendingInvites []GroupMember `json:"pending_invites"`
PendingRequests []GroupMember `json:"pending_requests"`
InviteLink string `json:"invite_link"`
Admins []GroupAdmin `json:"admins"`
Permissions ds.GroupPermissions `json:"permissions"`
}
type IdentityEntry struct {
@ -162,20 +175,22 @@ type IdentityEntry struct {
}
type SignalCliGroupEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
IsMember bool `json:"isMember"`
IsBlocked bool `json:"isBlocked"`
Members []GroupMember `json:"members"`
PendingMembers []GroupMember `json:"pendingMembers"`
RequestingMembers []GroupMember `json:"requestingMembers"`
GroupInviteLink string `json:"groupInviteLink"`
Admins []GroupAdmin `json:"admins"`
Uuid string `json:"uuid"`
PermissionEditDetails string `json:"permissionEditDetails"`
PermissionAddMember string `json:"permissionAddMember"`
PermissionSendMessage string `json:"permissionSendMessage"`
Name string `json:"name"`
Description string `json:"description"`
Id string `json:"id"`
IsMember bool `json:"isMember"`
IsBlocked bool `json:"isBlocked"`
MemberLabel *string `json:"memberLabel"`
MemberLabelEmoji *string `json:"memberLabelEmoji"`
Members []SignalCliGroupMember `json:"members"`
PendingMembers []GroupMember `json:"pendingMembers"`
RequestingMembers []GroupMember `json:"requestingMembers"`
GroupInviteLink string `json:"groupInviteLink"`
Admins []GroupAdmin `json:"admins"`
Uuid string `json:"uuid"`
PermissionEditDetails string `json:"permissionEditDetails"`
PermissionAddMember string `json:"permissionAddMember"`
PermissionSendMessage string `json:"permissionSendMessage"`
}
type SignalCliIdentityEntry struct {
@ -1222,6 +1237,35 @@ func prefixUsernameMembers(members []string) []string {
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 {
if len(members) == 0 {
@ -1355,10 +1399,12 @@ func signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry SignalCliGroupE
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
groupEntry.Member = signalCliGroupEntry.IsMember
groupEntry.Description = signalCliGroupEntry.Description
groupEntry.MemberLabel = stringValue(signalCliGroupEntry.MemberLabel)
groupEntry.MemberLabelEmoji = stringValue(signalCliGroupEntry.MemberLabelEmoji)
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
groupEntry.Members = signalCliGroupEntry.Members
groupEntry.Members = signalCliGroupMembersToGroupMembers(signalCliGroupEntry.Members)
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
groupEntry.Admins = signalCliGroupEntry.Admins
@ -1396,7 +1442,11 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
}
for _, signalCliGroupEntry := range signalCliGroupEntries {
groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry))
groupEntry := signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry)
if groupEntry.MemberLabel == "" && groupEntry.MemberLabelEmoji == "" {
groupEntry.MemberLabel, groupEntry.MemberLabelEmoji = findOwnGroupMemberLabel(signalCliGroupEntry.Members, number)
}
groupEntries = append(groupEntries, groupEntry)
}
return groupEntries, nil
@ -1412,6 +1462,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
for _, expandedGroupEntry := range expandedGroupEntries {
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description,
MemberLabel: expandedGroupEntry.MemberLabel, MemberLabelEmoji: expandedGroupEntry.MemberLabelEmoji,
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
members := []string{}
@ -2074,7 +2125,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,
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 avatarTmpPath string = ""
if base64Avatar != nil {
@ -2123,6 +2175,8 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
EditGroupPermissions string `json:"setPermissionEditDetails,omitempty"`
AddMembersPermissions string `json:"setPermissionAddMember,omitempty"`
SendMessagesPermissions string `json:"setPermissionSendMessages,omitempty"`
MemberLabel *string `json:"memberLabel,omitempty"`
MemberLabelEmoji *string `json:"memberLabelEmoji,omitempty"`
}
request := Request{GroupId: groupId}
@ -2153,6 +2207,9 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
request.SendMessagesPermissions = sendMessagesPermission.String()
}
request.MemberLabel = memberLabel
request.MemberLabelEmoji = memberLabelEmoji
jsonRpc2Client, err := s.getJsonRpc2Client()
if err != nil {
return err
@ -2192,6 +2249,14 @@ func (s *SignalClient) UpdateGroup(number string, groupId string, base64Avatar *
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, "")
}

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

@ -219,6 +219,17 @@ const docTemplate = `{
],
"type": "object"
},
"api.MemberLabel": {
"properties": {
"emoji": {
"type": "string"
},
"name": {
"type": "string"
}
},
"type": "object"
},
"api.PinMessageInGroupRequest": {
"properties": {
"duration": {
@ -654,6 +665,9 @@ const docTemplate = `{
],
"type": "string"
},
"member_label": {
"$ref": "#/definitions/api.MemberLabel"
},
"name": {
"type": "string"
},
@ -809,6 +823,12 @@ const docTemplate = `{
"member": {
"type": "boolean"
},
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"members": {
"items": {
"type": "string"
@ -842,6 +862,8 @@ const docTemplate = `{
"internal_id",
"invite_link",
"member",
"member_label",
"member_label_emoji",
"members",
"name",
"pending_invites",

View File

@ -214,6 +214,17 @@
],
"type": "object"
},
"api.MemberLabel": {
"properties": {
"emoji": {
"type": "string"
},
"name": {
"type": "string"
}
},
"type": "object"
},
"api.PinMessageInGroupRequest": {
"properties": {
"duration": {
@ -649,6 +660,9 @@
],
"type": "string"
},
"member_label": {
"$ref": "#/definitions/api.MemberLabel"
},
"name": {
"type": "string"
},
@ -804,6 +818,12 @@
"member": {
"type": "boolean"
},
"member_label": {
"type": "string"
},
"member_label_emoji": {
"type": "string"
},
"members": {
"items": {
"type": "string"
@ -837,6 +857,8 @@
"internal_id",
"invite_link",
"member",
"member_label",
"member_label_emoji",
"members",
"name",
"pending_invites",