nip5A: nsites.
This commit is contained in:
@@ -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