Skip to content

Commit fe9df01

Browse files
authored
Merge pull request #859 from pinkustar/expose-group-membership
Expose group membership (isMember) on the /v1/groups endpoints
2 parents a4f5855 + ede08b6 commit fe9df01

4 files changed

Lines changed: 124 additions & 17 deletions

File tree

src/client/client.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ type GroupEntry struct {
119119
InternalId string `json:"internal_id"`
120120
Members []string `json:"members"`
121121
Blocked bool `json:"blocked"`
122+
Member bool `json:"member"`
122123
PendingInvites []string `json:"pending_invites"`
123124
PendingRequests []string `json:"pending_requests"`
124125
InviteLink string `json:"invite_link"`
@@ -143,6 +144,7 @@ type ExpandedGroupEntry struct {
143144
InternalId string `json:"internal_id"`
144145
Members []GroupMember `json:"members"`
145146
Blocked bool `json:"blocked"`
147+
Member bool `json:"member"`
146148
PendingInvites []GroupMember `json:"pending_invites"`
147149
PendingRequests []GroupMember `json:"pending_requests"`
148150
InviteLink string `json:"invite_link"`
@@ -1330,6 +1332,25 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi
13301332
return s.updateGroupAdmins(number, groupId, admins, false)
13311333
}
13321334

1335+
func signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry SignalCliGroupEntry) ExpandedGroupEntry {
1336+
var groupEntry ExpandedGroupEntry
1337+
groupEntry.InternalId = signalCliGroupEntry.Id
1338+
groupEntry.Name = signalCliGroupEntry.Name
1339+
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
1340+
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
1341+
groupEntry.Member = signalCliGroupEntry.IsMember
1342+
groupEntry.Description = signalCliGroupEntry.Description
1343+
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
1344+
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
1345+
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
1346+
groupEntry.Members = signalCliGroupEntry.Members
1347+
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
1348+
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
1349+
groupEntry.Admins = signalCliGroupEntry.Admins
1350+
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
1351+
return groupEntry
1352+
}
1353+
13331354
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
13341355
groupEntries := []ExpandedGroupEntry{}
13351356

@@ -1360,22 +1381,7 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
13601381
}
13611382

13621383
for _, signalCliGroupEntry := range signalCliGroupEntries {
1363-
var groupEntry ExpandedGroupEntry
1364-
groupEntry.InternalId = signalCliGroupEntry.Id
1365-
groupEntry.Name = signalCliGroupEntry.Name
1366-
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
1367-
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
1368-
groupEntry.Description = signalCliGroupEntry.Description
1369-
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
1370-
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
1371-
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
1372-
groupEntry.Members = signalCliGroupEntry.Members
1373-
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
1374-
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
1375-
groupEntry.Admins = signalCliGroupEntry.Admins
1376-
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
1377-
1378-
groupEntries = append(groupEntries, groupEntry)
1384+
groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry))
13791385
}
13801386

