mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-06-30 10:30:44 +02:00
move ExternalPointer to nip73 and write nip27.Parse() that gets all the parts of the text including URLs, Nostr URIs and just raw text.
This commit is contained in:
@ -1,6 +1,9 @@
|
|||||||
package nip22
|
package nip22
|
||||||
|
|
||||||
import "github.com/nbd-wtf/go-nostr"
|
import (
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip73"
|
||||||
|
)
|
||||||
|
|
||||||
func GetThreadRoot(tags nostr.Tags) nostr.Pointer {
|
func GetThreadRoot(tags nostr.Tags) nostr.Pointer {
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
@ -15,7 +18,7 @@ func GetThreadRoot(tags nostr.Tags) nostr.Pointer {
|
|||||||
ep, _ := nostr.EntityPointerFromTag(tag)
|
ep, _ := nostr.EntityPointerFromTag(tag)
|
||||||
return ep
|
return ep
|
||||||
case "I":
|
case "I":
|
||||||
ep, _ := nostr.ExternalPointerFromTag(tag)
|
ep, _ := nip73.ExternalPointerFromTag(tag)
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +38,7 @@ func GetImmediateParent(tags nostr.Tags) nostr.Pointer {
|
|||||||
ep, _ := nostr.EntityPointerFromTag(tag)
|
ep, _ := nostr.EntityPointerFromTag(tag)
|
||||||
return ep
|
return ep
|
||||||
case "i":
|
case "i":
|
||||||
ep, _ := nostr.ExternalPointerFromTag(tag)
|
ep, _ := nip73.ExternalPointerFromTag(tag)
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
152
nip27/blocks.go
Normal file
152
nip27/blocks.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package nip27
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip73"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
Text string
|
||||||
|
Start int
|
||||||
|
Pointer nostr.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
noCharacter = regexp.MustCompile(`(?m)\W`)
|
||||||
|
noURLCharacter = regexp.MustCompile(`(?m)\W |\W$|$|,| `)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(content string) iter.Seq[Block] {
|
||||||
|
return func(yield func(Block) bool) {
|
||||||
|
max := len(content)
|
||||||
|
index := 0
|
||||||
|
prevIndex := 0
|
||||||
|
|
||||||
|
for index < max {
|
||||||
|
pu := strings.IndexRune(content[index:], ':')
|
||||||
|
if pu == -1 {
|
||||||
|
// reached end
|
||||||
|
break
|
||||||
|
}
|
||||||
|
u := pu + index
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case u >= 5 && content[u-5:u] == "nostr" && u+60 < max:
|
||||||
|
m := noCharacter.FindStringIndex(content[u+60:])
|
||||||
|
end := max
|
||||||
|
if m != nil {
|
||||||
|
end = u + 60 + m[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, data, err := nip19.Decode(content[u+1 : end])
|
||||||
|
if err != nil {
|
||||||
|
// ignore this, not a valid nostr uri
|
||||||
|
index = u + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointer nostr.Pointer
|
||||||
|
switch prefix {
|
||||||
|
case "npub":
|
||||||
|
pointer = nostr.ProfilePointer{PublicKey: data.(string)}
|
||||||
|
case "nprofile", "nevent", "naddr":
|
||||||
|
pointer = data.(nostr.Pointer)
|
||||||
|
case "note", "nsec":
|
||||||
|
fallthrough // I'm so cool
|
||||||
|
default:
|
||||||
|
// ignore this, treat it as not a valid uri
|
||||||
|
index = end + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevIndex != u-5 {
|
||||||
|
if !yield(Block{Text: content[prevIndex : u-5], Start: prevIndex}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(Block{Pointer: pointer, Text: content[u-5 : end], Start: u - 5}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index = end
|
||||||
|
prevIndex = index
|
||||||
|
continue
|
||||||
|
case (u >= 5 && content[u-5:u] == "https") || (u >= 4 && content[u-4:u] == "http"):
|
||||||
|
m := noURLCharacter.FindStringIndex(content[u+4:])
|
||||||
|
end := max
|
||||||
|
if m != nil {
|
||||||
|
end = u + 4 + m[0]
|
||||||
|
}
|
||||||
|
prefixLen := 4
|
||||||
|
if content[u-1] == 's' {
|
||||||
|
prefixLen = 5
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(content[u-prefixLen : end])
|
||||||
|
if err != nil || !strings.Contains(parsed.Host, ".") {
|
||||||
|
// ignore this, not a valid url
|
||||||
|
index = end + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevIndex != u-prefixLen {
|
||||||
|
if !yield(Block{Text: content[prevIndex : u-prefixLen], Start: prevIndex}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(Block{Pointer: nip73.ExternalPointer{Thing: content[u-prefixLen : end]}, Text: content[u-prefixLen : end], Start: u - prefixLen}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index = end
|
||||||
|
prevIndex = index
|
||||||
|
continue
|
||||||
|
case (u >= 3 && content[u-3:u] == "wss") || (u >= 2 && content[u-2:u] == "ws"):
|
||||||
|
m := noURLCharacter.FindStringIndex(content[u+4:])
|
||||||
|
end := max
|
||||||
|
if m != nil {
|
||||||
|
end = u + 4 + m[0]
|
||||||
|
}
|
||||||
|
prefixLen := 2
|
||||||
|
if content[u-1] == 's' {
|
||||||
|
prefixLen = 3
|
||||||
|
}
|
||||||
|
parsed, err := url.Parse(content[u-prefixLen : end])
|
||||||
|
if err != nil || !strings.Contains(parsed.Host, ".") {
|
||||||
|
// ignore this, not a valid url
|
||||||
|
index = end + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevIndex != u-prefixLen {
|
||||||
|
if !yield(Block{Text: content[prevIndex : u-prefixLen], Start: prevIndex}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(Block{Pointer: nip73.ExternalPointer{Thing: content[u-prefixLen : end]}, Text: content[u-prefixLen : end], Start: u - prefixLen}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index = end
|
||||||
|
prevIndex = index
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// ignore this, it is nothing
|
||||||
|
index = u + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevIndex != max {
|
||||||
|
yield(Block{Text: content[prevIndex:], Start: prevIndex})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
nip27/blocks_test.go
Normal file
50
nip27/blocks_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package nip27
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip73"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
for i, tc := range []struct {
|
||||||
|
content string
|
||||||
|
expected []Block
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!",
|
||||||
|
[]Block{
|
||||||
|
{Text: "hello, ", Start: 0},
|
||||||
|
{Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg", Start: 7, Pointer: nostr.ProfilePointer{PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393"}},
|
||||||
|
{Text: " wrote ", Start: 83},
|
||||||
|
{Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4", Start: 90, Pointer: nostr.EventPointer{ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393"}},
|
||||||
|
{Text: "!", Start: 164},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`:wss://oa.ao; this was a relay and now here's a video -> https://videos.com/video.mp4! and some music: http://music.com/song.mp3
|
||||||
|
and a regular link: https://regular.com/page?ok=true. and now a broken link: https://kjxkxk and a broken nostr ref: nostr:nevent1qqsr0f9w78uyy09qwmjt0kv63j4l7sxahq33725lqyyp79whlfjurwspz4mhxue69uhh56nzv34hxcfwv9ehw6nyddhq0ag9xg and a fake nostr ref: nostr:llll ok but finally https://ok.com!`,
|
||||||
|
[]Block{
|
||||||
|
{Text: ":", Start: 0},
|
||||||
|
{Text: "wss://oa.ao", Start: 1, Pointer: nip73.ExternalPointer{Thing: "wss://oa.ao"}},
|
||||||
|
{Text: "; this was a relay and now here's a video -> ", Start: 12},
|
||||||
|
{Text: "https://videos.com/video.mp4", Start: 57, Pointer: nip73.ExternalPointer{Thing: "https://videos.com/video.mp4"}},
|
||||||
|
{Text: "! and some music: ", Start: 85},
|
||||||
|
{Text: "http://music.com/song.mp3", Start: 103, Pointer: nip73.ExternalPointer{Thing: "http://music.com/song.mp3"}},
|
||||||
|
{Text: "\nand a regular link: ", Start: 128},
|
||||||
|
{Text: "https://regular.com/page?ok=true", Start: 149, Pointer: nip73.ExternalPointer{Thing: "https://regular.com/page?ok=true"}},
|
||||||
|
{Text: ". and now a broken link: https://kjxkxk and a broken nostr ref: nostr:nevent1qqsr0f9w78uyy09qwmjt0kv63j4l7sxahq33725lqyyp79whlfjurwspz4mhxue69uhh56nzv34hxcfwv9ehw6nyddhq0ag9xg and a fake nostr ref: nostr:llll ok but finally ", Start: 181},
|
||||||
|
{Text: "https://ok.com", Start: 405, Pointer: nip73.ExternalPointer{Thing: "https://ok.com"}},
|
||||||
|
{Text: "!", Start: 419},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expected, slices.Collect(Parse(tc.content)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ type Reference struct {
|
|||||||
|
|
||||||
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b`)
|
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b`)
|
||||||
|
|
||||||
|
// Deprecated: this is useless, use Parse() isntead (but the semantics is different)
|
||||||
func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
|
func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
|
||||||
return func(yield func(Reference) bool) {
|
return func(yield func(Reference) bool) {
|
||||||
for _, ref := range mentionRegex.FindAllStringSubmatchIndex(evt.Content, -1) {
|
for _, ref := range mentionRegex.FindAllStringSubmatchIndex(evt.Content, -1) {
|
||||||
|
23
nip73/pointer.go
Normal file
23
nip73/pointer.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package nip73
|
||||||
|
|
||||||
|
import "github.com/nbd-wtf/go-nostr"
|
||||||
|
|
||||||
|
var _ nostr.Pointer = (*ExternalPointer)(nil)
|
||||||
|
|
||||||
|
// ExternalPointer represents a pointer to a URL or something else.
|
||||||
|
type ExternalPointer struct {
|
||||||
|
Thing string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalPointerFromTag creates a ExternalPointer from an "i" tag
|
||||||
|
func ExternalPointerFromTag(refTag nostr.Tag) (ExternalPointer, error) {
|
||||||
|
return ExternalPointer{refTag[1]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep ExternalPointer) MatchesEvent(_ nostr.Event) bool { return false }
|
||||||
|
func (ep ExternalPointer) AsTagReference() string { return ep.Thing }
|
||||||
|
func (ep ExternalPointer) AsFilter() nostr.Filter { return nostr.Filter{} }
|
||||||
|
|
||||||
|
func (ep ExternalPointer) AsTag() nostr.Tag {
|
||||||
|
return nostr.Tag{"i", ep.Thing}
|
||||||
|
}
|
19
pointers.go
19
pointers.go
@ -26,7 +26,6 @@ var (
|
|||||||
_ Pointer = (*ProfilePointer)(nil)
|
_ Pointer = (*ProfilePointer)(nil)
|
||||||
_ Pointer = (*EventPointer)(nil)
|
_ Pointer = (*EventPointer)(nil)
|
||||||
_ Pointer = (*EntityPointer)(nil)
|
_ Pointer = (*EntityPointer)(nil)
|
||||||
_ Pointer = (*ExternalPointer)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProfilePointer represents a pointer to a Nostr profile.
|
// ProfilePointer represents a pointer to a Nostr profile.
|
||||||
@ -174,21 +173,3 @@ func (ep EntityPointer) AsTag() Tag {
|
|||||||
}
|
}
|
||||||
return Tag{"a", ep.AsTagReference()}
|
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}
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user