nip5A: nsites.
This commit is contained in:
@@ -274,6 +274,10 @@ func (kind Kind) Name() string {
|
|||||||
return "VideoViewEvent"
|
return "VideoViewEvent"
|
||||||
case KindCommunityDefinition:
|
case KindCommunityDefinition:
|
||||||
return "CommunityDefinition"
|
return "CommunityDefinition"
|
||||||
|
case KindNsiteRoot:
|
||||||
|
return "NsiteRoot"
|
||||||
|
case KindNsiteNamed:
|
||||||
|
return "NsiteNamed"
|
||||||
}
|
}
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
@@ -360,6 +364,7 @@ const (
|
|||||||
KindGoodWikiAuthorList Kind = 10101
|
KindGoodWikiAuthorList Kind = 10101
|
||||||
KindGoodWikiRelayList Kind = 10102
|
KindGoodWikiRelayList Kind = 10102
|
||||||
KindNWCWalletInfo Kind = 13194
|
KindNWCWalletInfo Kind = 13194
|
||||||
|
KindNsiteRoot Kind = 15128
|
||||||
KindLightningPubRPC Kind = 21000
|
KindLightningPubRPC Kind = 21000
|
||||||
KindClientAuthentication Kind = 22242
|
KindClientAuthentication Kind = 22242
|
||||||
KindNWCWalletRequest Kind = 23194
|
KindNWCWalletRequest Kind = 23194
|
||||||
@@ -394,6 +399,7 @@ const (
|
|||||||
KindDraftClassifiedListing Kind = 30403
|
KindDraftClassifiedListing Kind = 30403
|
||||||
KindRepositoryAnnouncement Kind = 30617
|
KindRepositoryAnnouncement Kind = 30617
|
||||||
KindRepositoryState Kind = 30618
|
KindRepositoryState Kind = 30618
|
||||||
|
KindNsiteNamed Kind = 35128
|
||||||
KindSimpleGroupMetadata Kind = 39000
|
KindSimpleGroupMetadata Kind = 39000
|
||||||
KindSimpleGroupAdmins Kind = 39001
|
KindSimpleGroupAdmins Kind = 39001
|
||||||
KindSimpleGroupMembers Kind = 39002
|
KindSimpleGroupMembers Kind = 39002
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package nip5a
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NormalizePath(p string) string {
|
||||||
|
if !strings.HasSuffix(p, ".html") && !strings.HasSuffix(p, "/") {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(p, "/") {
|
||||||
|
return p + "index.html"
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func PubKeyFromBase36(value string) (nostr.PubKey, error) {
|
||||||
|
bi, ok := new(big.Int).SetString(value, 36)
|
||||||
|
if !ok {
|
||||||
|
return nostr.ZeroPK, fmt.Errorf("invalid base36 pubkey")
|
||||||
|
}
|
||||||
|
buf := bi.Bytes()
|
||||||
|
if len(buf) > 32 {
|
||||||
|
return nostr.ZeroPK, fmt.Errorf("base36 pubkey too long")
|
||||||
|
}
|
||||||
|
var pk nostr.PubKey
|
||||||
|
copy(pk[32-len(buf):], buf)
|
||||||
|
return pk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PubKeyToBase36(pubkey nostr.PubKey) string {
|
||||||
|
value := new(big.Int).SetBytes(pubkey[:]).Text(36)
|
||||||
|
return strings.Repeat("0", 50-len(value)) + value
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
package nip5a
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/nip19"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SiteManifest struct {
|
||||||
|
Event *nostr.Event
|
||||||
|
Pubkey nostr.PubKey
|
||||||
|
Root bool
|
||||||
|
Identifier string
|
||||||
|
Paths map[string][32]byte
|
||||||
|
Servers []string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSiteManifest(event *nostr.Event) (*SiteManifest, error) {
|
||||||
|
sm := &SiteManifest{Event: event}
|
||||||
|
|
||||||
|
switch event.Kind {
|
||||||
|
case nostr.KindNsiteRoot:
|
||||||
|
sm.Root = true
|
||||||
|
case nostr.KindNsiteNamed:
|
||||||
|
sm.Root = false
|
||||||
|
for _, tag := range event.Tags {
|
||||||
|
if len(tag) >= 2 && tag[0] == "d" {
|
||||||
|
sm.Identifier = tag[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sm.Identifier == "" {
|
||||||
|
return nil, fmt.Errorf("named site manifest missing d tag")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid site manifest kind: %d", event.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
sm.Pubkey = event.PubKey
|
||||||
|
sm.Paths = make(map[string][32]byte, len(event.Tags))
|
||||||
|
|
||||||
|
for _, tag := range event.Tags {
|
||||||
|
if len(tag) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch tag[0] {
|
||||||
|
case "path":
|
||||||
|
var hash [32]byte
|
||||||
|
if len(tag[2]) != 64 {
|
||||||
|
return nil, fmt.Errorf("invalid hash '%s' for path '%s'", tag[2], tag[1])
|
||||||
|
}
|
||||||
|
if _, err := hex.Decode(hash[:], unsafe.Slice(unsafe.StringData(tag[2]), 64)); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid hash '%s' for path '%s'", tag[2], tag[1])
|
||||||
|
}
|
||||||
|
sm.Paths[NormalizePath(tag[1])] = hash
|
||||||
|
case "server":
|
||||||
|
sm.Servers = append(sm.Servers, tag[1])
|
||||||
|
case "title":
|
||||||
|
sm.Title = tag[1]
|
||||||
|
case "description":
|
||||||
|
sm.Description = tag[1]
|
||||||
|
case "source":
|
||||||
|
sm.Source = tag[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SiteManifest) ToEvent(pubkey nostr.PubKey) *nostr.Event {
|
||||||
|
event := &nostr.Event{
|
||||||
|
PubKey: pubkey,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if sm.Root {
|
||||||
|
event.Kind = nostr.KindNsiteRoot
|
||||||
|
} else {
|
||||||
|
event.Kind = nostr.KindNsiteNamed
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"d", sm.Identifier})
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, hash := range sm.Paths {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"path", NormalizePath(path), hex.EncodeToString(hash[:])})
|
||||||
|
}
|
||||||
|
for _, s := range sm.Servers {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"server", s})
|
||||||
|
}
|
||||||
|
if sm.Title != "" {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"title", sm.Title})
|
||||||
|
}
|
||||||
|
if sm.Description != "" {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"description", sm.Description})
|
||||||
|
}
|
||||||
|
if sm.Source != "" {
|
||||||
|
event.Tags = append(event.Tags, nostr.Tag{"source", sm.Source})
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:inline
|
||||||
|
func (sm *SiteManifest) GetHashForPath(path string) ([32]byte, bool) {
|
||||||
|
path = NormalizePath(path)
|
||||||
|
hash, ok := sm.Paths[path]
|
||||||
|
return hash, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeSiteURL(label string) (pubkey nostr.PubKey, identifier string, isRoot bool, err error) {
|
||||||
|
label, _, _ = strings.Cut(label, ".")
|
||||||
|
|
||||||
|
if strings.HasPrefix(label, "npub1") {
|
||||||
|
_, value, err := nip19.Decode(label)
|
||||||
|
if err != nil {
|
||||||
|
return nostr.ZeroPK, "", false, err
|
||||||
|
}
|
||||||
|
return value.(nostr.PubKey), "", true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(label) < 51 || len(label) > 63 || strings.HasSuffix(label, "-") {
|
||||||
|
return nostr.ZeroPK, "", false, fmt.Errorf("invalid site label format")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeyB36 := label[:50]
|
||||||
|
dTag := label[50:]
|
||||||
|
if !regexp.MustCompile(`^[a-z0-9-]{1,13}$`).MatchString(dTag) {
|
||||||
|
return nostr.ZeroPK, "", false, fmt.Errorf("invalid dtag format")
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := PubKeyFromBase36(pubkeyB36)
|
||||||
|
if err != nil {
|
||||||
|
return nostr.ZeroPK, "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pk, dTag, false, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user