mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-07-03 03:55:51 +02:00
nip27 parsing improved (and with nip08 support removed) in its own package.
This commit is contained in:
78
nip27/references.go
Normal file
78
nip27/references.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package nip27
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reference struct {
|
||||||
|
Text string
|
||||||
|
Start int
|
||||||
|
End int
|
||||||
|
Profile *nostr.ProfilePointer
|
||||||
|
Event *nostr.EventPointer
|
||||||
|
Entity *nostr.EntityPointer
|
||||||
|
}
|
||||||
|
|
||||||
|
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b`)
|
||||||
|
|
||||||
|
func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
|
||||||
|
return func(yield func(Reference) bool) {
|
||||||
|
for _, ref := range mentionRegex.FindAllStringSubmatchIndex(evt.Content, -1) {
|
||||||
|
reference := Reference{
|
||||||
|
Text: evt.Content[ref[0]:ref[1]],
|
||||||
|
Start: ref[0],
|
||||||
|
End: ref[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
nip19code := evt.Content[ref[2]:ref[3]]
|
||||||
|
|
||||||
|
if prefix, data, err := nip19.Decode(nip19code); err == nil {
|
||||||
|
switch prefix {
|
||||||
|
case "npub":
|
||||||
|
reference.Profile = &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]}
|
||||||
|
}
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
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{}}
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(reference) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
nip27/references_test.go
Normal file
46
nip27/references_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package nip27
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseReferences(t *testing.T) {
|
||||||
|
evt := nostr.Event{
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"p", "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393", "wss://xawr.com"},
|
||||||
|
{"e", "a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33", "wss://other.com", "reply"},
|
||||||
|
{"e", "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393", "wss://nasdj.com"},
|
||||||
|
},
|
||||||
|
Content: "hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!",
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []Reference{
|
||||||
|
{
|
||||||
|
Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg",
|
||||||
|
Start: 7,
|
||||||
|
End: 83,
|
||||||
|
Profile: &nostr.ProfilePointer{
|
||||||
|
PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
|
||||||
|
Relays: []string{"wss://xawr.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4",
|
||||||
|
Start: 90,
|
||||||
|
End: 164,
|
||||||
|
Event: &nostr.EventPointer{
|
||||||
|
ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
|
||||||
|
Relays: []string{"wss://nasdj.com"},
|
||||||
|
Author: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := slices.Collect(ParseReferences(evt))
|
||||||
|
|
||||||
|
require.EqualValues(t, expected, got)
|
||||||
|
}
|
@ -1,108 +0,0 @@
|
|||||||
package sdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"iter"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Reference struct {
|
|
||||||
Text string
|
|
||||||
Start int
|
|
||||||
End int
|
|
||||||
Profile *nostr.ProfilePointer
|
|
||||||
Event *nostr.EventPointer
|
|
||||||
Entity *nostr.EntityPointer
|
|
||||||
}
|
|
||||||
|
|
||||||
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]`)
|
|
||||||
|
|
||||||
// ParseReferences parses both NIP-08 and NIP-27 references in a single unifying interface.
|
|
||||||
func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
|
|
||||||
return func(yield func(Reference) bool) {
|
|
||||||
for _, ref := range mentionRegex.FindAllStringSubmatchIndex(evt.Content, -1) {
|
|
||||||
reference := Reference{
|
|
||||||
Text: evt.Content[ref[0]:ref[1]],
|
|
||||||
Start: ref[0],
|
|
||||||
End: ref[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref[6] == -1 {
|
|
||||||
// didn't find a NIP-10 #[0] reference, so it's a NIP-27 mention
|
|
||||||
nip19code := evt.Content[ref[2]:ref[3]]
|
|
||||||
|
|
||||||
if prefix, data, err := nip19.Decode(nip19code); err == nil {
|
|
||||||
switch prefix {
|
|
||||||
case "npub":
|
|
||||||
reference.Profile = &nostr.ProfilePointer{
|
|
||||||
PublicKey: data.(string), Relays: []string{},
|
|
||||||
}
|
|
||||||
case "nprofile":
|
|
||||||
pp := data.(nostr.ProfilePointer)
|
|
||||||
reference.Profile = &pp
|
|
||||||
case "note":
|
|
||||||
reference.Event = &nostr.EventPointer{ID: data.(string), Relays: []string{}}
|
|
||||||
case "nevent":
|
|
||||||
evp := data.(nostr.EventPointer)
|
|
||||||
reference.Event = &evp
|
|
||||||
case "naddr":
|
|
||||||
addr := data.(nostr.EntityPointer)
|
|
||||||
reference.Entity = &addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// it's a NIP-10 mention.
|
|
||||||
// parse the number, get data from event tags.
|
|
||||||
n := evt.Content[ref[6]:ref[7]]
|
|
||||||
idx, err := strconv.Atoi(n)
|
|
||||||
if err != nil || len(evt.Tags) <= idx {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tag := evt.Tags[idx]; tag != nil && len(tag) >= 2 {
|
|
||||||
switch tag[0] {
|
|
||||||
case "p":
|
|
||||||
relays := make([]string, 0, 1)
|
|
||||||
if len(tag) > 2 && tag[2] != "" {
|
|
||||||
relays = append(relays, tag[2])
|
|
||||||
}
|
|
||||||
reference.Profile = &nostr.ProfilePointer{
|
|
||||||
PublicKey: tag[1],
|
|
||||||
Relays: relays,
|
|
||||||
}
|
|
||||||
case "e":
|
|
||||||
relays := make([]string, 0, 1)
|
|
||||||
if len(tag) > 2 && tag[2] != "" {
|
|
||||||
relays = append(relays, tag[2])
|
|
||||||
}
|
|
||||||
reference.Event = &nostr.EventPointer{
|
|
||||||
ID: tag[1],
|
|
||||||
Relays: relays,
|
|
||||||
}
|
|
||||||
case "a":
|
|
||||||
if parts := strings.Split(tag[1], ":"); len(parts) == 3 {
|
|
||||||
kind, _ := strconv.Atoi(parts[0])
|
|
||||||
relays := make([]string, 0, 1)
|
|
||||||
if len(tag) > 2 && tag[2] != "" {
|
|
||||||
relays = append(relays, tag[2])
|
|
||||||
}
|
|
||||||
reference.Entity = &nostr.EntityPointer{
|
|
||||||
Identifier: parts[2],
|
|
||||||
PublicKey: parts[1],
|
|
||||||
Kind: kind,
|
|
||||||
Relays: relays,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !yield(reference) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
package sdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseReferences(t *testing.T) {
|
|
||||||
evt := nostr.Event{
|
|
||||||
Tags: nostr.Tags{
|
|
||||||
{"p", "c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8", "wss://nostr.com"},
|
|
||||||
{"e", "a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33", "wss://other.com", "reply"},
|
|
||||||
{"e", "31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8", ""},
|
|
||||||
},
|
|
||||||
Content: "hello #[0], have you seen #[2]? it was made by nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg on nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4! broken #[3]",
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []Reference{
|
|
||||||
{
|
|
||||||
Text: "#[0]",
|
|
||||||
Start: 6,
|
|
||||||
End: 10,
|
|
||||||
Profile: &nostr.ProfilePointer{
|
|
||||||
PublicKey: "c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8",
|
|
||||||
Relays: []string{"wss://nostr.com"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "#[2]",
|
|
||||||
Start: 26,
|
|
||||||
End: 30,
|
|
||||||
Event: &nostr.EventPointer{
|
|
||||||
ID: "31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8",
|
|
||||||
Relays: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg",
|
|
||||||
Start: 47,
|
|
||||||
End: 123,
|
|
||||||
Profile: &nostr.ProfilePointer{
|
|
||||||
PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
|
|
||||||
Relays: []string{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4",
|
|
||||||
Start: 127,
|
|
||||||
End: 201,
|
|
||||||
Event: &nostr.EventPointer{
|
|
||||||
ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
|
|
||||||
Relays: []string{},
|
|
||||||
Author: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
got := slices.Collect(ParseReferences(evt))
|
|
||||||
|
|
||||||
if len(got) != len(expected) {
|
|
||||||
t.Errorf("got %d references, expected %d", len(got), len(expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, g := range got {
|
|
||||||
e := expected[i]
|
|
||||||
if g.Text != e.Text {
|
|
||||||
t.Errorf("%d: got text %s, expected %s", i, g.Text, e.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.Start != e.Start {
|
|
||||||
t.Errorf("%d: got start %d, expected %d", i, g.Start, e.Start)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.End != e.End {
|
|
||||||
t.Errorf("%d: got end %d, expected %d", i, g.End, e.End)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g.Entity == nil && e.Entity != nil) ||
|
|
||||||
(g.Event == nil && e.Event != nil) ||
|
|
||||||
(g.Profile == nil && e.Profile != nil) {
|
|
||||||
t.Errorf("%d: got some unexpected nil", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.Profile != nil && (g.Profile.PublicKey != e.Profile.PublicKey ||
|
|
||||||
len(g.Profile.Relays) != len(e.Profile.Relays) ||
|
|
||||||
(len(g.Profile.Relays) > 0 && g.Profile.Relays[0] != e.Profile.Relays[0])) {
|
|
||||||
t.Errorf("%d: profile value is wrong", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.Event != nil && (g.Event.ID != e.Event.ID ||
|
|
||||||
g.Event.Author != e.Event.Author ||
|
|
||||||
len(g.Event.Relays) != len(e.Event.Relays) ||
|
|
||||||
(len(g.Event.Relays) > 0 && g.Event.Relays[0] != e.Event.Relays[0])) {
|
|
||||||
fmt.Println(g.Event.ID, g.Event.Relays, len(g.Event.Relays), g.Event.Relays[0] == "")
|
|
||||||
fmt.Println(e.Event.Relays, len(e.Event.Relays))
|
|
||||||
t.Errorf("%d: event value is wrong", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.Entity != nil && (g.Entity.PublicKey != e.Entity.PublicKey ||
|
|
||||||
g.Entity.Identifier != e.Entity.Identifier ||
|
|
||||||
g.Entity.Kind != e.Entity.Kind ||
|
|
||||||
len(g.Entity.Relays) != len(g.Entity.Relays)) {
|
|
||||||
t.Errorf("%d: entity value is wrong", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip27"
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ func (sys *System) trackEventHints(ie nostr.RelayEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ref := range ParseReferences(*ie.Event) {
|
for ref := range nip27.ParseReferences(*ie.Event) {
|
||||||
if ref.Profile != nil {
|
if ref.Profile != nil {
|
||||||
for _, relay := range ref.Profile.Relays {
|
for _, relay := range ref.Profile.Relays {
|
||||||
if IsVirtualRelay(relay) {
|
if IsVirtualRelay(relay) {
|
||||||
|
Reference in New Issue
Block a user