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

259
kinds.go
View File

@ -1,136 +1,135 @@
package nostr
const (
KindProfileMetadata int = 0
KindTextNote int = 1
KindRecommendServer int = 2
KindFollowList int = 3
KindEncryptedDirectMessage int = 4
KindDeletion int = 5
KindRepost int = 6
KindReaction int = 7
KindBadgeAward int = 8
KindSimpleGroupChatMessage int = 9
KindSimpleGroupThreadedReply int = 10
KindSimpleGroupThread int = 11
KindSimpleGroupReply int = 12
KindSeal int = 13
KindDirectMessage int = 14
KindGenericRepost int = 16
KindReactionToWebsite int = 17
KindChannelCreation int = 40
KindChannelMetadata int = 41
KindChannelMessage int = 42
KindChannelHideMessage int = 43
KindChannelMuteUser int = 44
KindChess int = 64
KindMergeRequests int = 818
KindBid int = 1021
KIndBidConfirmation int = 1022
KindOpenTimestamps int = 1040
KindGiftWrap int = 1059
KindFileMetadata int = 1063
KindLiveChatMessage int = 1311
KindPatch int = 1617
KindIssue int = 1621
KindReply int = 1622
KindStatusOpen int = 1630
KindStatusApplied int = 1631
KindStatusClosed int = 1632
KindStatusDraft int = 1633
KindProblemTracker int = 1971
KindReporting int = 1984
KindLabel int = 1985
KindRelayReviews int = 1986
KindAIEmbeddings int = 1987
KindTorrent int = 2003
KindTorrentComment int = 2004
KindCoinjoinPool int = 2022
KindCommunityPostApproval int = 4550
KindJobFeedback int = 7000
KindSimpleGroupAddUser 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
KindSimpleGroupJoinRequest int = 9021
KindSimpleGroupLeaveRequest int = 9022
KindZapGoal int = 9041
KindTidalLogin int = 9467
KindZapRequest int = 9734
KindZap int = 9735
KindHighlights int = 9802
KindMuteList int = 10000
KindPinList int = 10001
KindRelayListMetadata int = 10002
KindBookmarkList int = 10003
KindCommunityList int = 10004
KindPublicChatList int = 10005
KindBlockedRelayList int = 10006
KindSearchRelayList int = 10007
KindSimpleGroupList int = 10009
KindInterestList int = 10015
KindEmojiList int = 10030
KindDMRelayList int = 10050
KindUserServerList int = 10063
KindFileStorageServerList int = 10096
KindGoodWikiAuthorList int = 10101
KindGoodWikiRelayList int = 10102
KindNWCWalletInfo int = 13194
KindLightningPubRPC int = 21000
KindClientAuthentication int = 22242
KindNWCWalletRequest int = 23194
KindNWCWalletResponse int = 23195
KindNostrConnect int = 24133
KindBlobs int = 24242
KindHTTPAuth int = 27235
KindCategorizedPeopleList int = 30000
KindCategorizedBookmarksList int = 30001
KindRelaySets int = 30002
KindBookmarkSets int = 30003
KindCuratedSets int = 30004
KindCuratedVideoSets int = 30005
KindMuteSets int = 30007
KindProfileBadges int = 30008
KindBadgeDefinition int = 30009
KindInterestSets int = 30015
KindStallDefinition int = 30017
KindProductDefinition int = 30018
KindMarketplaceUI int = 30019
KindProductSoldAsAuction int = 30020
KindArticle int = 30023
KindDraftArticle int = 30024
KindEmojiSets int = 30030
KindModularArticleHeader int = 30040
KindModularArticleContent int = 30041
KindReleaseArtifactSets int = 30063
KindApplicationSpecificData int = 30078
KindLiveEvent int = 30311
KindUserStatuses int = 30315
KindClassifiedListing int = 30402
KindDraftClassifiedListing int = 30403
KindRepositoryAnnouncement int = 30617
KindRepositoryState int = 30618
KindSimpleGroupMetadata int = 39000
KindWikiArticle int = 30818
KindRedirects int = 30819
KindFeed int = 31890
KindDateCalendarEvent int = 31922
KindTimeCalendarEvent int = 31923
KindCalendar int = 31924
KindCalendarEventRSVP int = 31925
KindHandlerRecommendation int = 31989
KindHandlerInformation int = 31990
KindVideoEvent int = 34235
KindShortVideoEvent int = 34236
KindVideoViewEvent int = 34237
KindCommunityDefinition int = 34550
KindSimpleGroupAdmins int = 39001
KindSimpleGroupMembers int = 39002
KindProfileMetadata int = 0
KindTextNote int = 1
KindRecommendServer int = 2
KindFollowList int = 3
KindEncryptedDirectMessage int = 4
KindDeletion int = 5
KindRepost int = 6
KindReaction int = 7
KindBadgeAward int = 8
KindSimpleGroupChatMessage int = 9
KindSimpleGroupThreadedReply int = 10
KindSimpleGroupThread int = 11
KindSimpleGroupReply int = 12
KindSeal int = 13
KindDirectMessage int = 14
KindGenericRepost int = 16
KindReactionToWebsite int = 17
KindChannelCreation int = 40
KindChannelMetadata int = 41
KindChannelMessage int = 42
KindChannelHideMessage int = 43
KindChannelMuteUser int = 44
KindChess int = 64
KindMergeRequests int = 818
KindBid int = 1021
KIndBidConfirmation int = 1022
KindOpenTimestamps int = 1040
KindGiftWrap int = 1059
KindFileMetadata int = 1063
KindLiveChatMessage int = 1311
KindPatch int = 1617
KindIssue int = 1621
KindReply int = 1622
KindStatusOpen int = 1630
KindStatusApplied int = 1631
KindStatusClosed int = 1632
KindStatusDraft int = 1633
KindProblemTracker int = 1971
KindReporting int = 1984
KindLabel int = 1985
KindRelayReviews int = 1986
KindAIEmbeddings int = 1987
KindTorrent int = 2003
KindTorrentComment int = 2004
KindCoinjoinPool int = 2022
KindCommunityPostApproval int = 4550
KindJobFeedback int = 7000
KindSimpleGroupPutUser int = 9000
KindSimpleGroupRemoveUser int = 9001
KindSimpleGroupEditMetadata int = 9002
KindSimpleGroupDeleteEvent int = 9005
KindSimpleGroupCreateGroup int = 9007
KindSimpleGroupDeleteGroup int = 9008
KindSimpleGroupCreateInvite int = 9009
KindSimpleGroupJoinRequest int = 9021
KindSimpleGroupLeaveRequest int = 9022
KindZapGoal int = 9041
KindTidalLogin int = 9467
KindZapRequest int = 9734
KindZap int = 9735
KindHighlights int = 9802
KindMuteList int = 10000
KindPinList int = 10001
KindRelayListMetadata int = 10002
KindBookmarkList int = 10003
KindCommunityList int = 10004
KindPublicChatList int = 10005
KindBlockedRelayList int = 10006
KindSearchRelayList int = 10007
KindSimpleGroupList int = 10009
KindInterestList int = 10015
KindEmojiList int = 10030
KindDMRelayList int = 10050
KindUserServerList int = 10063
KindFileStorageServerList int = 10096
KindGoodWikiAuthorList int = 10101
KindGoodWikiRelayList int = 10102
KindNWCWalletInfo int = 13194
KindLightningPubRPC int = 21000
KindClientAuthentication int = 22242
KindNWCWalletRequest int = 23194
KindNWCWalletResponse int = 23195
KindNostrConnect int = 24133
KindBlobs int = 24242
KindHTTPAuth int = 27235
KindCategorizedPeopleList int = 30000
KindCategorizedBookmarksList int = 30001
KindRelaySets int = 30002
KindBookmarkSets int = 30003
KindCuratedSets int = 30004
KindCuratedVideoSets int = 30005
KindMuteSets int = 30007
KindProfileBadges int = 30008
KindBadgeDefinition int = 30009
KindInterestSets int = 30015
KindStallDefinition int = 30017
KindProductDefinition int = 30018
KindMarketplaceUI int = 30019
KindProductSoldAsAuction int = 30020
KindArticle int = 30023
KindDraftArticle int = 30024
KindEmojiSets int = 30030
KindModularArticleHeader int = 30040
KindModularArticleContent int = 30041
KindReleaseArtifactSets int = 30063
KindApplicationSpecificData int = 30078
KindLiveEvent int = 30311
KindUserStatuses int = 30315
KindClassifiedListing int = 30402
KindDraftClassifiedListing int = 30403
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
KindDateCalendarEvent int = 31922
KindTimeCalendarEvent int = 31923
KindCalendar int = 31924
KindCalendarEventRSVP int = 31925
KindHandlerRecommendation int = 31989
KindHandlerInformation int = 31990
KindVideoEvent int = 34235
KindShortVideoEvent int = 34236
KindVideoViewEvent int = 34237
KindCommunityDefinition int = 34550
)
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,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]
}
}