nip29: update to latest NIP version (change some kinds and remove permissions and moderation stuff).

This commit is contained in:
fiatjaf
2024-11-09 14:25:30 -03:00
parent 7fc2f88e79
commit 456f8687ed
5 changed files with 251 additions and 214 deletions

View File

@@ -3,6 +3,7 @@ package nip29
import (
"fmt"
"net/url"
"slices"
"strings"
"github.com/nbd-wtf/go-nostr"
@@ -40,13 +41,56 @@ type Group struct {
Name string
Picture string
About string
Members map[string]*Role
Members map[string][]*Role
Private bool
Closed bool
Roles []*Role
LastMetadataUpdate nostr.Timestamp
LastAdminsUpdate nostr.Timestamp
LastMembersUpdate nostr.Timestamp
LastRolesUpdate nostr.Timestamp
}
func (group Group) String() string {
maybePrivate := ""
maybeClosed := ""
if group.Private {
maybePrivate = " private"
}
if group.Closed {
maybeClosed = " closed"
}
members := make([]string, len(group.Members))
i := 0
for pubkey, roles := range group.Members {
members[i] = pubkey
if len(roles) > 0 {
members[i] += ":"
}
for _, role := range roles {
members[i] += role.Name
if slices.Contains(group.Roles, role) {
members[i] += "*"
}
members[i] += "/"
}
members[i] = strings.TrimRight(members[i], "/")
i++
}
return fmt.Sprintf(`<Group %s name="%s"%s%s picture="%s" about="%s" members=[%v]>`,
group.Address,
group.Name,
maybePrivate,
maybeClosed,
group.Picture,
group.About,
strings.Join(members, " "),
)
}
// NewGroup takes a group address in the form "<id>'<relay-hostname>"
@@ -59,7 +103,7 @@ func NewGroup(gadstr string) (Group, error) {
return Group{
Address: gad,
Name: gad.ID,
Members: make(map[string]*Role),
Members: make(map[string][]*Role),
}, nil
}
@@ -70,7 +114,7 @@ func NewGroupFromMetadataEvent(relayURL string, evt *nostr.Event) (Group, error)
ID: evt.Tags.GetD(),
},
Name: evt.Tags.GetD(),
Members: make(map[string]*Role),
Members: make(map[string][]*Role),
}
err := g.MergeInMetadataEvent(evt)
@@ -118,18 +162,20 @@ func (group Group) ToAdminsEvent() *nostr.Event {
}
evt.Tags[0] = nostr.Tag{"d", group.Address.ID}
for member, role := range group.Members {
if role != nil {
// is an admin
tag := make([]string, 3, 3+len(role.Permissions))
tag[0] = "p"
tag[1] = member
tag[2] = role.Name
for perm := range role.Permissions {
tag = append(tag, string(perm))
}
evt.Tags = append(evt.Tags, tag)
for member, roles := range group.Members {
if len(roles) == 0 {
// is not an admin
continue
}
// is an admin
tag := make([]string, 2, 2+len(roles))
tag[0] = "p"
tag[1] = member
for _, role := range roles {
tag = append(tag, role.Name)
}
evt.Tags = append(evt.Tags, tag)
}
return evt
@@ -151,6 +197,22 @@ func (group Group) ToMembersEvent() *nostr.Event {
return evt
}
func (group Group) ToRolesEvent() *nostr.Event {
evt := &nostr.Event{
Kind: nostr.KindSimpleGroupMembers,
CreatedAt: group.LastMembersUpdate,
Tags: make(nostr.Tags, 1, 1+len(group.Members)),
}
evt.Tags[0] = nostr.Tag{"d", group.Address.ID}
for _, role := range group.Roles {
// include both admins and normal members
evt.Tags = append(evt.Tags, nostr.Tag{"role", role.Name, role.Description})
}
return evt
}
func (group *Group) MergeInMetadataEvent(evt *nostr.Event) error {
if evt.Kind != nostr.KindSimpleGroupMetadata {
return fmt.Errorf("expected kind %d, got %d", nostr.KindSimpleGroupMetadata, evt.Kind)
@@ -202,16 +264,8 @@ func (group *Group) MergeInAdminsEvent(evt *nostr.Event) error {
continue
}
role := group.Members[tag[1]]
if role == nil {
role = &Role{Name: tag[2]}
group.Members[tag[1]] = role
}
if role.Permissions == nil {
role.Permissions = make(map[Permission]struct{}, len(tag)-3)
}
for _, perm := range tag[2:] {
role.Permissions[Permission(perm)] = struct{}{}
for _, roleName := range tag[2:] {
group.Members[tag[1]] = append(group.Members[tag[1]], group.GetRoleByName(roleName))
}
}
@@ -240,7 +294,7 @@ func (group *Group) MergeInMembersEvent(evt *nostr.Event) error {
_, exists := group.Members[tag[1]]
if !exists {
group.Members[tag[1]] = EmptyRole
group.Members[tag[1]] = nil
}
}

View File

@@ -8,58 +8,29 @@ import (
type Role struct {
Name string
Permissions map[Permission]struct{}
Description string
}
type Permission string
const (
PermAddUser Permission = "add-user"
PermEditMetadata Permission = "edit-metadata"
PermDeleteEvent Permission = "delete-event"
PermRemoveUser Permission = "remove-user"
PermAddPermission Permission = "add-permission"
PermRemovePermission Permission = "remove-permission"
PermEditGroupStatus Permission = "edit-group-status"
PermDeleteGroup Permission = "delete-group"
)
type KindRange []int
var ModerationEventKinds = KindRange{
nostr.KindSimpleGroupAddUser,
nostr.KindSimpleGroupPutUser,
nostr.KindSimpleGroupRemoveUser,
nostr.KindSimpleGroupEditMetadata,
nostr.KindSimpleGroupAddPermission,
nostr.KindSimpleGroupRemovePermission,
nostr.KindSimpleGroupDeleteEvent,
nostr.KindSimpleGroupEditGroupStatus,
nostr.KindSimpleGroupCreateGroup,
nostr.KindSimpleGroupDeleteGroup,
nostr.KindSimpleGroupCreateInvite,
}
var MetadataEventKinds = KindRange{
nostr.KindSimpleGroupMetadata,
nostr.KindSimpleGroupAdmins,
nostr.KindSimpleGroupMembers,
nostr.KindSimpleGroupRoles,
}
func (kr KindRange) Includes(kind int) bool {
_, ok := slices.BinarySearch(kr, kind)
return ok
}
var (
// used for normal members without admin powers
EmptyRole *Role = nil
PermissionsMap = map[Permission]struct{}{
PermAddUser: {},
PermEditMetadata: {},
PermDeleteEvent: {},
PermRemoveUser: {},
PermAddPermission: {},
PermRemovePermission: {},
PermEditGroupStatus: {},
}
)

View File

@@ -3,7 +3,7 @@ package nip29
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
@@ -19,41 +19,42 @@ func TestGroupEventBackAndForth(t *testing.T) {
group1.Private = true
meta1 := group1.ToMetadataEvent()
assert.Equal(t, "xyz", meta1.Tags.GetD(), "translation of group1 to metadata event failed: %s", meta1)
assert.NotNil(t, meta1.Tags.GetFirst([]string{"name", "banana"}), "translation of group1 to metadata event failed: %s", meta1)
assert.NotNil(t, meta1.Tags.GetFirst([]string{"private"}), "translation of group1 to metadata event failed: %s", meta1)
require.Equal(t, "xyz", meta1.Tags.GetD(), "translation of group1 to metadata event failed: %s", meta1)
require.NotNil(t, meta1.Tags.GetFirst([]string{"name", "banana"}), "translation of group1 to metadata event failed: %s", meta1)
require.NotNil(t, meta1.Tags.GetFirst([]string{"private"}), "translation of group1 to metadata event failed: %s", meta1)
group2, _ := NewGroup("groups.com'abc")
group2.Members[ALICE] = &Role{Name: "nada", Permissions: map[Permission]struct{}{PermAddUser: {}}}
group2.Members[BOB] = &Role{Name: "nada", Permissions: map[Permission]struct{}{PermEditMetadata: {}}}
group2.Members[CAROL] = EmptyRole
group2.Members[DEREK] = EmptyRole
group2.Members[ALICE] = []*Role{{Name: "nada"}}
group2.Members[BOB] = []*Role{{Name: "nada"}}
group2.Members[CAROL] = nil
group2.Members[DEREK] = nil
admins2 := group2.ToAdminsEvent()
assert.Equal(t, "abc", admins2.Tags.GetD(), "translation of group2 to admins event failed")
assert.Equal(t, 3, len(admins2.Tags), "translation of group2 to admins event failed")
assert.NotNil(t, admins2.Tags.GetFirst([]string{"p", ALICE, "nada", "add-user"}), "translation of group2 to admins event failed")
assert.NotNil(t, admins2.Tags.GetFirst([]string{"p", BOB, "nada", "edit-metadata"}), "translation of group2 to admins event failed")
require.Equal(t, "abc", admins2.Tags.GetD(), "translation of group2 to admins event failed")
require.Equal(t, 3, len(admins2.Tags), "translation of group2 to admins event failed")
require.NotNil(t, admins2.Tags.GetFirst([]string{"p", ALICE, "nada"}), "translation of group2 to admins event failed")
require.NotNil(t, admins2.Tags.GetFirst([]string{"p", BOB, "nada"}), "translation of group2 to admins event failed")
members2 := group2.ToMembersEvent()
assert.Equal(t, "abc", members2.Tags.GetD(), "translation of group2 to members2 event failed")
assert.Equal(t, 5, len(members2.Tags), "translation of group2 to members2 event failed")
assert.NotNil(t, members2.Tags.GetFirst([]string{"p", ALICE}), "translation of group2 to members2 event failed")
assert.NotNil(t, members2.Tags.GetFirst([]string{"p", BOB}), "translation of group2 to members2 event failed")
assert.NotNil(t, members2.Tags.GetFirst([]string{"p", CAROL}), "translation of group2 to members2 event failed")
assert.NotNil(t, members2.Tags.GetFirst([]string{"p", DEREK}), "translation of group2 to members2 event failed")
require.Equal(t, "abc", members2.Tags.GetD(), "translation of group2 to members2 event failed")
require.Equal(t, 5, len(members2.Tags), "translation of group2 to members2 event failed")
require.NotNil(t, members2.Tags.GetFirst([]string{"p", ALICE}), "translation of group2 to members2 event failed")
require.NotNil(t, members2.Tags.GetFirst([]string{"p", BOB}), "translation of group2 to members2 event failed")
require.NotNil(t, members2.Tags.GetFirst([]string{"p", CAROL}), "translation of group2 to members2 event failed")
require.NotNil(t, members2.Tags.GetFirst([]string{"p", DEREK}), "translation of group2 to members2 event failed")
group1.MergeInMembersEvent(members2)
assert.Equal(t, 4, len(group1.Members), "merge of members2 into group1 failed")
assert.Equal(t, EmptyRole, group1.Members[ALICE], "merge of members2 into group1 failed")
assert.Equal(t, EmptyRole, group1.Members[DEREK], "merge of members2 into group1 failed")
require.Equal(t, 4, len(group1.Members), "merge of members2 into group1 failed")
require.Len(t, group1.Members[ALICE], 0, "merge of members2 into group1 failed")
require.Len(t, group1.Members[DEREK], 0, "merge of members2 into group1 failed")
group1.MergeInAdminsEvent(admins2)
assert.Equal(t, 4, len(group1.Members), "merge of admins2 into group1 failed")
assert.Equal(t, "nada", group1.Members[ALICE].Name, "merge of admins2 into group1 failed")
assert.Equal(t, EmptyRole, group1.Members[DEREK], "merge of admins2 into group1 failed")
require.Equal(t, 4, len(group1.Members), "merge of admins2 into group1 failed")
require.Equal(t, "nada", group1.Members[ALICE][0].Name, "merge of admins2 into group1 failed")
require.Len(t, group1.Members[DEREK], 0, "merge of admins2 into group1 failed")
group2.MergeInMetadataEvent(meta1)
assert.Equal(t, "banana", group2.Name, "merge of meta1 into group2 failed")
assert.Equal(t, "abc", group2.Address.ID, "merge of meta1 into group2 failed")
require.Equal(t, "banana", group2.Name, "merge of meta1 into group2 failed")
require.Equal(t, "abc", group2.Address.ID, "merge of meta1 into group2 failed")
}

12
nip29/utils.go Normal file
View File

@@ -0,0 +1,12 @@
package nip29
import "slices"
func (group Group) GetRoleByName(name string) *Role {
idx := slices.IndexFunc(group.Roles, func(role *Role) bool { return role.Name == name })
if idx == -1 {
return &Role{Name: name}
} else {
return group.Roles[idx]
}
}