13811387
return groupEntries, nil
@@ -1390,7 +1396,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
13901396
groupEntries := []GroupEntry{}
13911397
for _, expandedGroupEntry := range expandedGroupEntries {
13921398
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
1393-
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
1399+
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description,
13941400
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
13951401

13961402
members := []string{}

src/client/groups_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package client
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
// sampleListGroupsJSON mirrors the shape of signal-cli's `listGroups` JSON output
9+
// (see signal-cli's ListGroupsCommand / the jsonrpc man page). It contains a group
10+
// the account is still in (isMember=true), a group it has left or been removed from
11+
// (isMember=false) — the "ghost" group that the REST API previously could not
12+
// distinguish — and a blocked-but-still-member group to verify the two flags are
13+
// mapped independently.
14+
const sampleListGroupsJSON = `[
15+
{
16+
"id": "Pmpi+EfPWmsxiomLe9Nx2XF9HOE483p6iKiFj65iMwI=",
17+
"name": "Current Group",
18+
"description": "still a member",
19+
"isMember": true,
20+
"isBlocked": false,
21+
"members": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
22+
"pendingMembers": [],
23+
"requestingMembers": [],
24+
"admins": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
25+
"groupInviteLink": "",
26+
"permissionAddMember": "EVERY_MEMBER",
27+
"permissionSendMessage": "EVERY_MEMBER"
28+
},
29+
{
30+
"id": "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MGFiY2RlZmdoaWo=",
31+
"name": "Left Group",
32+
"description": "removed or left",
33+
"isMember": false,
34+
"isBlocked": false,
35+
"members": [],
36+
"pendingMembers": [],
37+
"requestingMembers": [],
38+
"admins": [],
39+
"groupInviteLink": ""
40+
},
41+
{
42+
"id": "YmxvY2tlZGdyb3VwaWQwMDAwMDAwMDAwMDAwMDAwMDA=",
43+
"name": "Blocked But Member",
44+
"description": "blocked yet still a member",
45+
"isMember": true,
46+
"isBlocked": true,
47+
"members": [],
48+
"pendingMembers": [],
49+
"requestingMembers": [],
50+
"admins": [],
51+
"groupInviteLink": ""
52+
}
53+
]`
54+
55+
// TestSignalCliGroupEntryToExpandedGroupEntry verifies that the signal-cli isMember
56+
// flag is carried through to the REST ExpandedGroupEntry.Member field, independently
57+
// of the isBlocked -> Blocked mapping.
58+
func TestSignalCliGroupEntryToExpandedGroupEntry(t *testing.T) {
59+
var entries []SignalCliGroupEntry
60+
if err := json.Unmarshal([]byte(sampleListGroupsJSON), &entries); err != nil {
61+
t.Fatalf("failed to unmarshal sample listGroups JSON: %v", err)
62+
}
63+
if len(entries) != 3 {
64+
t.Fatalf("expected 3 group entries, got %d", len(entries))
65+
}
66+
67+
cases := []struct {
68+
name string
69+
wantMember bool
70+
wantBlocked bool
71+
}{
72+
{"Current Group", true, false},
73+
{"Left Group", false, false},
74+
{"Blocked But Member", true, true},
75+
}
76+
77+
for i, c := range cases {
78+
got := signalCliGroupEntryToExpandedGroupEntry(entries[i])
79+
80+
if got.Name != c.name {
81+
t.Errorf("entry %d: Name = %q, want %q", i, got.Name, c.name)
82+
}
83+
if got.Member != c.wantMember {
84+
t.Errorf("%s: Member = %v, want %v (must reflect signal-cli isMember)", c.name, got.Member, c.wantMember)
85+
}
86+
if got.Blocked != c.wantBlocked {
87+
t.Errorf("%s: Blocked = %v, want %v", c.name, got.Blocked, c.wantBlocked)
88+
}
89+
if got.InternalId != entries[i].Id {
90+
t.Errorf("%s: InternalId = %q, want %q", c.name, got.InternalId, entries[i].Id)
91+
}
92+
}
93+
}

src/docs/docs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,9 @@ const docTemplate = `{
806806
"invite_link": {
807807
"type": "string"
808808
},
809+
"member": {
810+
"type": "boolean"
811+
},
809812
"members": {
810813
"items": {
811814
"type": "string"
@@ -838,6 +841,7 @@ const docTemplate = `{
838841
"id",
839842
"internal_id",
840843
"invite_link",
844+
"member",
841845
"members",
842846
"name",
843847
"pending_invites",

src/docs/swagger.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,9 @@
801801
"invite_link": {
802802
"type": "string"
803803
},
804+
"member": {
805+
"type": "boolean"
806+
},
804807
"members": {
805808
"items": {
806809
"type": "string"
@@ -833,6 +836,7 @@
833836
"id",
834837
"internal_id",
835838
"invite_link",
839+
"member",
836840
"members",
837841
"name",
838842
"pending_invites",

0 commit comments

Comments
 (0)