sdk.ParseReferences()

This commit is contained in:
fiatjaf 2023-03-27 08:47:59 -03:00
parent d3a1624880
commit b0031bfd86
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
2 changed files with 208 additions and 0 deletions

116
sdk/references.go Normal file
View File

@ -0,0 +1,116 @@
package sdk
import (
"regexp"
"strconv"
"strings"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
type Reference struct {
Text string
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) []*Reference {
var references []*Reference
for _, ref := range mentionRegex.FindAllStringSubmatch(evt.Content, -1) {
if ref[2] != "" {
// it's a NIP-27 mention
if prefix, data, err := nip19.Decode(ref[1]); err == nil {
switch prefix {
case "npub":
references = append(references, &Reference{
Text: ref[0],
Profile: &nostr.ProfilePointer{
PublicKey: data.(string), Relays: []string{},
},
})
case "nprofile":
pp := data.(nostr.ProfilePointer)
references = append(references, &Reference{
Text: ref[0],
Profile: &pp,
})
case "note":
references = append(references, &Reference{
Text: ref[0],
Event: &nostr.EventPointer{ID: data.(string), Relays: []string{}},
})
case "nevent":
evp := data.(nostr.EventPointer)
references = append(references, &Reference{
Text: ref[0],
Event: &evp,
})
case "naddr":
addr := data.(nostr.EntityPointer)
references = append(references, &Reference{
Text: ref[0],
Entity: &addr,
})
}
}
} else if ref[3] != "" {
// it's a NIP-10 mention
idx, err := strconv.Atoi(ref[3])
if err != nil || len(evt.Tags) <= idx {
continue
}
if tag := evt.Tags[idx]; tag != nil {
switch tag[0] {
case "p":
relays := make([]string, 0, 1)
if len(tag) > 2 && tag[2] != "" {
relays = append(relays, tag[2])
}
references = append(references, &Reference{
Text: ref[0],
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])
}
references = append(references, &Reference{
Text: ref[0],
Event: &nostr.EventPointer{
ID: tag[1],
Relays: relays,
},
})
case "a":
if parts := strings.Split(ref[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])
}
references = append(references, &Reference{
Text: ref[0],
Entity: &nostr.EntityPointer{
Identifier: parts[2],
PublicKey: parts[1],
Kind: kind,
Relays: relays,
},
})
}
}
}
}
}
return references
}

92
sdk/references_test.go Normal file
View File

@ -0,0 +1,92 @@
package sdk
import (
"fmt"
"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]",
Profile: &nostr.ProfilePointer{
PublicKey: "c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8",
Relays: []string{"wss://nostr.com"},
},
},
{
Text: "#[2]",
Event: &nostr.EventPointer{
ID: "31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8",
Relays: []string{},
},
},
{
Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg",
Profile: &nostr.ProfilePointer{
PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{},
},
},
{
Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4",
Event: &nostr.EventPointer{
ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{},
Author: "",
},
},
}
got := 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.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)
}
}
}