breaking pointer mess

- ExternalPointer (?)
- nip27, nip22 and nip10 functions to return pointers
- get rid of sdk/thread helpers that were just a thin layer over nip10 and nip22
This commit is contained in:
fiatjaf 2025-03-10 02:35:02 -03:00
parent f575f63f6c
commit 7e04bbb4b8
8 changed files with 119 additions and 79 deletions

View File

@ -36,11 +36,12 @@ func GetImmediateParent(tags nostr.Tags) *nostr.EventPointer {
if len(tag) >= 4 {
if tag[3] == "reply" {
return &tag
parent = tag
break
}
if tag[3] == "root" {
if tag[3] == "parent" {
// will be used as our first fallback
root = &tag
parent = tag
continue
}
if tag[3] == "mention" {
@ -49,15 +50,23 @@ func GetImmediateParent(tags nostr.Tags) *nostr.EventPointer {
}
}
lastE = &tag // will be used as our second fallback (clients that don't add markers)
lastE = tag // will be used as our second fallback (clients that don't add markers)
}
// if we reached this point we don't have a "reply", but if we have a "root"
// that means this event is a direct reply to the root
if root != nil {
return root
// if we reached this point we don't have a "reply", but if we have a "parent"
// that means this event is a direct reply to the parent
if parent != nil {
p, _ := nostr.EventPointerFromTag(parent)
return &p
}
if lastE != nil {
// if we reached this point and we have at least one "e" we'll use that (the last)
return lastE
// (we don't bother looking for relay or author hints because these clients don't add these anyway)
return &nostr.EventPointer{
ID: lastE[1],
}
}
return nil
}

View File

@ -9,8 +9,13 @@ import (
func EncodePointer(pointer nostr.Pointer) string {
switch v := pointer.(type) {
case nostr.ProfilePointer:
if v.Relays == nil {
res, _ := EncodePublicKey(v.PublicKey)
return res
} else {
res, _ := EncodeProfile(v.PublicKey, v.Relays)
return res
}
case nostr.EventPointer:
res, _ := EncodeEvent(v.ID, v.Relays, v.Author)
return res

View File

@ -2,25 +2,41 @@ package nip22
import "github.com/nbd-wtf/go-nostr"
func GetThreadRoot(tags nostr.Tags) *nostr.Tag {
func GetThreadRoot(tags nostr.Tags) nostr.Pointer {
for _, tag := range tags {
if len(tag) < 2 {
continue
}
if tag[0] == "E" || tag[0] == "A" || tag[0] == "I" {
return &tag
switch tag[0] {
case "E":
ep, _ := nostr.EventPointerFromTag(tag)
return ep
case "A":
ep, _ := nostr.EntityPointerFromTag(tag)
return ep
case "I":
ep, _ := nostr.ExternalPointerFromTag(tag)
return ep
}
}
return nil
}
func GetImmediateReply(tags nostr.Tags) *nostr.Tag {
func GetImmediateParent(tags nostr.Tags) nostr.Pointer {
for _, tag := range tags {
if len(tag) < 2 {
continue
}
if tag[0] == "e" || tag[0] == "a" || tag[0] == "i" {
return &tag
switch tag[0] {
case "e":
ep, _ := nostr.EventPointerFromTag(tag)
return ep
case "a":
ep, _ := nostr.EntityPointerFromTag(tag)
return ep
case "i":
ep, _ := nostr.ExternalPointerFromTag(tag)
return ep
}
}
return nil

View File

@ -12,9 +12,7 @@ type Reference struct {
Text string
Start int
End int
Profile *nostr.ProfilePointer
Event *nostr.EventPointer
Entity *nostr.EntityPointer
Pointer nostr.Pointer
}
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b`)
@ -33,40 +31,45 @@ func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
if prefix, data, err := nip19.Decode(nip19code); err == nil {
switch prefix {
case "npub":
reference.Profile = &nostr.ProfilePointer{
pointer := &nostr.ProfilePointer{
PublicKey: data.(string), Relays: []string{},
}
tag := evt.Tags.GetFirst([]string{"p", reference.Profile.PublicKey})
if tag != nil && len(*tag) >= 3 {
reference.Profile.Relays = []string{(*tag)[2]}
tag := evt.Tags.FindWithValue("p", pointer.PublicKey)
if tag != nil && len(tag) >= 3 {
pointer.Relays = []string{tag[2]}
}
if nostr.IsValidPublicKey(pointer.PublicKey) {
reference.Pointer = pointer
}
case "nprofile":
pp := data.(nostr.ProfilePointer)
reference.Profile = &pp
tag := evt.Tags.GetFirst([]string{"p", reference.Profile.PublicKey})
if tag != nil && len(*tag) >= 3 {
reference.Profile.Relays = append(reference.Profile.Relays, (*tag)[2])
pointer := data.(nostr.ProfilePointer)
tag := evt.Tags.FindWithValue("p", pointer.PublicKey)
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
}
if nostr.IsValidPublicKey(pointer.PublicKey) {
reference.Pointer = pointer
}
case "note":
// we don't even bother here because people using note1 codes aren't including relay hints anyway
reference.Event = &nostr.EventPointer{ID: data.(string), Relays: []string{}}
reference.Pointer = &nostr.EventPointer{ID: data.(string), Relays: nil}
case "nevent":
evp := data.(nostr.EventPointer)
reference.Event = &evp
tag := evt.Tags.GetFirst([]string{"e", reference.Event.ID})
if tag != nil && len(*tag) >= 3 {
reference.Event.Relays = append(reference.Event.Relays, (*tag)[2])
if reference.Event.Author == "" && len(*tag) >= 5 {
reference.Event.Author = (*tag)[4]
pointer := data.(nostr.EventPointer)
tag := evt.Tags.FindWithValue("e", pointer.ID)
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
if pointer.Author == "" && len(tag) >= 5 && nostr.IsValidPublicKey(tag[4]) {
pointer.Author = tag[4]
}
}
reference.Pointer = pointer
case "naddr":
addr := data.(nostr.EntityPointer)
reference.Entity = &addr
tag := evt.Tags.GetFirst([]string{"a", reference.Entity.AsTagReference()})
if tag != nil && len(*tag) >= 3 {
reference.Entity.Relays = append(reference.Entity.Relays, (*tag)[2])
pointer := data.(nostr.EntityPointer)
tag := evt.Tags.FindWithValue("a", pointer.AsTagReference())
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
}
reference.Pointer = pointer
}
}

View File

@ -23,7 +23,7 @@ func TestParseReferences(t *testing.T) {
Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg",
Start: 7,
End: 83,
Profile: &nostr.ProfilePointer{
Pointer: nostr.ProfilePointer{
PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{"wss://xawr.com"},
},
@ -32,7 +32,7 @@ func TestParseReferences(t *testing.T) {
Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4",
Start: 90,
End: 164,
Event: &nostr.EventPointer{
Pointer: nostr.EventPointer{
ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{"wss://nasdj.com"},
Author: "",

View File

@ -26,6 +26,7 @@ var (
_ Pointer = (*ProfilePointer)(nil)
_ Pointer = (*EventPointer)(nil)
_ Pointer = (*EntityPointer)(nil)
_ Pointer = (*ExternalPointer)(nil)
)
// ProfilePointer represents a pointer to a Nostr profile.
@ -36,7 +37,7 @@ type ProfilePointer struct {
// ProfilePointerFromTag creates a ProfilePointer from a "p" tag (but it doesn't have to be necessarily a "p" tag, could be something else).
func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
pk := (refTag)[1]
pk := refTag[1]
if !IsValidPublicKey(pk) {
return ProfilePointer{}, fmt.Errorf("invalid pubkey '%s'", pk)
}
@ -74,7 +75,7 @@ type EventPointer struct {
// EventPointerFromTag creates an EventPointer from an "e" tag (but it could be other tag name, it isn't checked).
func EventPointerFromTag(refTag Tag) (EventPointer, error) {
id := (refTag)[1]
id := refTag[1]
if !IsValid32ByteHex(id) {
return EventPointer{}, fmt.Errorf("invalid id '%s'", id)
}
@ -86,8 +87,10 @@ func EventPointerFromTag(refTag Tag) (EventPointer, error) {
if relay := (refTag)[2]; IsValidRelayURL(relay) {
pointer.Relays = []string{relay}
}
if len(refTag) > 3 && IsValidPublicKey((refTag)[3]) {
if len(refTag) > 3 && IsValidPublicKey(refTag[3]) {
pointer.Author = (refTag)[3]
} else if len(refTag) > 4 && IsValidPublicKey(refTag[4]) {
pointer.Author = (refTag)[4]
}
}
return pointer, nil
@ -171,3 +174,21 @@ func (ep EntityPointer) AsTag() Tag {
}
return Tag{"a", ep.AsTagReference()}
}
// ExternalPointer represents a pointer to a Nostr profile.
type ExternalPointer struct {
Thing string
}
// ExternalPointerFromTag creates a ExternalPointer from an "i" tag
func ExternalPointerFromTag(refTag Tag) (ExternalPointer, error) {
return ExternalPointer{refTag[1]}, nil
}
func (ep ExternalPointer) MatchesEvent(_ Event) bool { return false }
func (ep ExternalPointer) AsTagReference() string { return ep.Thing }
func (ep ExternalPointer) AsFilter() Filter { return Filter{} }
func (ep ExternalPointer) AsTag() Tag {
return Tag{"i", ep.Thing}
}

View File

@ -1,23 +0,0 @@
package sdk
import (
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip10"
"github.com/nbd-wtf/go-nostr/nip22"
)
func GetThreadRoot(evt *nostr.Event) *nostr.Tag {
if evt.Kind == nostr.KindComment {
return nip22.GetThreadRoot(evt.Tags)
} else {
return nip10.GetThreadRoot(evt.Tags)
}
}
func GetImmediateReply(evt *nostr.Event) *nostr.Tag {
if evt.Kind == nostr.KindComment {
return nip22.GetImmediateReply(evt.Tags)
} else {
return nip10.GetImmediateReply(evt.Tags)
}
}

View File

@ -106,27 +106,36 @@ func (sys *System) trackEventHints(ie nostr.RelayEvent) {
}
for ref := range nip27.ParseReferences(*ie.Event) {
if ref.Profile != nil {
for _, relay := range ref.Profile.Relays {
switch pointer := ref.Pointer.(type) {
case nostr.ProfilePointer:
for _, relay := range pointer.Relays {
if IsVirtualRelay(relay) {
continue
}
if p, err := url.Parse(relay); err != nil || (p.Scheme != "wss" && p.Scheme != "ws") {
continue
}
if nostr.IsValidPublicKey(ref.Profile.PublicKey) {
sys.Hints.Save(ref.Profile.PublicKey, nostr.NormalizeURL(relay), hints.LastInHint, ie.CreatedAt)
sys.Hints.Save(pointer.PublicKey, nostr.NormalizeURL(relay), hints.LastInHint, ie.CreatedAt)
}
}
} else if ref.Event != nil && nostr.IsValidPublicKey(ref.Event.Author) {
for _, relay := range ref.Event.Relays {
case nostr.EventPointer:
for _, relay := range pointer.Relays {
if IsVirtualRelay(relay) {
continue
}
if p, err := url.Parse(relay); err != nil || (p.Scheme != "wss" && p.Scheme != "ws") {
continue
}
sys.Hints.Save(ref.Event.Author, nostr.NormalizeURL(relay), hints.LastInHint, ie.CreatedAt)
sys.Hints.Save(pointer.Author, nostr.NormalizeURL(relay), hints.LastInHint, ie.CreatedAt)
}
case nostr.EntityPointer:
for _, relay := range pointer.Relays {
if IsVirtualRelay(relay) {
continue
}
if p, err := url.Parse(relay); err != nil || (p.Scheme != "wss" && p.Scheme != "ws") {
continue
}
sys.Hints.Save(pointer.PublicKey, nostr.NormalizeURL(relay), hints.LastInHint, ie.CreatedAt)
}
}
}