From 7e04bbb4b86b514509c1e299c8848780d3804e19 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 10 Mar 2025 02:35:02 -0300 Subject: [PATCH] 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 --- nip10/nip10.go | 29 ++++++++++++++-------- nip19/pointer.go | 9 +++++-- nip22/nip22.go | 28 ++++++++++++++++----- nip27/references.go | 53 +++++++++++++++++++++------------------- nip27/references_test.go | 4 +-- pointers.go | 27 +++++++++++++++++--- sdk/thread.go | 23 ----------------- sdk/tracker.go | 25 +++++++++++++------ 8 files changed, 119 insertions(+), 79 deletions(-) delete mode 100644 sdk/thread.go diff --git a/nip10/nip10.go b/nip10/nip10.go index d27ef02..7bc52c4 100644 --- a/nip10/nip10.go +++ b/nip10/nip10.go @@ -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 we reached this point and we have at least one "e" we'll use that (the last) - return lastE + if lastE != nil { + // if we reached this point and we have at least one "e" we'll use that (the last) + // (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 } diff --git a/nip19/pointer.go b/nip19/pointer.go index ef400aa..89abcba 100644 --- a/nip19/pointer.go +++ b/nip19/pointer.go @@ -9,8 +9,13 @@ import ( func EncodePointer(pointer nostr.Pointer) string { switch v := pointer.(type) { case nostr.ProfilePointer: - res, _ := EncodeProfile(v.PublicKey, v.Relays) - return res + 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 diff --git a/nip22/nip22.go b/nip22/nip22.go index 863d0ef..4d9a4f3 100644 --- a/nip22/nip22.go +++ b/nip22/nip22.go @@ -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 diff --git a/nip27/references.go b/nip27/references.go index d48b781..eeeb898 100644 --- a/nip27/references.go +++ b/nip27/references.go @@ -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 } } diff --git a/nip27/references_test.go b/nip27/references_test.go index 8e70c1f..b720ac9 100644 --- a/nip27/references_test.go +++ b/nip27/references_test.go @@ -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: "", diff --git a/pointers.go b/pointers.go index 3458c06..a13d010 100644 --- a/pointers.go +++ b/pointers.go @@ -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} +} diff --git a/sdk/thread.go b/sdk/thread.go deleted file mode 100644 index 20a4590..0000000 --- a/sdk/thread.go +++ /dev/null @@ -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) - } -} diff --git a/sdk/tracker.go b/sdk/tracker.go index a6da6b6..d81410c 100644 --- a/sdk/tracker.go +++ b/sdk/tracker.go @@ -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) } } }