From 8458e262918a5d6955016fd5d7fabc8f30be09e7 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 23 Nov 2025 06:46:51 -0300 Subject: [PATCH] nip10: fix parsing when the reply is an "a". fix and test nip10. --- nip10/nip10.go | 29 ++++++++------ nip10/nip10_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++++ pointers.go | 2 +- 3 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 nip10/nip10_test.go diff --git a/nip10/nip10.go b/nip10/nip10.go index e5dbb4e..39c3898 100644 --- a/nip10/nip10.go +++ b/nip10/nip10.go @@ -21,7 +21,7 @@ func GetThreadRoot(tags nostr.Tags) nostr.Pointer { func GetImmediateParent(tags nostr.Tags) nostr.Pointer { var parent nostr.Tag - var lastE nostr.Tag + var last nostr.Tag for i := 0; i <= len(tags)-1; i++ { tag := tags[i] @@ -38,32 +38,37 @@ func GetImmediateParent(tags nostr.Tags) nostr.Pointer { parent = tag break } - if tag[3] == "parent" { - // will be used as our first fallback - parent = tag - continue - } if tag[3] == "mention" { // this invalidates this tag as a second fallback mechanism (clients that don't add markers) continue } } - lastE = tag // will be used as our second fallback (clients that don't add markers) + last = 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 "parent" // that means this event is a direct reply to the parent if parent != nil { - p, _ := nostr.EventPointerFromTag(parent) - return p + if parent[0] == "e" { + p, _ := nostr.EventPointerFromTag(parent) + return p + } else { + a, _ := nostr.EntityPointerFromTag(parent) + return a + } } - if lastE != nil { + if last != 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) - p, _ := nostr.EventPointerFromTag(lastE) - return p + if last[0] == "e" { + p, _ := nostr.EventPointerFromTag(last) + return p + } else { + a, _ := nostr.EntityPointerFromTag(last) + return a + } } return nil diff --git a/nip10/nip10_test.go b/nip10/nip10_test.go new file mode 100644 index 0000000..7f115a0 --- /dev/null +++ b/nip10/nip10_test.go @@ -0,0 +1,92 @@ +package nip10 + +import ( + "testing" + + "fiatjaf.com/nostr" + "github.com/stretchr/testify/require" +) + +func TestGetThreadRoot(t *testing.T) { + event1 := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + event2 := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + + // test with root tag + tags := nostr.Tags{ + {"e", event1, "relay1", "author1", "root"}, + {"e", event2, "relay2", "author2", "reply"}, + } + root := GetThreadRoot(tags) + require.NotNil(t, root) + ep := root.(nostr.EventPointer) + require.Equal(t, event1, ep.ID.Hex()) + + // test fallback to first e tag + tags2 := nostr.Tags{ + {"e", event2, "relay2", "author2"}, + {"p", "pubkey1"}, + } + root2 := GetThreadRoot(tags2) + require.NotNil(t, root2) + ep2 := root2.(nostr.EventPointer) + require.Equal(t, event2, ep2.ID.Hex()) + + // test no e tags + tags3 := nostr.Tags{ + {"p", "pubkey1"}, + } + root3 := GetThreadRoot(tags3) + require.Nil(t, root3) +} + +func TestGetImmediateParent(t *testing.T) { + event1 := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + event2 := "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" + pubkey := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210" + + // test with reply tag + tags := nostr.Tags{ + {"e", event1, "relay1", "author1", "root"}, + {"e", event2, "relay2", "author2", "reply"}, + } + parent := GetImmediateParent(tags) + require.NotNil(t, parent) + ep := parent.(nostr.EventPointer) + require.Equal(t, event2, ep.ID.Hex()) + + // test with parent tag + tags2 := nostr.Tags{ + {"e", event1, "relay1", "author1"}, + {"e", event2, "relay2", "author2"}, + } + parent2 := GetImmediateParent(tags2) + require.NotNil(t, parent2) + ep2 := parent2.(nostr.EventPointer) + require.Equal(t, event2, ep2.ID.Hex()) + + // test with mention (should skip) + tags3 := nostr.Tags{ + {"e", event1, "relay1", "author1", "mention"}, + {"e", event2, "relay2", "author2"}, + } + parent3 := GetImmediateParent(tags3) + require.NotNil(t, parent3) + ep3 := parent3.(nostr.EventPointer) + require.Equal(t, event2, ep3.ID.Hex()) // last e + + // test with a tag + tags4 := nostr.Tags{ + {"a", "1:" + pubkey + ":d", "relay", "author", "reply"}, + } + parent4 := GetImmediateParent(tags4) + require.NotNil(t, parent4) + ap := parent4.(nostr.EntityPointer) + require.Equal(t, "1:"+pubkey+":d", ap.AsTagReference()) + + // test no valid tags + tags5 := nostr.Tags{ + {"p", "pubkey1"}, + } + parent5 := GetImmediateParent(tags5) + require.Nil(t, parent5) +} diff --git a/pointers.go b/pointers.go index a992086..11060d5 100644 --- a/pointers.go +++ b/pointers.go @@ -166,7 +166,7 @@ func (ep EntityPointer) MatchesEvent(evt Event) bool { } func (ep EntityPointer) AsTagReference() string { - return fmt.Sprintf("%d:%s:%s", ep.Kind, ep.PublicKey, ep.Identifier) + return fmt.Sprintf("%d:%s:%s", ep.Kind, ep.PublicKey.Hex(), ep.Identifier) } func (ep EntityPointer) AsFilter() Filter {