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

@ -48,15 +48,13 @@ const (
KindCoinjoinPool int = 2022
KindCommunityPostApproval int = 4550
KindJobFeedback int = 7000
KindSimpleGroupAddUser int = 9000
KindSimpleGroupPutUser int = 9000
KindSimpleGroupRemoveUser int = 9001
KindSimpleGroupEditMetadata int = 9002
KindSimpleGroupAddPermission int = 9003
KindSimpleGroupRemovePermission int = 9004
KindSimpleGroupDeleteEvent int = 9005
KindSimpleGroupEditGroupStatus int = 9006
KindSimpleGroupCreateGroup int = 9007
KindSimpleGroupDeleteGroup int = 9008
KindSimpleGroupCreateInvite int = 9009
KindSimpleGroupJoinRequest int = 9021
KindSimpleGroupLeaveRequest int = 9022
KindZapGoal int = 9041
@ -116,6 +114,9 @@ const (
KindRepositoryAnnouncement int = 30617
KindRepositoryState int = 30618
KindSimpleGroupMetadata int = 39000
KindSimpleGroupAdmins int = 39001
KindSimpleGroupMembers int = 39002
KindSimpleGroupRoles int = 39003
KindWikiArticle int = 30818
KindRedirects int = 30819
KindFeed int = 31890
@ -129,8 +130,6 @@ const (
KindShortVideoEvent int = 34236
KindVideoViewEvent int = 34237
KindCommunityDefinition int = 34550
KindSimpleGroupAdmins int = 39001
KindSimpleGroupMembers int = 39002
)
func IsRegularKind(kind int) bool {

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,19 +162,21 @@ func (group Group) ToAdminsEvent() *nostr.Event {
}
evt.Tags[0] = nostr.Tag{"d", group.Address.ID}
for member, role := range group.Members {
if role != nil {
for member, roles := range group.Members {
if len(roles) == 0 {
// is not an admin
continue
}
// is an admin
tag := make([]string, 3, 3+len(role.Permissions))
tag := make([]string, 2, 2+len(roles))
tag[0] = "p"
tag[1] = member
tag[2] = role.Name
for perm := range role.Permissions {
tag = append(tag, string(perm))
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]
}
}