a bunch of [32]byte conversions. still more needed.
This commit is contained in:
12
README.md
12
README.md
@@ -1,6 +1,6 @@
|
||||
[](https://github.com/nbd-wtf/go-nostr/actions/workflows/test.yml)
|
||||
[](https://pkg.go.dev/github.com/nbd-wtf/go-nostr)
|
||||
[](https://goreportcard.com/report/github.com/nbd-wtf/go-nostr)
|
||||
[](https://fiatjaf.com/nostrlib/actions/workflows/test.yml)
|
||||
[](https://pkg.go.dev/fiatjaf.com/nostrlib)
|
||||
[](https://goreportcard.com/report/fiatjaf.com/nostrlib)
|
||||
|
||||
<a href="https://nbd.wtf"><img align="right" height="196" src="https://user-images.githubusercontent.com/1653275/194609043-0add674b-dd40-41ed-986c-ab4a2e053092.png" /></a>
|
||||
|
||||
@@ -10,7 +10,7 @@ go-nostr
|
||||
A set of useful things for [Nostr](https://github.com/nostr-protocol/nostr)-related software.
|
||||
|
||||
```bash
|
||||
go get github.com/nbd-wtf/go-nostr
|
||||
go get fiatjaf.com/nostrlib
|
||||
```
|
||||
|
||||
### Generating a key
|
||||
@@ -21,8 +21,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip19"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
var defaultConnectionOptions = &ws.DialOptions{
|
||||
CompressionMode: ws.CompressionContextTakeover,
|
||||
HTTPHeader: http.Header{
|
||||
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"github.com/nbd-wtf/go-nostr"},
|
||||
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostrlib"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestCount(t *testing.T) {
|
||||
defer rl.Close()
|
||||
|
||||
count, _, err := rl.Count(context.Background(), Filters{
|
||||
{Kinds: []int{KindFollowList}, Tags: TagMap{"p": []string{"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}}},
|
||||
{Kinds: []uint16{KindFollowList}, Tags: TagMap{"p": []string{"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}}},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, count, int64(0))
|
||||
|
||||
40
envelopes.go
40
envelopes.go
@@ -124,7 +124,7 @@ func (v EventEnvelope) MarshalJSON() ([]byte, error) {
|
||||
// ReqEnvelope represents a REQ message.
|
||||
type ReqEnvelope struct {
|
||||
SubscriptionID string
|
||||
Filters
|
||||
Filter
|
||||
}
|
||||
|
||||
func (_ ReqEnvelope) Label() string { return "REQ" }
|
||||
@@ -136,13 +136,8 @@ func (v *ReqEnvelope) FromJSON(data string) error {
|
||||
return fmt.Errorf("failed to decode REQ envelope: missing filters")
|
||||
}
|
||||
v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
|
||||
v.Filters = make(Filters, len(arr)-2)
|
||||
f := 0
|
||||
for i := 2; i < len(arr); i++ {
|
||||
if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw)), &v.Filters[f]); err != nil {
|
||||
return fmt.Errorf("%w -- on filter %d", err, f)
|
||||
}
|
||||
f++
|
||||
if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Filter); err != nil {
|
||||
return fmt.Errorf("on filter: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -152,11 +147,8 @@ func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{NoEscapeHTML: true}
|
||||
w.RawString(`["REQ","`)
|
||||
w.RawString(v.SubscriptionID)
|
||||
w.RawString(`"`)
|
||||
for _, filter := range v.Filters {
|
||||
w.RawString(`,`)
|
||||
filter.MarshalEasyJSON(&w)
|
||||
}
|
||||
w.RawString(`",`)
|
||||
v.Filter.MarshalEasyJSON(&w)
|
||||
w.RawString(`]`)
|
||||
return w.BuildBytes()
|
||||
}
|
||||
@@ -198,15 +190,9 @@ func (v *CountEnvelope) FromJSON(data string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
f := 0
|
||||
for i := 2; i < len(arr); i++ {
|
||||
item := unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw))
|
||||
|
||||
if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
|
||||
return fmt.Errorf("%w -- on filter %d", err, f)
|
||||
}
|
||||
|
||||
f++
|
||||
item := unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw))
|
||||
if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
|
||||
return fmt.Errorf("on filter: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -216,7 +202,7 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{NoEscapeHTML: true}
|
||||
w.RawString(`["COUNT","`)
|
||||
w.RawString(v.SubscriptionID)
|
||||
w.RawString(`",`)
|
||||
w.RawString(`"`)
|
||||
if v.Count != nil {
|
||||
w.RawString(`{"count":`)
|
||||
w.RawString(strconv.FormatInt(*v.Count, 10))
|
||||
@@ -357,7 +343,7 @@ func (v ClosedEnvelope) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// OKEnvelope represents an OK message.
|
||||
type OKEnvelope struct {
|
||||
EventID string
|
||||
EventID ID
|
||||
OK bool
|
||||
Reason string
|
||||
}
|
||||
@@ -374,7 +360,9 @@ func (v *OKEnvelope) FromJSON(data string) error {
|
||||
if len(arr) < 4 {
|
||||
return fmt.Errorf("failed to decode OK envelope: missing fields")
|
||||
}
|
||||
v.EventID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
|
||||
if _, err := hex.Decode(v.EventID[:], []byte(arr[1].Str)); err != nil {
|
||||
return err
|
||||
}
|
||||
v.OK = arr[2].Raw == "true"
|
||||
v.Reason = string(unsafe.Slice(unsafe.StringData(arr[3].Str), len(arr[3].Str)))
|
||||
|
||||
@@ -384,7 +372,7 @@ func (v *OKEnvelope) FromJSON(data string) error {
|
||||
func (v OKEnvelope) MarshalJSON() ([]byte, error) {
|
||||
w := jwriter.Writer{NoEscapeHTML: true}
|
||||
w.RawString(`["OK","`)
|
||||
w.RawString(v.EventID)
|
||||
w.RawString(hex.EncodeToString(v.EventID[:]))
|
||||
w.RawString(`",`)
|
||||
ok := "false"
|
||||
if v.OK {
|
||||
|
||||
@@ -165,7 +165,6 @@ func (sv *sonicVisitor) OnArrayEnd() error {
|
||||
sv.mainEnvelope = sv.event
|
||||
case inReq:
|
||||
sv.mainEnvelope = sv.req
|
||||
sv.smp.doneWithFilterSlice(sv.req.Filters)
|
||||
case inOk:
|
||||
sv.mainEnvelope = sv.ok
|
||||
case inEose:
|
||||
@@ -184,13 +183,13 @@ func (sv *sonicVisitor) OnArrayEnd() error {
|
||||
// filter object properties
|
||||
case inIds:
|
||||
sv.whereWeAre = inFilterObject
|
||||
sv.smp.doneWithStringSlice(sv.currentFilter.IDs)
|
||||
sv.smp.doneWithIDSlice(sv.currentFilter.IDs)
|
||||
case inAuthors:
|
||||
sv.whereWeAre = inFilterObject
|
||||
sv.smp.doneWithStringSlice(sv.currentFilter.Authors)
|
||||
sv.smp.doneWithPubKeySlice(sv.currentFilter.Authors)
|
||||
case inKinds:
|
||||
sv.whereWeAre = inFilterObject
|
||||
sv.smp.doneWithIntSlice(sv.currentFilter.Kinds)
|
||||
sv.smp.doneWithUint16Slice(sv.currentFilter.Kinds)
|
||||
case inAFilterTag:
|
||||
sv.currentFilter.Tags[sv.currentFilterTagName] = sv.currentFilterTagList
|
||||
sv.whereWeAre = inFilterObject
|
||||
@@ -268,13 +267,13 @@ func (sv *sonicVisitor) OnObjectKey(key string) error {
|
||||
sv.whereWeAre = inUntil
|
||||
case "ids":
|
||||
sv.whereWeAre = inIds
|
||||
sv.currentFilter.IDs = sv.smp.reusableStringArray
|
||||
sv.currentFilter.IDs = sv.smp.reusableIDArray
|
||||
case "authors":
|
||||
sv.whereWeAre = inAuthors
|
||||
sv.currentFilter.Authors = sv.smp.reusableStringArray
|
||||
sv.currentFilter.Authors = sv.smp.reusablePubKeyArray
|
||||
case "kinds":
|
||||
sv.whereWeAre = inKinds
|
||||
sv.currentFilter.Kinds = sv.smp.reusableIntArray
|
||||
sv.currentFilter.Kinds = sv.smp.reusableUint16Array
|
||||
case "search":
|
||||
sv.whereWeAre = inSearch
|
||||
case "count", "hll":
|
||||
@@ -316,7 +315,7 @@ func (sv *sonicVisitor) OnObjectEnd() error {
|
||||
sv.currentEvent = nil
|
||||
case inFilterObject:
|
||||
if sv.req != nil {
|
||||
sv.req.Filters = append(sv.req.Filters, *sv.currentFilter)
|
||||
sv.req.Filter = *sv.currentFilter
|
||||
sv.whereWeAre = inReq
|
||||
} else {
|
||||
sv.count.Filter = *sv.currentFilter
|
||||
@@ -335,6 +334,7 @@ func (sv *sonicVisitor) OnObjectEnd() error {
|
||||
func (sv *sonicVisitor) OnString(v string) error {
|
||||
// fmt.Println("***", "OnString", v, "==", sv.whereWeAre)
|
||||
|
||||
var err error
|
||||
switch sv.whereWeAre {
|
||||
case inEnvelope:
|
||||
switch v {
|
||||
@@ -342,7 +342,7 @@ func (sv *sonicVisitor) OnString(v string) error {
|
||||
sv.event = &EventEnvelope{}
|
||||
sv.whereWeAre = inEvent
|
||||
case "REQ":
|
||||
sv.req = &ReqEnvelope{Filters: sv.smp.reusableFilterArray}
|
||||
sv.req = &ReqEnvelope{}
|
||||
sv.whereWeAre = inReq
|
||||
case "OK":
|
||||
sv.ok = &OKEnvelope{}
|
||||
@@ -372,8 +372,11 @@ func (sv *sonicVisitor) OnString(v string) error {
|
||||
case inReq:
|
||||
sv.req.SubscriptionID = v
|
||||
case inOk:
|
||||
if sv.ok.EventID == "" {
|
||||
sv.ok.EventID = v
|
||||
if sv.ok.EventID == [32]byte{} {
|
||||
sv.ok.EventID, err = IDFromHex(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
sv.ok.Reason = v
|
||||
}
|
||||
@@ -396,9 +399,17 @@ func (sv *sonicVisitor) OnString(v string) error {
|
||||
|
||||
// filter object properties
|
||||
case inIds:
|
||||
sv.currentFilter.IDs = append(sv.currentFilter.IDs, v)
|
||||
id, err := IDFromHex(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sv.currentFilter.IDs = append(sv.currentFilter.IDs, id)
|
||||
case inAuthors:
|
||||
sv.currentFilter.Authors = append(sv.currentFilter.Authors, v)
|
||||
pk, err := PubKeyFromHex(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sv.currentFilter.Authors = append(sv.currentFilter.Authors, pk)
|
||||
case inSearch:
|
||||
sv.currentFilter.Search = v
|
||||
sv.whereWeAre = inFilterObject
|
||||
@@ -484,36 +495,27 @@ func (_ sonicVisitor) OnFloat64(v float64, n stdlibjson.Number) error {
|
||||
}
|
||||
|
||||
type sonicMessageParser struct {
|
||||
reusableFilterArray []Filter
|
||||
reusableTagArray []Tag
|
||||
reusableIDArray []ID
|
||||
reusablePubKeyArray []PubKey
|
||||
reusableStringArray []string
|
||||
reusableIntArray []int
|
||||
reusableUint16Array []uint16
|
||||
}
|
||||
|
||||
// NewMessageParser returns a sonicMessageParser object that is intended to be reused many times.
|
||||
// It is not goroutine-safe.
|
||||
func NewMessageParser() sonicMessageParser {
|
||||
return sonicMessageParser{
|
||||
reusableFilterArray: make([]Filter, 0, 1000),
|
||||
reusableTagArray: make([]Tag, 0, 10000),
|
||||
reusableStringArray: make([]string, 0, 10000),
|
||||
reusableIntArray: make([]int, 0, 10000),
|
||||
reusableIDArray: make([]ID, 0, 10000),
|
||||
reusablePubKeyArray: make([]PubKey, 0, 10000),
|
||||
reusableUint16Array: make([]uint16, 0, 10000),
|
||||
}
|
||||
}
|
||||
|
||||
var NewSonicMessageParser = NewMessageParser
|
||||
|
||||
func (smp *sonicMessageParser) doneWithFilterSlice(slice []Filter) {
|
||||
if unsafe.SliceData(smp.reusableFilterArray) == unsafe.SliceData(slice) {
|
||||
smp.reusableFilterArray = slice[len(slice):]
|
||||
}
|
||||
|
||||
if cap(smp.reusableFilterArray) < 7 {
|
||||
// create a new one
|
||||
smp.reusableFilterArray = make([]Filter, 0, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
|
||||
if unsafe.SliceData(smp.reusableTagArray) == unsafe.SliceData(slice) {
|
||||
smp.reusableTagArray = slice[len(slice):]
|
||||
@@ -525,7 +527,7 @@ func (smp *sonicMessageParser) doneWithTagSlice(slice []Tag) {
|
||||
}
|
||||
}
|
||||
|
||||
func (smp *sonicMessageParser) doneWithStringSlice(slice []string) {
|
||||
func (smp *sonicMessageParser) doneWithIDSlice(slice []string) {
|
||||
if unsafe.SliceData(smp.reusableStringArray) == unsafe.SliceData(slice) {
|
||||
smp.reusableStringArray = slice[len(slice):]
|
||||
}
|
||||
@@ -536,14 +538,14 @@ func (smp *sonicMessageParser) doneWithStringSlice(slice []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (smp *sonicMessageParser) doneWithIntSlice(slice []int) {
|
||||
if unsafe.SliceData(smp.reusableIntArray) == unsafe.SliceData(slice) {
|
||||
smp.reusableIntArray = slice[len(slice):]
|
||||
func (smp *sonicMessageParser) doneWithUint16Slice(slice []uint16) {
|
||||
if unsafe.SliceData(smp.reusableUint16Array) == unsafe.SliceData(slice) {
|
||||
smp.reusableUint16Array = slice[len(slice):]
|
||||
}
|
||||
|
||||
if cap(smp.reusableIntArray) < 8 {
|
||||
if cap(smp.reusableUint16Array) < 8 {
|
||||
// create a new one
|
||||
smp.reusableIntArray = make([]int, 0, 10000)
|
||||
smp.reusableUint16Array = make([]uint16, 0, 10000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestParseMessage(t *testing.T) {
|
||||
{
|
||||
Name: "REQ envelope",
|
||||
Message: `["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko", "batuke"]}]`,
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []int{1}}, {Kinds: []int{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}},
|
||||
ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []uint16{1}}, {Kinds: []uint16{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}},
|
||||
},
|
||||
{
|
||||
Name: "CLOSE envelope",
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestEOSEMadness(t *testing.T) {
|
||||
defer rl.Close()
|
||||
|
||||
sub, err := rl.Subscribe(context.Background(), Filters{
|
||||
{Kinds: []int{KindTextNote}, Limit: 2},
|
||||
{Kinds: []uint16{KindTextNote}, Limit: 2},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
51
event.go
51
event.go
@@ -10,13 +10,13 @@ import (
|
||||
|
||||
// Event represents a Nostr event.
|
||||
type Event struct {
|
||||
ID string
|
||||
PubKey string
|
||||
ID ID
|
||||
PubKey PubKey
|
||||
CreatedAt Timestamp
|
||||
Kind int
|
||||
Kind uint16
|
||||
Tags Tags
|
||||
Content string
|
||||
Sig string
|
||||
Sig [64]byte
|
||||
}
|
||||
|
||||
func (evt Event) String() string {
|
||||
@@ -24,37 +24,14 @@ func (evt Event) String() string {
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// GetID computes the event ID and returns it as a hex string.
|
||||
func (evt *Event) GetID() string {
|
||||
h := sha256.Sum256(evt.Serialize())
|
||||
return hex.EncodeToString(h[:])
|
||||
// GetID serializes and returns the event ID as a string.
|
||||
func (evt *Event) GetID() ID {
|
||||
return sha256.Sum256(evt.Serialize())
|
||||
}
|
||||
|
||||
// CheckID checks if the implied ID matches the given ID more efficiently.
|
||||
func (evt *Event) CheckID() bool {
|
||||
if len(evt.ID) != 64 {
|
||||
return false
|
||||
}
|
||||
|
||||
ser := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80)
|
||||
ser = serializeEventInto(evt, ser)
|
||||
h := sha256.Sum256(ser)
|
||||
|
||||
const hextable = "0123456789abcdef"
|
||||
|
||||
for i := 0; i < 32; i++ {
|
||||
b := hextable[h[i]>>4]
|
||||
if b != evt.ID[i*2] {
|
||||
return false
|
||||
}
|
||||
|
||||
b = hextable[h[i]&0x0f]
|
||||
if b != evt.ID[i*2+1] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return evt.GetID() == evt.ID
|
||||
}
|
||||
|
||||
// Serialize outputs a byte array that can be hashed to produce the canonical event "id".
|
||||
@@ -68,13 +45,13 @@ func (evt *Event) Serialize() []byte {
|
||||
func serializeEventInto(evt *Event, dst []byte) []byte {
|
||||
// the header portion is easy to serialize
|
||||
// [0,"pubkey",created_at,kind,[
|
||||
dst = append(dst, "[0,\""...)
|
||||
dst = append(dst, evt.PubKey...)
|
||||
dst = append(dst, "\","...)
|
||||
dst = append(dst, `[0,"`...)
|
||||
dst = hex.AppendEncode(dst, evt.PubKey[:])
|
||||
dst = append(dst, `",`...)
|
||||
dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...)
|
||||
dst = append(dst, ',')
|
||||
dst = append(dst, strconv.Itoa(evt.Kind)...)
|
||||
dst = append(dst, ',')
|
||||
dst = append(dst, `,`...)
|
||||
dst = append(dst, strconv.FormatUint(uint64(evt.Kind), 10)...)
|
||||
dst = append(dst, `,`...)
|
||||
|
||||
// tags
|
||||
dst = append(dst, '[')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
@@ -23,6 +25,7 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
var reusableBuffer [64]byte
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(true)
|
||||
in.WantColon()
|
||||
@@ -33,13 +36,15 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
|
||||
}
|
||||
switch key {
|
||||
case "id":
|
||||
out.ID = in.String()
|
||||
hex.Decode(reusableBuffer[:], []byte(in.String()))
|
||||
copy(out.ID[:], reusableBuffer[0:32])
|
||||
case "pubkey":
|
||||
out.PubKey = in.String()
|
||||
hex.Decode(reusableBuffer[:], []byte(in.String()))
|
||||
copy(out.PubKey[:], reusableBuffer[0:32])
|
||||
case "created_at":
|
||||
out.CreatedAt = Timestamp(in.Int64())
|
||||
case "kind":
|
||||
out.Kind = in.Int()
|
||||
out.Kind = uint16(in.Int())
|
||||
case "tags":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
@@ -83,7 +88,8 @@ func easyjsonF642ad3eDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Event)
|
||||
case "content":
|
||||
out.Content = in.String()
|
||||
case "sig":
|
||||
out.Sig = in.String()
|
||||
hex.Decode(reusableBuffer[:], []byte(in.String()))
|
||||
copy(out.Sig[:], reusableBuffer[0:64])
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
@@ -100,20 +106,20 @@ func easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Event)
|
||||
{
|
||||
const prefix string = "\"kind\":"
|
||||
out.RawString(prefix)
|
||||
out.Int(in.Kind)
|
||||
out.Int(int(in.Kind))
|
||||
}
|
||||
{
|
||||
if in.ID != "" {
|
||||
if in.ID != [32]byte{} {
|
||||
const prefix string = ",\"id\":"
|
||||
out.RawString(prefix)
|
||||
out.String(in.ID)
|
||||
out.String(hex.EncodeToString(in.ID[:]))
|
||||
}
|
||||
}
|
||||
{
|
||||
if in.PubKey != "" {
|
||||
if in.PubKey != [32]byte{} {
|
||||
const prefix string = ",\"pubkey\":"
|
||||
out.RawString(prefix)
|
||||
out.String(in.PubKey)
|
||||
out.String(hex.EncodeToString(in.PubKey[:]))
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -146,10 +152,10 @@ func easyjsonF642ad3eEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Event)
|
||||
out.String(in.Content)
|
||||
}
|
||||
{
|
||||
if in.Sig != "" {
|
||||
if in.Sig != [64]byte{} {
|
||||
const prefix string = ",\"sig\":"
|
||||
out.RawString(prefix)
|
||||
out.String(in.Sig)
|
||||
out.String(hex.EncodeToString(in.Sig[:]))
|
||||
}
|
||||
}
|
||||
out.RawByte('}')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"testing"
|
||||
@@ -34,15 +35,18 @@ func TestEventParsingAndVerifying(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEventSerialization(t *testing.T) {
|
||||
sig := [64]byte{}
|
||||
hex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79"))
|
||||
|
||||
events := []Event{
|
||||
{
|
||||
ID: "92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d",
|
||||
PubKey: "e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b",
|
||||
ID: MustIDFromHex("92570b321da503eac8014b23447301eb3d0bbdfbace0d11a4e4072e72bb7205d"),
|
||||
PubKey: MustPubKeyFromHex("e9142f724955c5854de36324dab0434f97b15ec6b33464d56ebe491e3f559d1b"),
|
||||
Kind: KindEncryptedDirectMessage,
|
||||
CreatedAt: Timestamp(1671028682),
|
||||
Tags: Tags{Tag{"p", "f8340b2bde651576b75af61aa26c80e13c65029f00f7f64004eece679bf7059f"}},
|
||||
Content: "you say yes, I say no",
|
||||
Sig: "ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79",
|
||||
Sig: sig,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ func TestEventSerialization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustSignEvent(t *testing.T, privkey string, event *Event) {
|
||||
func mustSignEvent(t *testing.T, privkey [32]byte, event *Event) {
|
||||
t.Helper()
|
||||
if err := event.Sign(privkey); err != nil {
|
||||
t.Fatalf("event.Sign: %v", err)
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip19"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
51
filter.go
51
filter.go
@@ -6,12 +6,10 @@ import (
|
||||
"github.com/mailru/easyjson"
|
||||
)
|
||||
|
||||
type Filters []Filter
|
||||
|
||||
type Filter struct {
|
||||
IDs []string
|
||||
Kinds []int
|
||||
Authors []string
|
||||
IDs []ID
|
||||
Kinds []uint16
|
||||
Authors []PubKey
|
||||
Tags TagMap
|
||||
Since *Timestamp
|
||||
Until *Timestamp
|
||||
@@ -24,29 +22,6 @@ type Filter struct {
|
||||
|
||||
type TagMap map[string][]string
|
||||
|
||||
func (eff Filters) String() string {
|
||||
j, _ := json.Marshal(eff)
|
||||
return string(j)
|
||||
}
|
||||
|
||||
func (eff Filters) Match(event *Event) bool {
|
||||
for _, filter := range eff {
|
||||
if filter.Matches(event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (eff Filters) MatchIgnoringTimestampConstraints(event *Event) bool {
|
||||
for _, filter := range eff {
|
||||
if filter.MatchesIgnoringTimestampConstraints(event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ef Filter) String() string {
|
||||
j, _ := easyjson.Marshal(ef)
|
||||
return string(j)
|
||||
@@ -99,11 +74,11 @@ func FilterEqual(a Filter, b Filter) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !similar(a.IDs, b.IDs) {
|
||||
if !similarID(a.IDs, b.IDs) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !similar(a.Authors, b.Authors) {
|
||||
if !similarPublicKey(a.Authors, b.Authors) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -142,14 +117,26 @@ func FilterEqual(a Filter, b Filter) bool {
|
||||
|
||||
func (ef Filter) Clone() Filter {
|
||||
clone := Filter{
|
||||
IDs: slices.Clone(ef.IDs),
|
||||
Authors: slices.Clone(ef.Authors),
|
||||
Kinds: slices.Clone(ef.Kinds),
|
||||
Limit: ef.Limit,
|
||||
Search: ef.Search,
|
||||
LimitZero: ef.LimitZero,
|
||||
}
|
||||
|
||||
if ef.IDs != nil {
|
||||
clone.IDs = make([]ID, len(ef.IDs))
|
||||
for i, src := range ef.IDs {
|
||||
copy(clone.IDs[i][:], src[:])
|
||||
}
|
||||
}
|
||||
|
||||
if ef.Authors != nil {
|
||||
clone.Authors = make([]PubKey, len(ef.Authors))
|
||||
for i, src := range ef.Authors {
|
||||
copy(clone.Authors[i][:], src[:])
|
||||
}
|
||||
}
|
||||
|
||||
if ef.Tags != nil {
|
||||
clone.Tags = make(TagMap, len(ef.Tags))
|
||||
for k, v := range ef.Tags {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
@@ -41,17 +43,17 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
|
||||
in.Delim('[')
|
||||
if out.IDs == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.IDs = make([]string, 0, 20)
|
||||
out.IDs = make([]ID, 0, 20)
|
||||
} else {
|
||||
out.IDs = []string{}
|
||||
out.IDs = []ID{}
|
||||
}
|
||||
} else {
|
||||
out.IDs = (out.IDs)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v1 string
|
||||
v1 = string(in.String())
|
||||
out.IDs = append(out.IDs, v1)
|
||||
id := [32]byte{}
|
||||
hex.Decode(id[:], []byte(in.String()))
|
||||
out.IDs = append(out.IDs, id)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
@@ -64,17 +66,15 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
|
||||
in.Delim('[')
|
||||
if out.Kinds == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Kinds = make([]int, 0, 8)
|
||||
out.Kinds = make([]uint16, 0, 8)
|
||||
} else {
|
||||
out.Kinds = []int{}
|
||||
out.Kinds = []uint16{}
|
||||
}
|
||||
} else {
|
||||
out.Kinds = (out.Kinds)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v2 int
|
||||
v2 = int(in.Int())
|
||||
out.Kinds = append(out.Kinds, v2)
|
||||
out.Kinds = append(out.Kinds, uint16(in.Int()))
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
@@ -87,17 +87,17 @@ func easyjson4d398eaaDecodeGithubComNbdWtfGoNostr(in *jlexer.Lexer, out *Filter)
|
||||
in.Delim('[')
|
||||
if out.Authors == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Authors = make([]string, 0, 40)
|
||||
out.Authors = make([]PubKey, 0, 40)
|
||||
} else {
|
||||
out.Authors = []string{}
|
||||
out.Authors = []PubKey{}
|
||||
}
|
||||
} else {
|
||||
out.Authors = (out.Authors)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v3 string
|
||||
v3 = string(in.String())
|
||||
out.Authors = append(out.Authors, v3)
|
||||
pk := [32]byte{}
|
||||
hex.Decode(pk[:], []byte(in.String()))
|
||||
out.Authors = append(out.Authors, pk)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
@@ -178,7 +178,7 @@ func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter
|
||||
if v4 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
out.String(string(v5))
|
||||
out.String(hex.EncodeToString(v5[:]))
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func easyjson4d398eaaEncodeGithubComNbdWtfGoNostr(out *jwriter.Writer, in Filter
|
||||
if v8 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
out.String(string(v9))
|
||||
out.String(hex.EncodeToString(v9[:]))
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestFilterUnmarshal(t *testing.T) {
|
||||
func TestFilterMarshal(t *testing.T) {
|
||||
until := Timestamp(12345678)
|
||||
filterj, err := json.Marshal(Filter{
|
||||
Kinds: []int{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
|
||||
Kinds: []uint16{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
|
||||
Tags: TagMap{"fruit": {"banana", "mango"}},
|
||||
Until: &until,
|
||||
})
|
||||
@@ -60,7 +60,7 @@ func TestFilterUnmarshalWithLimitZero(t *testing.T) {
|
||||
func TestFilterMarshalWithLimitZero(t *testing.T) {
|
||||
until := Timestamp(12345678)
|
||||
filterj, err := json.Marshal(Filter{
|
||||
Kinds: []int{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
|
||||
Kinds: []uint16{KindTextNote, KindRecommendServer, KindEncryptedDirectMessage},
|
||||
Tags: TagMap{"fruit": {"banana", "mango"}},
|
||||
Until: &until,
|
||||
LimitZero: true,
|
||||
@@ -83,50 +83,50 @@ func TestFilterMatchingLive(t *testing.T) {
|
||||
|
||||
func TestFilterEquality(t *testing.T) {
|
||||
assert.True(t, FilterEqual(
|
||||
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}},
|
||||
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}},
|
||||
Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}},
|
||||
Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}},
|
||||
), "kinds filters should be equal")
|
||||
|
||||
assert.True(t, FilterEqual(
|
||||
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"a", "b"}}},
|
||||
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"b", "a"}}},
|
||||
Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"a", "b"}}},
|
||||
Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion}, Tags: TagMap{"letter": {"b", "a"}}},
|
||||
), "kind+tags filters should be equal")
|
||||
|
||||
tm := Now()
|
||||
assert.True(t, FilterEqual(
|
||||
Filter{
|
||||
Kinds: []int{KindEncryptedDirectMessage, KindDeletion},
|
||||
Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion},
|
||||
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
|
||||
Since: &tm,
|
||||
IDs: []string{"aaaa", "bbbb"},
|
||||
IDs: []ID{{'a', 'a'}, {'b', 'b'}},
|
||||
},
|
||||
Filter{
|
||||
Kinds: []int{KindDeletion, KindEncryptedDirectMessage},
|
||||
Kinds: []uint16{KindDeletion, KindEncryptedDirectMessage},
|
||||
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
|
||||
Since: &tm,
|
||||
IDs: []string{"aaaa", "bbbb"},
|
||||
IDs: []ID{{'a', 'a'}, {'b', 'b'}},
|
||||
},
|
||||
), "kind+2tags+since+ids filters should be equal")
|
||||
|
||||
assert.False(t, FilterEqual(
|
||||
Filter{Kinds: []int{KindTextNote, KindEncryptedDirectMessage, KindDeletion}},
|
||||
Filter{Kinds: []int{KindEncryptedDirectMessage, KindDeletion, KindRepost}},
|
||||
Filter{Kinds: []uint16{KindTextNote, KindEncryptedDirectMessage, KindDeletion}},
|
||||
Filter{Kinds: []uint16{KindEncryptedDirectMessage, KindDeletion, KindRepost}},
|
||||
), "kinds filters shouldn't be equal")
|
||||
}
|
||||
|
||||
func TestFilterClone(t *testing.T) {
|
||||
ts := Now() - 60*60
|
||||
flt := Filter{
|
||||
Kinds: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
Kinds: []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||||
Tags: TagMap{"letter": {"a", "b"}, "fruit": {"banana"}},
|
||||
Since: &ts,
|
||||
IDs: []string{"9894b4b5cb5166d23ee8899a4151cf0c66aec00bde101982a13b8e8ceb972df9"},
|
||||
IDs: []ID{IDFromHex("9894b4b5cb5166d23ee8899a4151cf0c66aec00bde101982a13b8e8ceb972df9")},
|
||||
}
|
||||
clone := flt.Clone()
|
||||
assert.True(t, FilterEqual(flt, clone), "clone is not equal:\n %v !=\n %v", flt, clone)
|
||||
|
||||
clone1 := flt.Clone()
|
||||
clone1.IDs = append(clone1.IDs, "88f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
|
||||
clone1.IDs = append(clone1.IDs, IDFromHex("88f0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"))
|
||||
assert.False(t, FilterEqual(flt, clone1), "modifying the clone ids should cause it to not be equal anymore")
|
||||
|
||||
clone2 := flt.Clone()
|
||||
@@ -143,11 +143,11 @@ func TestFilterClone(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTheoreticalLimit(t *testing.T) {
|
||||
require.Equal(t, 6, GetTheoreticalLimit(Filter{IDs: []string{"a", "b", "c", "d", "e", "f"}}))
|
||||
require.Equal(t, 9, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c"}, Kinds: []int{3, 0, 10002}}))
|
||||
require.Equal(t, 4, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d"}, Kinds: []int{10050}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d"}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Kinds: []int{3, 0, 10002}}))
|
||||
require.Equal(t, 24, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d", "e", "f"}, Kinds: []int{30023, 30024}, Tags: TagMap{"d": []string{"aaa", "bbb"}}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []string{"a", "b", "c", "d", "e", "f"}, Kinds: []int{30023, 30024}}))
|
||||
require.Equal(t, 6, GetTheoreticalLimit(Filter{IDs: []ID{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}}))
|
||||
require.Equal(t, 9, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}}, Kinds: []uint16{3, 0, 10002}}))
|
||||
require.Equal(t, 4, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}}, Kinds: []uint16{10050}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Kinds: []uint16{3, 0, 10002}}))
|
||||
require.Equal(t, 24, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}, Kinds: []uint16{30023, 30024}, Tags: TagMap{"d": []string{"aaa", "bbb"}}}))
|
||||
require.Equal(t, -1, GetTheoreticalLimit(Filter{Authors: []PubKey{{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}}, Kinds: []uint16{30023, 30024}}))
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
||||
module github.com/nbd-wtf/go-nostr
|
||||
module fiatjaf.com/nostr
|
||||
|
||||
go 1.24.1
|
||||
|
||||
|
||||
59
helpers.go
59
helpers.go
@@ -1,6 +1,7 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -49,6 +50,48 @@ func similar[E constraints.Ordered](as, bs []E) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func similarID(as, bs []ID) bool {
|
||||
if len(as) != len(bs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, a := range as {
|
||||
for _, b := range bs {
|
||||
if b == a {
|
||||
goto next
|
||||
}
|
||||
}
|
||||
// didn't find a B that corresponded to the current A
|
||||
return false
|
||||
|
||||
next:
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func similarPublicKey(as, bs []PubKey) bool {
|
||||
if len(as) != len(bs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, a := range as {
|
||||
for _, b := range bs {
|
||||
if b == a {
|
||||
goto next
|
||||
}
|
||||
}
|
||||
// didn't find a B that corresponded to the current A
|
||||
return false
|
||||
|
||||
next:
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Escaping strings for JSON encoding according to RFC8259.
|
||||
// Also encloses result in quotation marks "".
|
||||
func escapeString(dst []byte, s string) []byte {
|
||||
@@ -140,11 +183,11 @@ func extractSubID(jsonStr string) string {
|
||||
return jsonStr[start : start+end]
|
||||
}
|
||||
|
||||
func extractEventID(jsonStr string) string {
|
||||
func extractEventID(jsonStr string) ID {
|
||||
// look for "id" pattern
|
||||
start := strings.Index(jsonStr, `"id"`)
|
||||
if start == -1 {
|
||||
return ""
|
||||
return [32]byte{}
|
||||
}
|
||||
|
||||
// move to the next quote
|
||||
@@ -152,14 +195,16 @@ func extractEventID(jsonStr string) string {
|
||||
start += 4 + offset + 1
|
||||
|
||||
// get 64 characters of the id
|
||||
return jsonStr[start : start+64]
|
||||
var id [32]byte
|
||||
hex.Decode(id[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
|
||||
return id
|
||||
}
|
||||
|
||||
func extractEventPubKey(jsonStr string) string {
|
||||
func extractEventPubKey(jsonStr string) PubKey {
|
||||
// look for "pubkey" pattern
|
||||
start := strings.Index(jsonStr, `"pubkey"`)
|
||||
if start == -1 {
|
||||
return ""
|
||||
return PubKey{}
|
||||
}
|
||||
|
||||
// move to the next quote
|
||||
@@ -167,7 +212,9 @@ func extractEventPubKey(jsonStr string) string {
|
||||
start += 8 + offset + 1
|
||||
|
||||
// get 64 characters of the pubkey
|
||||
return jsonStr[start : start+64]
|
||||
var pk [32]byte
|
||||
hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64))
|
||||
return pk
|
||||
}
|
||||
|
||||
func extractDTag(jsonStr string) string {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip46"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*BunkerSigner)(nil)
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"fiatjaf.com/nostrlib/nip49"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*EncryptedKeySigner)(nil)
|
||||
|
||||
10
keyer/lib.go
10
keyer/lib.go
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip05"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip05"
|
||||
"fiatjaf.com/nostrlib/nip19"
|
||||
"fiatjaf.com/nostrlib/nip46"
|
||||
"fiatjaf.com/nostrlib/nip49"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package keyer
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*ManualSigner)(nil)
|
||||
|
||||
@@ -3,8 +3,8 @@ package keyer
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
|
||||
41
keys.go
41
keys.go
@@ -2,48 +2,27 @@ package nostr
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
)
|
||||
|
||||
func GeneratePrivateKey() string {
|
||||
params := btcec.S256().Params()
|
||||
one := new(big.Int).SetInt64(1)
|
||||
|
||||
b := make([]byte, params.BitSize/8+8)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
return ""
|
||||
func GeneratePrivateKey() [32]byte {
|
||||
var sk [32]byte
|
||||
if _, err := io.ReadFull(rand.Reader, sk[:]); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes when generating private key"))
|
||||
}
|
||||
|
||||
k := new(big.Int).SetBytes(b)
|
||||
n := new(big.Int).Sub(params.N, one)
|
||||
k.Mod(k, n)
|
||||
k.Add(k, one)
|
||||
|
||||
return fmt.Sprintf("%064x", k.Bytes())
|
||||
return sk
|
||||
}
|
||||
|
||||
func GetPublicKey(sk string) (string, error) {
|
||||
b, err := hex.DecodeString(sk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, pk := btcec.PrivKeyFromBytes(b)
|
||||
return hex.EncodeToString(schnorr.SerializePubKey(pk)), nil
|
||||
func GetPublicKey(sk [32]byte) PubKey {
|
||||
_, pk := btcec.PrivKeyFromBytes(sk[:])
|
||||
return [32]byte(pk.SerializeCompressed()[1:])
|
||||
}
|
||||
|
||||
func IsValidPublicKey(pk string) bool {
|
||||
if !isLowerHex(pk) {
|
||||
return false
|
||||
}
|
||||
|
||||
v, _ := hex.DecodeString(pk)
|
||||
_, err := schnorr.ParsePubKey(v)
|
||||
func IsValidPublicKey(pk [32]byte) bool {
|
||||
_, err := schnorr.ParsePubKey(pk[:])
|
||||
return err == nil
|
||||
}
|
||||
|
||||
272
kinds.go
272
kinds.go
@@ -1,152 +1,152 @@
|
||||
package nostr
|
||||
|
||||
const (
|
||||
KindProfileMetadata int = 0
|
||||
KindTextNote int = 1
|
||||
KindRecommendServer int = 2
|
||||
KindFollowList int = 3
|
||||
KindEncryptedDirectMessage int = 4
|
||||
KindDeletion int = 5
|
||||
KindRepost int = 6
|
||||
KindReaction int = 7
|
||||
KindBadgeAward int = 8
|
||||
KindSimpleGroupChatMessage int = 9
|
||||
KindSimpleGroupThreadedReply int = 10
|
||||
KindSimpleGroupThread int = 11
|
||||
KindSimpleGroupReply int = 12
|
||||
KindSeal int = 13
|
||||
KindDirectMessage int = 14
|
||||
KindGenericRepost int = 16
|
||||
KindReactionToWebsite int = 17
|
||||
KindChannelCreation int = 40
|
||||
KindChannelMetadata int = 41
|
||||
KindChannelMessage int = 42
|
||||
KindChannelHideMessage int = 43
|
||||
KindChannelMuteUser int = 44
|
||||
KindChess int = 64
|
||||
KindMergeRequests int = 818
|
||||
KindComment int = 1111
|
||||
KindBid int = 1021
|
||||
KindBidConfirmation int = 1022
|
||||
KindOpenTimestamps int = 1040
|
||||
KindGiftWrap int = 1059
|
||||
KindFileMetadata int = 1063
|
||||
KindLiveChatMessage int = 1311
|
||||
KindPatch int = 1617
|
||||
KindIssue int = 1621
|
||||
KindReply int = 1622
|
||||
KindStatusOpen int = 1630
|
||||
KindStatusApplied int = 1631
|
||||
KindStatusClosed int = 1632
|
||||
KindStatusDraft int = 1633
|
||||
KindProblemTracker int = 1971
|
||||
KindReporting int = 1984
|
||||
KindLabel int = 1985
|
||||
KindRelayReviews int = 1986
|
||||
KindAIEmbeddings int = 1987
|
||||
KindTorrent int = 2003
|
||||
KindTorrentComment int = 2004
|
||||
KindCoinjoinPool int = 2022
|
||||
KindCommunityPostApproval int = 4550
|
||||
KindJobFeedback int = 7000
|
||||
KindSimpleGroupPutUser int = 9000
|
||||
KindSimpleGroupRemoveUser int = 9001
|
||||
KindSimpleGroupEditMetadata int = 9002
|
||||
KindSimpleGroupDeleteEvent int = 9005
|
||||
KindSimpleGroupCreateGroup int = 9007
|
||||
KindSimpleGroupDeleteGroup int = 9008
|
||||
KindSimpleGroupCreateInvite int = 9009
|
||||
KindSimpleGroupJoinRequest int = 9021
|
||||
KindSimpleGroupLeaveRequest int = 9022
|
||||
KindZapGoal int = 9041
|
||||
KindNutZap int = 9321
|
||||
KindTidalLogin int = 9467
|
||||
KindZapRequest int = 9734
|
||||
KindZap int = 9735
|
||||
KindHighlights int = 9802
|
||||
KindMuteList int = 10000
|
||||
KindPinList int = 10001
|
||||
KindRelayListMetadata int = 10002
|
||||
KindBookmarkList int = 10003
|
||||
KindCommunityList int = 10004
|
||||
KindPublicChatList int = 10005
|
||||
KindBlockedRelayList int = 10006
|
||||
KindSearchRelayList int = 10007
|
||||
KindSimpleGroupList int = 10009
|
||||
KindInterestList int = 10015
|
||||
KindNutZapInfo int = 10019
|
||||
KindEmojiList int = 10030
|
||||
KindDMRelayList int = 10050
|
||||
KindUserServerList int = 10063
|
||||
KindFileStorageServerList int = 10096
|
||||
KindGoodWikiAuthorList int = 10101
|
||||
KindGoodWikiRelayList int = 10102
|
||||
KindNWCWalletInfo int = 13194
|
||||
KindLightningPubRPC int = 21000
|
||||
KindClientAuthentication int = 22242
|
||||
KindNWCWalletRequest int = 23194
|
||||
KindNWCWalletResponse int = 23195
|
||||
KindNostrConnect int = 24133
|
||||
KindBlobs int = 24242
|
||||
KindHTTPAuth int = 27235
|
||||
KindCategorizedPeopleList int = 30000
|
||||
KindCategorizedBookmarksList int = 30001
|
||||
KindRelaySets int = 30002
|
||||
KindBookmarkSets int = 30003
|
||||
KindCuratedSets int = 30004
|
||||
KindCuratedVideoSets int = 30005
|
||||
KindMuteSets int = 30007
|
||||
KindProfileBadges int = 30008
|
||||
KindBadgeDefinition int = 30009
|
||||
KindInterestSets int = 30015
|
||||
KindStallDefinition int = 30017
|
||||
KindProductDefinition int = 30018
|
||||
KindMarketplaceUI int = 30019
|
||||
KindProductSoldAsAuction int = 30020
|
||||
KindArticle int = 30023
|
||||
KindDraftArticle int = 30024
|
||||
KindEmojiSets int = 30030
|
||||
KindModularArticleHeader int = 30040
|
||||
KindModularArticleContent int = 30041
|
||||
KindReleaseArtifactSets int = 30063
|
||||
KindApplicationSpecificData int = 30078
|
||||
KindLiveEvent int = 30311
|
||||
KindUserStatuses int = 30315
|
||||
KindClassifiedListing int = 30402
|
||||
KindDraftClassifiedListing int = 30403
|
||||
KindRepositoryAnnouncement int = 30617
|
||||
KindRepositoryState int = 30618
|
||||
KindSimpleGroupMetadata int = 39000
|
||||
KindSimpleGroupAdmins int = 39001
|
||||
KindSimpleGroupMembers int = 39002
|
||||
KindSimpleGroupRoles int = 39003
|
||||
KindWikiArticle int = 30818
|
||||
KindRedirects int = 30819
|
||||
KindFeed int = 31890
|
||||
KindDateCalendarEvent int = 31922
|
||||
KindTimeCalendarEvent int = 31923
|
||||
KindCalendar int = 31924
|
||||
KindCalendarEventRSVP int = 31925
|
||||
KindHandlerRecommendation int = 31989
|
||||
KindHandlerInformation int = 31990
|
||||
KindVideoEvent int = 34235
|
||||
KindShortVideoEvent int = 34236
|
||||
KindVideoViewEvent int = 34237
|
||||
KindCommunityDefinition int = 34550
|
||||
KindProfileMetadata uint16 = 0
|
||||
KindTextNote uint16 = 1
|
||||
KindRecommendServer uint16 = 2
|
||||
KindFollowList uint16 = 3
|
||||
KindEncryptedDirectMessage uint16 = 4
|
||||
KindDeletion uint16 = 5
|
||||
KindRepost uint16 = 6
|
||||
KindReaction uint16 = 7
|
||||
KindBadgeAward uint16 = 8
|
||||
KindSimpleGroupChatMessage uint16 = 9
|
||||
KindSimpleGroupThreadedReply uint16 = 10
|
||||
KindSimpleGroupThread uint16 = 11
|
||||
KindSimpleGroupReply uint16 = 12
|
||||
KindSeal uint16 = 13
|
||||
KindDirectMessage uint16 = 14
|
||||
KindGenericRepost uint16 = 16
|
||||
KindReactionToWebsite uint16 = 17
|
||||
KindChannelCreation uint16 = 40
|
||||
KindChannelMetadata uint16 = 41
|
||||
KindChannelMessage uint16 = 42
|
||||
KindChannelHideMessage uint16 = 43
|
||||
KindChannelMuteUser uint16 = 44
|
||||
KindChess uint16 = 64
|
||||
KindMergeRequests uint16 = 818
|
||||
KindComment uint16 = 1111
|
||||
KindBid uint16 = 1021
|
||||
KindBidConfirmation uint16 = 1022
|
||||
KindOpenTimestamps uint16 = 1040
|
||||
KindGiftWrap uint16 = 1059
|
||||
KindFileMetadata uint16 = 1063
|
||||
KindLiveChatMessage uint16 = 1311
|
||||
KindPatch uint16 = 1617
|
||||
KindIssue uint16 = 1621
|
||||
KindReply uint16 = 1622
|
||||
KindStatusOpen uint16 = 1630
|
||||
KindStatusApplied uint16 = 1631
|
||||
KindStatusClosed uint16 = 1632
|
||||
KindStatusDraft uint16 = 1633
|
||||
KindProblemTracker uint16 = 1971
|
||||
KindReporting uint16 = 1984
|
||||
KindLabel uint16 = 1985
|
||||
KindRelayReviews uint16 = 1986
|
||||
KindAIEmbeddings uint16 = 1987
|
||||
KindTorrent uint16 = 2003
|
||||
KindTorrentComment uint16 = 2004
|
||||
KindCoinjoinPool uint16 = 2022
|
||||
KindCommunityPostApproval uint16 = 4550
|
||||
KindJobFeedback uint16 = 7000
|
||||
KindSimpleGroupPutUser uint16 = 9000
|
||||
KindSimpleGroupRemoveUser uint16 = 9001
|
||||
KindSimpleGroupEditMetadata uint16 = 9002
|
||||
KindSimpleGroupDeleteEvent uint16 = 9005
|
||||
KindSimpleGroupCreateGroup uint16 = 9007
|
||||
KindSimpleGroupDeleteGroup uint16 = 9008
|
||||
KindSimpleGroupCreateInvite uint16 = 9009
|
||||
KindSimpleGroupJoinRequest uint16 = 9021
|
||||
KindSimpleGroupLeaveRequest uint16 = 9022
|
||||
KindZapGoal uint16 = 9041
|
||||
KindNutZap uint16 = 9321
|
||||
KindTidalLogin uint16 = 9467
|
||||
KindZapRequest uint16 = 9734
|
||||
KindZap uint16 = 9735
|
||||
KindHighlights uint16 = 9802
|
||||
KindMuteList uint16 = 10000
|
||||
KindPinList uint16 = 10001
|
||||
KindRelayListMetadata uint16 = 10002
|
||||
KindBookmarkList uint16 = 10003
|
||||
KindCommunityList uint16 = 10004
|
||||
KindPublicChatList uint16 = 10005
|
||||
KindBlockedRelayList uint16 = 10006
|
||||
KindSearchRelayList uint16 = 10007
|
||||
KindSimpleGroupList uint16 = 10009
|
||||
KindInterestList uint16 = 10015
|
||||
KindNutZapInfo uint16 = 10019
|
||||
KindEmojiList uint16 = 10030
|
||||
KindDMRelayList uint16 = 10050
|
||||
KindUserServerList uint16 = 10063
|
||||
KindFileStorageServerList uint16 = 10096
|
||||
KindGoodWikiAuthorList uint16 = 10101
|
||||
KindGoodWikiRelayList uint16 = 10102
|
||||
KindNWCWalletInfo uint16 = 13194
|
||||
KindLightningPubRPC uint16 = 21000
|
||||
KindClientAuthentication uint16 = 22242
|
||||
KindNWCWalletRequest uint16 = 23194
|
||||
KindNWCWalletResponse uint16 = 23195
|
||||
KindNostrConnect uint16 = 24133
|
||||
KindBlobs uint16 = 24242
|
||||
KindHTTPAuth uint16 = 27235
|
||||
KindCategorizedPeopleList uint16 = 30000
|
||||
KindCategorizedBookmarksList uint16 = 30001
|
||||
KindRelaySets uint16 = 30002
|
||||
KindBookmarkSets uint16 = 30003
|
||||
KindCuratedSets uint16 = 30004
|
||||
KindCuratedVideoSets uint16 = 30005
|
||||
KindMuteSets uint16 = 30007
|
||||
KindProfileBadges uint16 = 30008
|
||||
KindBadgeDefinition uint16 = 30009
|
||||
KindInterestSets uint16 = 30015
|
||||
KindStallDefinition uint16 = 30017
|
||||
KindProductDefinition uint16 = 30018
|
||||
KindMarketplaceUI uint16 = 30019
|
||||
KindProductSoldAsAuction uint16 = 30020
|
||||
KindArticle uint16 = 30023
|
||||
KindDraftArticle uint16 = 30024
|
||||
KindEmojiSets uint16 = 30030
|
||||
KindModularArticleHeader uint16 = 30040
|
||||
KindModularArticleContent uint16 = 30041
|
||||
KindReleaseArtifactSets uint16 = 30063
|
||||
KindApplicationSpecificData uint16 = 30078
|
||||
KindLiveEvent uint16 = 30311
|
||||
KindUserStatuses uint16 = 30315
|
||||
KindClassifiedListing uint16 = 30402
|
||||
KindDraftClassifiedListing uint16 = 30403
|
||||
KindRepositoryAnnouncement uint16 = 30617
|
||||
KindRepositoryState uint16 = 30618
|
||||
KindSimpleGroupMetadata uint16 = 39000
|
||||
KindSimpleGroupAdmins uint16 = 39001
|
||||
KindSimpleGroupMembers uint16 = 39002
|
||||
KindSimpleGroupRoles uint16 = 39003
|
||||
KindWikiArticle uint16 = 30818
|
||||
KindRedirects uint16 = 30819
|
||||
KindFeed uint16 = 31890
|
||||
KindDateCalendarEvent uint16 = 31922
|
||||
KindTimeCalendarEvent uint16 = 31923
|
||||
KindCalendar uint16 = 31924
|
||||
KindCalendarEventRSVP uint16 = 31925
|
||||
KindHandlerRecommendation uint16 = 31989
|
||||
KindHandlerInformation uint16 = 31990
|
||||
KindVideoEvent uint16 = 34235
|
||||
KindShortVideoEvent uint16 = 34236
|
||||
KindVideoViewEvent uint16 = 34237
|
||||
KindCommunityDefinition uint16 = 34550
|
||||
)
|
||||
|
||||
func IsRegularKind(kind int) bool {
|
||||
func IsRegularKind(kind uint16) bool {
|
||||
return kind < 10000 && kind != 0 && kind != 3
|
||||
}
|
||||
|
||||
func IsReplaceableKind(kind int) bool {
|
||||
func IsReplaceableKind(kind uint16) bool {
|
||||
return kind == 0 || kind == 3 || (10000 <= kind && kind < 20000)
|
||||
}
|
||||
|
||||
func IsEphemeralKind(kind int) bool {
|
||||
func IsEphemeralKind(kind uint16) bool {
|
||||
return 20000 <= kind && kind < 30000
|
||||
}
|
||||
|
||||
func IsAddressableKind(kind int) bool {
|
||||
func IsAddressableKind(kind uint16) bool {
|
||||
return 30000 <= kind && kind < 40000
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip10
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import "fiatjaf.com/nostrlib"
|
||||
|
||||
func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer {
|
||||
for _, tag := range tags {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// Fetch fetches the NIP-11 metadata for a relay.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
nostr "github.com/nbd-wtf/go-nostr"
|
||||
nostr "fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
nostr "github.com/nbd-wtf/go-nostr"
|
||||
nostr "fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip14
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import "fiatjaf.com/nostrlib"
|
||||
|
||||
func GetSubject(tags nostr.Tags) string {
|
||||
for _, tag := range tags {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip59"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip59"
|
||||
)
|
||||
|
||||
func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil/bech32"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip19
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package nip22
|
||||
|
||||
import (
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip73"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
)
|
||||
|
||||
func GetThreadRoot(tags nostr.Tags) nostr.Pointer {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type GroupAddress struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip29
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip31
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import "fiatjaf.com/nostrlib"
|
||||
|
||||
func GetAlt(event nostr.Event) string {
|
||||
for _, tag := range event.Tags {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bluekeyes/go-gitdiff/gitdiff"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Patch struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip34
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type RepositoryState struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip40
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip45
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used
|
||||
|
||||
@@ -3,9 +3,9 @@ package nip46
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
var _ Signer = (*DynamicSigner)(nil)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigFastest
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
var _ Signer = (*StaticKeySigner)(nil)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip05"
|
||||
"fiatjaf.com/nostrlib/nip05"
|
||||
)
|
||||
|
||||
func queryWellKnownNostrJson(ctx context.Context, fullname string) (pubkey string, relays []string, err error) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type CalendarEventKind int
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type LiveEvent struct {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip44"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
// GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type HistoryEntry struct {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
type lightningSwapStatus int
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
type receiveSettings struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
func (w *Wallet) SendExternal(
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut11"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
type SendOption func(opts *sendSettings)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut03"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
)
|
||||
|
||||
type swapSettings struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/keyer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip60"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60"
|
||||
)
|
||||
|
||||
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip73
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import "fiatjaf.com/nostr"
|
||||
|
||||
var _ nostr.Pointer = (*ExternalPointer)(nil)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package negentropy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
func (n *Negentropy) readTimestamp(reader *StringHexReader) (nostr.Timestamp, error) {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
)
|
||||
|
||||
type Accumulator struct {
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage"
|
||||
)
|
||||
|
||||
type Vector struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
const FingerprintSize = 16
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
)
|
||||
|
||||
type direction struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
func DecodeRequest(req Request) (MethodParams, error) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
type IMeta []IMetaEntry
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip92
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip94
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
func ParseFileMetadata(event nostr.Event) FileMetadata {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// Upload uploads a file to the provided req.Host.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
// UploadRequest is a NIP96 upload request.
|
||||
|
||||
67
pointers.go
67
pointers.go
@@ -1,6 +1,7 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,15 +31,15 @@ var (
|
||||
|
||||
// ProfilePointer represents a pointer to a Nostr profile.
|
||||
type ProfilePointer struct {
|
||||
PublicKey string `json:"pubkey"`
|
||||
PublicKey PubKey `json:"pubkey"`
|
||||
Relays []string `json:"relays,omitempty"`
|
||||
}
|
||||
|
||||
// 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]
|
||||
if !IsValidPublicKey(pk) {
|
||||
return ProfilePointer{}, fmt.Errorf("invalid pubkey '%s'", pk)
|
||||
pk, err := PubKeyFromHex(refTag[1])
|
||||
if err != nil {
|
||||
return ProfilePointer{}, err
|
||||
}
|
||||
|
||||
pointer := ProfilePointer{
|
||||
@@ -54,29 +55,29 @@ func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
|
||||
|
||||
// MatchesEvent checks if the pointer matches an event.
|
||||
func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false }
|
||||
func (ep ProfilePointer) AsTagReference() string { return ep.PublicKey }
|
||||
func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []string{ep.PublicKey}} }
|
||||
func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []PubKey{ep.PublicKey}} }
|
||||
func (ep ProfilePointer) AsTagReference() string { return hex.EncodeToString(ep.PublicKey[:]) }
|
||||
|
||||
func (ep ProfilePointer) AsTag() Tag {
|
||||
if len(ep.Relays) > 0 {
|
||||
return Tag{"p", ep.PublicKey, ep.Relays[0]}
|
||||
return Tag{"p", hex.EncodeToString(ep.PublicKey[:]), ep.Relays[0]}
|
||||
}
|
||||
return Tag{"p", ep.PublicKey}
|
||||
return Tag{"p", hex.EncodeToString(ep.PublicKey[:])}
|
||||
}
|
||||
|
||||
// EventPointer represents a pointer to a nostr event.
|
||||
type EventPointer struct {
|
||||
ID string `json:"id"`
|
||||
ID ID `json:"id"`
|
||||
Relays []string `json:"relays,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Kind int `json:"kind,omitempty"`
|
||||
Author PubKey `json:"author,omitempty"`
|
||||
Kind uint16 `json:"kind,omitempty"`
|
||||
}
|
||||
|
||||
// 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]
|
||||
if !IsValid32ByteHex(id) {
|
||||
return EventPointer{}, fmt.Errorf("invalid id '%s'", id)
|
||||
id, err := IDFromHex(refTag[1])
|
||||
if err != nil {
|
||||
return EventPointer{}, err
|
||||
}
|
||||
|
||||
pointer := EventPointer{
|
||||
@@ -86,35 +87,35 @@ func EventPointerFromTag(refTag Tag) (EventPointer, error) {
|
||||
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
||||
pointer.Relays = []string{relay}
|
||||
}
|
||||
if len(refTag) > 3 && IsValidPublicKey(refTag[3]) {
|
||||
pointer.Author = (refTag)[3]
|
||||
} else if len(refTag) > 4 && IsValidPublicKey(refTag[4]) {
|
||||
pointer.Author = (refTag)[4]
|
||||
if len(refTag) > 3 {
|
||||
if pk, err := PubKeyFromHex(refTag[3]); err == nil {
|
||||
pointer.Author = pk
|
||||
}
|
||||
}
|
||||
}
|
||||
return pointer, nil
|
||||
}
|
||||
|
||||
func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID }
|
||||
func (ep EventPointer) AsTagReference() string { return ep.ID }
|
||||
func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []string{ep.ID}} }
|
||||
func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []ID{ep.ID}} }
|
||||
func (ep EventPointer) AsTagReference() string { return hex.EncodeToString(ep.ID[:]) }
|
||||
|
||||
// AsTag converts the pointer to a Tag.
|
||||
func (ep EventPointer) AsTag() Tag {
|
||||
if len(ep.Relays) > 0 {
|
||||
if ep.Author != "" {
|
||||
return Tag{"e", ep.ID, ep.Relays[0], ep.Author}
|
||||
if ep.Author != [32]byte{} {
|
||||
return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0], hex.EncodeToString(ep.Author[:])}
|
||||
} else {
|
||||
return Tag{"e", ep.ID, ep.Relays[0]}
|
||||
return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0]}
|
||||
}
|
||||
}
|
||||
return Tag{"e", ep.ID}
|
||||
return Tag{"e", hex.EncodeToString(ep.ID[:])}
|
||||
}
|
||||
|
||||
// EntityPointer represents a pointer to a nostr entity (addressable event).
|
||||
type EntityPointer struct {
|
||||
PublicKey string `json:"pubkey"`
|
||||
Kind int `json:"kind,omitempty"`
|
||||
PublicKey PubKey `json:"pubkey"`
|
||||
Kind uint16 `json:"kind,omitempty"`
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
Relays []string `json:"relays,omitempty"`
|
||||
}
|
||||
@@ -125,8 +126,10 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
||||
if len(spl) != 3 {
|
||||
return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1])
|
||||
}
|
||||
if !IsValidPublicKey(spl[1]) {
|
||||
return EntityPointer{}, fmt.Errorf("invalid addr pubkey '%s'", spl[1])
|
||||
|
||||
pk, err := PubKeyFromHex(spl[1])
|
||||
if err != nil {
|
||||
return EntityPointer{}, err
|
||||
}
|
||||
|
||||
kind, err := strconv.Atoi(spl[0])
|
||||
@@ -135,8 +138,8 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
||||
}
|
||||
|
||||
pointer := EntityPointer{
|
||||
Kind: kind,
|
||||
PublicKey: spl[1],
|
||||
Kind: uint16(kind),
|
||||
PublicKey: pk,
|
||||
Identifier: spl[2],
|
||||
}
|
||||
if len(refTag) > 2 {
|
||||
@@ -161,8 +164,8 @@ func (ep EntityPointer) AsTagReference() string {
|
||||
|
||||
func (ep EntityPointer) AsFilter() Filter {
|
||||
return Filter{
|
||||
Kinds: []int{ep.Kind},
|
||||
Authors: []string{ep.PublicKey},
|
||||
Kinds: []uint16{ep.Kind},
|
||||
Authors: []PubKey{ep.PublicKey},
|
||||
Tags: TagMap{"d": []string{ep.Identifier}},
|
||||
}
|
||||
}
|
||||
|
||||
103
pool.go
103
pool.go
@@ -12,7 +12,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip45/hyperloglog"
|
||||
"fiatjaf.com/nostr/nip45/hyperloglog"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
@@ -29,8 +29,8 @@ type SimplePool struct {
|
||||
cancel context.CancelCauseFunc
|
||||
|
||||
eventMiddleware func(RelayEvent)
|
||||
duplicateMiddleware func(relay string, id string)
|
||||
queryMiddleware func(relay string, pubkey string, kind int)
|
||||
duplicateMiddleware func(relay string, id ID)
|
||||
queryMiddleware func(relay string, pubkey PubKey, kind uint16)
|
||||
|
||||
// custom things not often used
|
||||
penaltyBoxMu sync.Mutex
|
||||
@@ -139,7 +139,7 @@ func (h WithEventMiddleware) ApplyPoolOption(pool *SimplePool) {
|
||||
}
|
||||
|
||||
// WithDuplicateMiddleware is a function that will be called with all duplicate ids received.
|
||||
type WithDuplicateMiddleware func(relay string, id string)
|
||||
type WithDuplicateMiddleware func(relay string, id ID)
|
||||
|
||||
func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) {
|
||||
pool.duplicateMiddleware = h
|
||||
@@ -147,7 +147,7 @@ func (h WithDuplicateMiddleware) ApplyPoolOption(pool *SimplePool) {
|
||||
|
||||
// WithAuthorKindQueryMiddleware is a function that will be called with every combination of relay+pubkey+kind queried
|
||||
// in a .SubMany*() call -- when applicable (i.e. when the query contains a pubkey and a kind).
|
||||
type WithAuthorKindQueryMiddleware func(relay string, pubkey string, kind int)
|
||||
type WithAuthorKindQueryMiddleware func(relay string, pubkey PubKey, kind uint16)
|
||||
|
||||
func (h WithAuthorKindQueryMiddleware) ApplyPoolOption(pool *SimplePool) {
|
||||
pool.queryMiddleware = h
|
||||
@@ -271,7 +271,7 @@ func (pool *SimplePool) SubscribeMany(
|
||||
filter Filter,
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
return pool.subMany(ctx, urls, Filters{filter}, nil, opts...)
|
||||
return pool.subMany(ctx, urls, filter, nil, opts...)
|
||||
}
|
||||
|
||||
// FetchMany opens a subscription, much like SubscribeMany, but it ends as soon as all Relays
|
||||
@@ -282,17 +282,7 @@ func (pool *SimplePool) FetchMany(
|
||||
filter Filter,
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
return pool.SubManyEose(ctx, urls, Filters{filter}, opts...)
|
||||
}
|
||||
|
||||
// Deprecated: SubMany is deprecated: use SubscribeMany instead.
|
||||
func (pool *SimplePool) SubMany(
|
||||
ctx context.Context,
|
||||
urls []string,
|
||||
filters Filters,
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
return pool.subMany(ctx, urls, filters, nil, opts...)
|
||||
return pool.SubManyEose(ctx, urls, filter, opts...)
|
||||
}
|
||||
|
||||
// SubscribeManyNotifyEOSE is like SubscribeMany, but takes a channel that is closed when
|
||||
@@ -304,11 +294,11 @@ func (pool *SimplePool) SubscribeManyNotifyEOSE(
|
||||
eoseChan chan struct{},
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
return pool.subMany(ctx, urls, Filters{filter}, eoseChan, opts...)
|
||||
return pool.subMany(ctx, urls, filter, eoseChan, opts...)
|
||||
}
|
||||
|
||||
type ReplaceableKey struct {
|
||||
PubKey string
|
||||
PubKey PubKey
|
||||
D string
|
||||
}
|
||||
|
||||
@@ -363,7 +353,7 @@ func (pool *SimplePool) FetchManyReplaceable(
|
||||
hasAuthed := false
|
||||
|
||||
subscribe:
|
||||
sub, err := relay.Subscribe(ctx, Filters{filter}, opts...)
|
||||
sub, err := relay.Subscribe(ctx, filter, opts...)
|
||||
if err != nil {
|
||||
debugLogf("error subscribing to %s with %v: %s", relay, filter, err)
|
||||
return
|
||||
@@ -414,14 +404,14 @@ func (pool *SimplePool) FetchManyReplaceable(
|
||||
func (pool *SimplePool) subMany(
|
||||
ctx context.Context,
|
||||
urls []string,
|
||||
filters Filters,
|
||||
filter Filter,
|
||||
eoseChan chan struct{},
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
_ = cancel // do this so `go vet` will stop complaining
|
||||
events := make(chan RelayEvent)
|
||||
seenAlready := xsync.NewMapOf[string, Timestamp]()
|
||||
seenAlready := xsync.NewMapOf[ID, Timestamp]()
|
||||
ticker := time.NewTicker(seenAlreadyDropTick)
|
||||
|
||||
eoseWg := sync.WaitGroup{}
|
||||
@@ -471,12 +461,10 @@ func (pool *SimplePool) subMany(
|
||||
var sub *Subscription
|
||||
|
||||
if mh := pool.queryMiddleware; mh != nil {
|
||||
for _, filter := range filters {
|
||||
if filter.Kinds != nil && filter.Authors != nil {
|
||||
for _, kind := range filter.Kinds {
|
||||
for _, author := range filter.Authors {
|
||||
mh(nm, author, kind)
|
||||
}
|
||||
if filter.Kinds != nil && filter.Authors != nil {
|
||||
for _, kind := range filter.Kinds {
|
||||
for _, author := range filter.Authors {
|
||||
mh(nm, author, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,13 +485,15 @@ func (pool *SimplePool) subMany(
|
||||
hasAuthed = false
|
||||
|
||||
subscribe:
|
||||
sub, err = relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(id, relay string) bool {
|
||||
_, exists := seenAlready.Load(id)
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
}
|
||||
return exists
|
||||
}))...)
|
||||
sub, err = relay.Subscribe(ctx, filter, append(opts,
|
||||
WithCheckDuplicate(func(id ID, relay string) bool {
|
||||
_, exists := seenAlready.Load(id)
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
}
|
||||
return exists
|
||||
}),
|
||||
)...)
|
||||
if err != nil {
|
||||
debugLogf("%s reconnecting because subscription died\n", nm)
|
||||
goto reconnect
|
||||
@@ -529,9 +519,7 @@ func (pool *SimplePool) subMany(
|
||||
// so we will update the filters here to include only events seem from now on
|
||||
// and try to reconnect until we succeed
|
||||
now := Now()
|
||||
for i := range filters {
|
||||
filters[i].Since = &now
|
||||
}
|
||||
filter.Since = &now
|
||||
debugLogf("%s reconnecting because sub.Events is broken\n", nm)
|
||||
goto reconnect
|
||||
}
|
||||
@@ -591,25 +579,26 @@ func (pool *SimplePool) subMany(
|
||||
func (pool *SimplePool) SubManyEose(
|
||||
ctx context.Context,
|
||||
urls []string,
|
||||
filters Filters,
|
||||
filter Filter,
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
seenAlready := xsync.NewMapOf[string, struct{}]()
|
||||
return pool.subManyEoseNonOverwriteCheckDuplicate(ctx, urls, filters,
|
||||
WithCheckDuplicate(func(id, relay string) bool {
|
||||
seenAlready := xsync.NewMapOf[ID, struct{}]()
|
||||
return pool.subManyEoseNonOverwriteCheckDuplicate(ctx, urls, filter,
|
||||
WithCheckDuplicate(func(id ID, relay string) bool {
|
||||
_, exists := seenAlready.LoadOrStore(id, struct{}{})
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
}
|
||||
return exists
|
||||
}),
|
||||
opts...)
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
|
||||
ctx context.Context,
|
||||
urls []string,
|
||||
filters Filters,
|
||||
filter Filter,
|
||||
wcd WithCheckDuplicate,
|
||||
opts ...SubscriptionOption,
|
||||
) chan RelayEvent {
|
||||
@@ -633,12 +622,10 @@ func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
|
||||
defer wg.Done()
|
||||
|
||||
if mh := pool.queryMiddleware; mh != nil {
|
||||
for _, filter := range filters {
|
||||
if filter.Kinds != nil && filter.Authors != nil {
|
||||
for _, kind := range filter.Kinds {
|
||||
for _, author := range filter.Authors {
|
||||
mh(nm, author, kind)
|
||||
}
|
||||
if filter.Kinds != nil && filter.Authors != nil {
|
||||
for _, kind := range filter.Kinds {
|
||||
for _, author := range filter.Authors {
|
||||
mh(nm, author, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -646,16 +633,16 @@ func (pool *SimplePool) subManyEoseNonOverwriteCheckDuplicate(
|
||||
|
||||
relay, err := pool.EnsureRelay(nm)
|
||||
if err != nil {
|
||||
debugLogf("error connecting to %s with %v: %s", nm, filters, err)
|
||||
debugLogf("error connecting to %s with %v: %s", nm, filter, err)
|
||||
return
|
||||
}
|
||||
|
||||
hasAuthed := false
|
||||
|
||||
subscribe:
|
||||
sub, err := relay.Subscribe(ctx, filters, opts...)
|
||||
sub, err := relay.Subscribe(ctx, filter, opts...)
|
||||
if err != nil {
|
||||
debugLogf("error subscribing to %s with %v: %s", relay, filters, err)
|
||||
debugLogf("error subscribing to %s with %v: %s", relay, filter, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -719,7 +706,7 @@ func (pool *SimplePool) CountMany(
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ce, err := relay.countInternal(ctx, Filters{filter}, opts...)
|
||||
ce, err := relay.countInternal(ctx, filter, opts...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -742,7 +729,7 @@ func (pool *SimplePool) QuerySingle(
|
||||
opts ...SubscriptionOption,
|
||||
) *RelayEvent {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
for ievt := range pool.SubManyEose(ctx, urls, Filters{filter}, opts...) {
|
||||
for ievt := range pool.SubManyEose(ctx, urls, filter, opts...) {
|
||||
cancel(errors.New("got the first event and ended successfully"))
|
||||
return &ievt
|
||||
}
|
||||
@@ -759,14 +746,14 @@ func (pool *SimplePool) BatchedSubManyEose(
|
||||
res := make(chan RelayEvent)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(dfs))
|
||||
seenAlready := xsync.NewMapOf[string, struct{}]()
|
||||
seenAlready := xsync.NewMapOf[ID, struct{}]()
|
||||
|
||||
for _, df := range dfs {
|
||||
go func(df DirectedFilter) {
|
||||
for ie := range pool.subManyEoseNonOverwriteCheckDuplicate(ctx,
|
||||
[]string{df.Relay},
|
||||
Filters{df.Filter},
|
||||
WithCheckDuplicate(func(id, relay string) bool {
|
||||
df.Filter,
|
||||
WithCheckDuplicate(func(id ID, relay string) bool {
|
||||
_, exists := seenAlready.LoadOrStore(id, struct{}{})
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
|
||||
7
relay.go
7
relay.go
@@ -36,7 +36,7 @@ type Relay struct {
|
||||
challenge string // NIP-42 challenge, we only keep the last
|
||||
noticeHandler func(string) // NIP-01 NOTICEs
|
||||
customHandler func(string) // nonstandard unparseable messages
|
||||
okCallbacks *xsync.MapOf[string, func(bool, string)]
|
||||
okCallbacks *xsync.MapOf[ID, func(bool, string)]
|
||||
writeQueue chan writeRequest
|
||||
subscriptionChannelCloseQueue chan *Subscription
|
||||
|
||||
@@ -58,7 +58,7 @@ func NewRelay(ctx context.Context, url string, opts ...RelayOption) *Relay {
|
||||
connectionContext: ctx,
|
||||
connectionContextCancel: cancel,
|
||||
Subscriptions: xsync.NewMapOf[int64, *Subscription](),
|
||||
okCallbacks: xsync.NewMapOf[string, func(bool, string)](),
|
||||
okCallbacks: xsync.NewMapOf[ID, func(bool, string)](),
|
||||
writeQueue: make(chan writeRequest),
|
||||
subscriptionChannelCloseQueue: make(chan *Subscription),
|
||||
requestHeader: nil,
|
||||
@@ -351,7 +351,8 @@ func (r *Relay) Auth(ctx context.Context, sign func(event *Event) error) error {
|
||||
return r.publish(ctx, authEvent.ID, &AuthEnvelope{Event: authEvent})
|
||||
}
|
||||
|
||||
func (r *Relay) publish(ctx context.Context, id string, env Envelope) error {
|
||||
// publish can be used both for EVENT and for AUTH
|
||||
func (r *Relay) publish(ctx context.Context, id ID, env Envelope) error {
|
||||
var err error
|
||||
var cancel context.CancelFunc
|
||||
|
||||
|
||||
@@ -57,12 +57,11 @@ func TestPublish(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func makeKeyPair(t *testing.T) (priv, pub string) {
|
||||
func makeKeyPair(t *testing.T) (priv, pub [32]byte) {
|
||||
t.Helper()
|
||||
|
||||
privkey := GeneratePrivateKey()
|
||||
pubkey, err := GetPublicKey(privkey)
|
||||
assert.NoError(t, err)
|
||||
pubkey := GetPublicKey(privkey)
|
||||
|
||||
return privkey, pubkey
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestPublishBlocked(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send back a not ok nip-20 command result
|
||||
res := []any{"OK", textNote.ID, false, "blocked"}
|
||||
res := []any{"OK", textNote.ID.String(), false, "blocked"}
|
||||
websocket.JSON.Send(conn, res)
|
||||
})
|
||||
defer ws.Close()
|
||||
@@ -175,12 +175,11 @@ var anyOriginHandshake = func(conf *websocket.Config, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeKeyPair(t *testing.T) (priv, pub string) {
|
||||
func makeKeyPair(t *testing.T) (priv, pub [32]byte) {
|
||||
t.Helper()
|
||||
|
||||
privkey := GeneratePrivateKey()
|
||||
pubkey, err := GetPublicKey(privkey)
|
||||
assert.NoError(t, err)
|
||||
pubkey := GetPublicKey(privkey)
|
||||
|
||||
return privkey, pubkey
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/sdk/dataloader"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk/dataloader"
|
||||
)
|
||||
|
||||
// this is similar to replaceable_loader and reuses logic from that.
|
||||
@@ -21,16 +21,16 @@ const (
|
||||
)
|
||||
|
||||
func (sys *System) initializeAddressableDataloaders() {
|
||||
sys.addressableLoaders = make([]*dataloader.Loader[string, []*nostr.Event], 4)
|
||||
sys.addressableLoaders = make([]*dataloader.Loader[nostr.PubKey, []*nostr.Event], 4)
|
||||
sys.addressableLoaders[kind_30000] = sys.createAddressableDataloader(30000)
|
||||
sys.addressableLoaders[kind_30002] = sys.createAddressableDataloader(30002)
|
||||
sys.addressableLoaders[kind_30015] = sys.createAddressableDataloader(30015)
|
||||
sys.addressableLoaders[kind_30030] = sys.createAddressableDataloader(30030)
|
||||
}
|
||||
|
||||
func (sys *System) createAddressableDataloader(kind int) *dataloader.Loader[string, []*nostr.Event] {
|
||||
func (sys *System) createAddressableDataloader(kind uint16) *dataloader.Loader[nostr.PubKey, []*nostr.Event] {
|
||||
return dataloader.NewBatchedLoader(
|
||||
func(ctxs []context.Context, pubkeys []string) map[string]dataloader.Result[[]*nostr.Event] {
|
||||
func(ctxs []context.Context, pubkeys []nostr.PubKey) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] {
|
||||
return sys.batchLoadAddressableEvents(ctxs, kind, pubkeys)
|
||||
},
|
||||
dataloader.Options{
|
||||
@@ -42,11 +42,11 @@ func (sys *System) createAddressableDataloader(kind int) *dataloader.Loader[stri
|
||||
|
||||
func (sys *System) batchLoadAddressableEvents(
|
||||
ctxs []context.Context,
|
||||
kind int,
|
||||
pubkeys []string,
|
||||
) map[string]dataloader.Result[[]*nostr.Event] {
|
||||
kind uint16,
|
||||
pubkeys []nostr.PubKey,
|
||||
) map[nostr.PubKey]dataloader.Result[[]*nostr.Event] {
|
||||
batchSize := len(pubkeys)
|
||||
results := make(map[string]dataloader.Result[[]*nostr.Event], batchSize)
|
||||
results := make(map[nostr.PubKey]dataloader.Result[[]*nostr.Event], batchSize)
|
||||
relayFilter := make([]nostr.DirectedFilter, 0, max(3, batchSize*2))
|
||||
relayFilterIndex := make(map[string]int, max(3, batchSize*2))
|
||||
|
||||
@@ -62,7 +62,7 @@ func (sys *System) batchLoadAddressableEvents(
|
||||
defer cancel()
|
||||
|
||||
// build batched queries for the external relays
|
||||
go func(i int, pubkey string) {
|
||||
go func(i int, pubkey nostr.PubKey) {
|
||||
// gather relays we'll use for this pubkey
|
||||
relays := sys.determineRelaysToQuery(ctx, pubkey, kind)
|
||||
|
||||
@@ -77,8 +77,8 @@ func (sys *System) batchLoadAddressableEvents(
|
||||
dfilter = nostr.DirectedFilter{
|
||||
Relay: relay,
|
||||
Filter: nostr.Filter{
|
||||
Kinds: []int{kind},
|
||||
Authors: make([]string, 0, batchSize-i /* this and all pubkeys after this can be added */),
|
||||
Kinds: []uint16{kind},
|
||||
Authors: make([]nostr.PubKey, 0, batchSize-i /* this and all pubkeys after this can be added */),
|
||||
},
|
||||
}
|
||||
idx = len(relayFilter)
|
||||
|
||||
8
sdk/cache/interface.go
vendored
8
sdk/cache/interface.go
vendored
@@ -3,8 +3,8 @@ package cache
|
||||
import "time"
|
||||
|
||||
type Cache32[V any] interface {
|
||||
Get(k string) (v V, ok bool)
|
||||
Delete(k string)
|
||||
Set(k string, v V) bool
|
||||
SetWithTTL(k string, v V, d time.Duration) bool
|
||||
Get(k [32]byte) (v V, ok bool)
|
||||
Delete(k [32]byte)
|
||||
Set(k [32]byte, v V) bool
|
||||
SetWithTTL(k [32]byte, v V, d time.Duration) bool
|
||||
}
|
||||
|
||||
38
sdk/cache/memory/cache.go
vendored
38
sdk/cache/memory/cache.go
vendored
@@ -2,47 +2,33 @@ package cache_memory
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/ristretto"
|
||||
)
|
||||
|
||||
type RistrettoCache[V any] struct {
|
||||
Cache *ristretto.Cache[string, V]
|
||||
Cache *ristretto.Cache[uint64, V]
|
||||
}
|
||||
|
||||
func New32[V any](max int64) *RistrettoCache[V] {
|
||||
cache, _ := ristretto.NewCache(&ristretto.Config[string, V]{
|
||||
func New[V any](max int64) *RistrettoCache[V] {
|
||||
cache, _ := ristretto.NewCache(&ristretto.Config[uint64, V]{
|
||||
NumCounters: max * 10,
|
||||
MaxCost: max,
|
||||
BufferItems: 64,
|
||||
KeyToHash: func(key string) (uint64, uint64) { return h32(key), 0 },
|
||||
KeyToHash: func(key uint64) (uint64, uint64) { return key, 0 },
|
||||
})
|
||||
return &RistrettoCache[V]{Cache: cache}
|
||||
}
|
||||
|
||||
func (s RistrettoCache[V]) Get(k string) (v V, ok bool) { return s.Cache.Get(k) }
|
||||
func (s RistrettoCache[V]) Delete(k string) { s.Cache.Del(k) }
|
||||
func (s RistrettoCache[V]) Set(k string, v V) bool { return s.Cache.Set(k, v, 1) }
|
||||
func (s RistrettoCache[V]) SetWithTTL(k string, v V, d time.Duration) bool {
|
||||
return s.Cache.SetWithTTL(k, v, 1, d)
|
||||
func (s RistrettoCache[V]) Get(k [32]byte) (v V, ok bool) {
|
||||
return s.Cache.Get(binary.BigEndian.Uint64(k[32-8:]))
|
||||
}
|
||||
func (s RistrettoCache[V]) Delete(k [32]byte) { s.Cache.Del(binary.BigEndian.Uint64(k[32-8:])) }
|
||||
func (s RistrettoCache[V]) Set(k [32]byte, v V) bool {
|
||||
return s.Cache.Set(binary.BigEndian.Uint64(k[32-8:]), v, 1)
|
||||
}
|
||||
|
||||
func h32(key string) uint64 {
|
||||
// we get an event id or pubkey as hex,
|
||||
// so just extract the last 8 bytes from it and turn them into a uint64
|
||||
return shortUint64(key)
|
||||
}
|
||||
|
||||
func shortUint64(idOrPubkey string) uint64 {
|
||||
length := len(idOrPubkey)
|
||||
if length < 8 {
|
||||
return 0
|
||||
}
|
||||
b, err := hex.DecodeString(idOrPubkey[length-8:])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uint64(binary.BigEndian.Uint32(b))
|
||||
func (s RistrettoCache[V]) SetWithTTL(k [32]byte, v V, d time.Duration) bool {
|
||||
return s.Cache.SetWithTTL(binary.BigEndian.Uint64(k[32-8:]), v, 1, d)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/sdk/kvstore"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk/kvstore"
|
||||
)
|
||||
|
||||
const eventRelayPrefix = byte('r')
|
||||
|
||||
// makeEventRelayKey creates a key for storing event relay information.
|
||||
// It uses the first 8 bytes of the event ID to create a compact key.
|
||||
func makeEventRelayKey(eventID []byte) []byte {
|
||||
func makeEventRelayKey(id nostr.ID) []byte {
|
||||
// format: 'r' + first 8 bytes of event ID
|
||||
key := make([]byte, 9)
|
||||
key[0] = eventRelayPrefix
|
||||
copy(key[1:], eventID[:8])
|
||||
copy(key[1:], id[:8])
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -75,15 +74,9 @@ func decodeRelayList(data []byte) []string {
|
||||
|
||||
// trackEventRelay records that an event was seen on a particular relay.
|
||||
// If onlyIfItExists is true, it will only update existing records and not create new ones.
|
||||
func (sys *System) trackEventRelay(eventID string, relay string, onlyIfItExists bool) {
|
||||
// decode the event ID hex into bytes
|
||||
idBytes, err := hex.DecodeString(eventID)
|
||||
if err != nil || len(idBytes) < 8 {
|
||||
return
|
||||
}
|
||||
|
||||
func (sys *System) trackEventRelay(id nostr.ID, relay string, onlyIfItExists bool) {
|
||||
// get the key for this event
|
||||
key := makeEventRelayKey(idBytes)
|
||||
key := makeEventRelayKey(id)
|
||||
|
||||
// update the relay list atomically
|
||||
sys.KVStore.Update(key, func(data []byte) ([]byte, error) {
|
||||
@@ -111,15 +104,9 @@ func (sys *System) trackEventRelay(eventID string, relay string, onlyIfItExists
|
||||
|
||||
// GetEventRelays returns all known relay URLs an event is known to be available on.
|
||||
// It is based on information kept on KVStore.
|
||||
func (sys *System) GetEventRelays(eventID string) ([]string, error) {
|
||||
// decode the event ID hex into bytes
|
||||
idBytes, err := hex.DecodeString(eventID)
|
||||
if err != nil || len(idBytes) < 8 {
|
||||
return nil, fmt.Errorf("invalid event id")
|
||||
}
|
||||
|
||||
func (sys *System) GetEventRelays(id nostr.ID) ([]string, error) {
|
||||
// get the key for this event
|
||||
key := makeEventRelayKey(idBytes)
|
||||
key := makeEventRelayKey(id)
|
||||
|
||||
// get stored relay list
|
||||
data, err := sys.KVStore.Get(key)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -29,7 +29,7 @@ func makePubkeyStreamKey(prefix byte, pubkey string) []byte {
|
||||
// each pubkey (stored in KVStore) onwards.
|
||||
func (sys *System) StreamLiveFeed(
|
||||
ctx context.Context,
|
||||
pubkeys []string,
|
||||
pubkeys []nostr.PubKey,
|
||||
kinds []int,
|
||||
) (<-chan *nostr.Event, error) {
|
||||
events := make(chan *nostr.Event)
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -56,9 +56,9 @@ func TestStreamLiveFeed(t *testing.T) {
|
||||
|
||||
// generate two random keypairs for testing
|
||||
sk1 := nostr.GeneratePrivateKey()
|
||||
pk1, _ := nostr.GetPublicKey(sk1)
|
||||
pk1 := nostr.GetPublicKey(sk1)
|
||||
sk2 := nostr.GeneratePrivateKey()
|
||||
pk2, _ := nostr.GetPublicKey(sk2)
|
||||
pk2 := nostr.GetPublicKey(sk2)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
@@ -131,7 +131,7 @@ func TestStreamLiveFeed(t *testing.T) {
|
||||
go sys.Pool.PublishMany(ctx, []string{"ws://localhost:48482", "ws://localhost:48483"}, evt2)
|
||||
|
||||
// start streaming events for both pubkeys
|
||||
events, err := sys.StreamLiveFeed(ctx, []string{pk1, pk2}, []int{1})
|
||||
events, err := sys.StreamLiveFeed(ctx, []nostr.PubKey{pk1, pk2}, []int{1})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start streaming: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package badgerh
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk/hints"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
||||
)
|
||||
|
||||
var _ hints.HintsDB = (*BadgerHints)(nil)
|
||||
@@ -30,7 +29,7 @@ func (bh *BadgerHints) Close() {
|
||||
bh.db.Close()
|
||||
}
|
||||
|
||||
func (bh *BadgerHints) Save(pubkey string, relay string, hintkey hints.HintKey, ts nostr.Timestamp) {
|
||||
func (bh *BadgerHints) Save(pubkey nostr.PubKey, relay string, hintkey hints.HintKey, ts nostr.Timestamp) {
|
||||
if now := nostr.Now(); ts > now {
|
||||
ts = now
|
||||
}
|
||||
@@ -64,7 +63,7 @@ func (bh *BadgerHints) Save(pubkey string, relay string, hintkey hints.HintKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (bh *BadgerHints) TopN(pubkey string, n int) []string {
|
||||
func (bh *BadgerHints) TopN(pubkey nostr.PubKey, n int) []string {
|
||||
type relayScore struct {
|
||||
relay string
|
||||
score int64
|
||||
@@ -73,7 +72,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
|
||||
scores := make([]relayScore, 0, n)
|
||||
err := bh.db.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix, _ = hex.DecodeString(pubkey)
|
||||
opts.Prefix = pubkey[:]
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
@@ -112,7 +111,7 @@ func (bh *BadgerHints) TopN(pubkey string, n int) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func (bh *BadgerHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores {
|
||||
func (bh *BadgerHints) GetDetailedScores(pubkey nostr.PubKey, n int) []hints.RelayScores {
|
||||
type relayScore struct {
|
||||
relay string
|
||||
tss timestamps
|
||||
@@ -121,11 +120,10 @@ func (bh *BadgerHints) GetDetailedScores(pubkey string, n int) []hints.RelayScor
|
||||
|
||||
scores := make([]relayScore, 0, n)
|
||||
err := bh.db.View(func(txn *badger.Txn) error {
|
||||
prefix, _ := hex.DecodeString(pubkey)
|
||||
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
for it.Seek(pubkey[:]); it.ValidForPrefix(pubkey[:]); it.Next() {
|
||||
item := it.Item()
|
||||
k := item.Key()
|
||||
relay := string(k[32:])
|
||||
@@ -172,7 +170,7 @@ func (bh *BadgerHints) PrintScores() {
|
||||
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer it.Close()
|
||||
|
||||
var lastPubkey string
|
||||
var lastPubkey nostr.PubKey
|
||||
i := 0
|
||||
|
||||
for it.Seek(nil); it.Valid(); it.Next() {
|
||||
|
||||
@@ -2,20 +2,19 @@ package badgerh
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func encodeKey(pubhintkey, relay string) []byte {
|
||||
func encodeKey(pubhintkey nostr.PubKey, relay string) []byte {
|
||||
k := make([]byte, 32+len(relay))
|
||||
hex.Decode(k[0:32], []byte(pubhintkey))
|
||||
copy(k[0:32], pubhintkey[:])
|
||||
copy(k[32:], relay)
|
||||
return k
|
||||
}
|
||||
|
||||
func parseKey(k []byte) (pubkey string, relay string) {
|
||||
pubkey = hex.EncodeToString(k[0:32])
|
||||
func parseKey(k []byte) (pubkey nostr.PubKey, relay string) {
|
||||
pubkey = [32]byte(k[0:32])
|
||||
relay = string(k[32:])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package hints
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import (
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type RelayScores struct {
|
||||
Relay string
|
||||
@@ -9,8 +11,8 @@ type RelayScores struct {
|
||||
}
|
||||
|
||||
type HintsDB interface {
|
||||
TopN(pubkey string, n int) []string
|
||||
Save(pubkey string, relay string, key HintKey, score nostr.Timestamp)
|
||||
TopN(pubkey nostr.PubKey, n int) []string
|
||||
Save(pubkey nostr.PubKey, relay string, key HintKey, score nostr.Timestamp)
|
||||
PrintScores()
|
||||
GetDetailedScores(pubkey string, n int) []RelayScores
|
||||
GetDetailedScores(pubkey nostr.PubKey, n int) []RelayScores
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package hints
|
||||
|
||||
import "github.com/nbd-wtf/go-nostr"
|
||||
import "fiatjaf.com/nostrlib"
|
||||
|
||||
const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/sdk/hints"
|
||||
)
|
||||
|
||||
var _ hints.HintsDB = (*HintDB)(nil)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user