sdk: FetchSpecificEvent()

This commit is contained in:
fiatjaf 2024-10-06 15:53:33 -03:00
parent f08e4e9af7
commit 36c197af42
5 changed files with 226 additions and 39 deletions

View File

@ -1,12 +1,18 @@
package sdk package sdk
import "time" import (
"slices"
"time"
)
var serial = 0 func appendUnique[I comparable](arr []I, item ...I) []I {
for _, item := range item {
func pickNext(list []string) string { if slices.Contains(arr, item) {
serial++ return arr
return list[serial%len(list)] }
arr = append(arr, item)
}
return arr
} }
func doThisNotMoreThanOnceAnHour(key string) (doItNow bool) { func doThisNotMoreThanOnceAnHour(key string) (doItNow bool) {

View File

@ -136,13 +136,13 @@ func (sys *System) determineRelaysToQuery(ctx context.Context, pubkey string, ki
var next string var next string
switch kind { switch kind {
case 0: case 0:
next = pickNext(sys.MetadataRelays) next = sys.MetadataRelays.Next()
case 3: case 3:
next = pickNext(sys.FollowListRelays) next = sys.FollowListRelays.Next()
case 10002: case 10002:
next = pickNext(sys.RelayListRelays) next = sys.RelayListRelays.Next()
default: default:
next = pickNext(sys.FallbackRelays) next = sys.FallbackRelays.Next()
} }
if !slices.Contains(relays, next) { if !slices.Contains(relays, next) {

View File

@ -8,9 +8,9 @@ import (
func (sys *System) SearchUsers(ctx context.Context, query string) []ProfileMetadata { func (sys *System) SearchUsers(ctx context.Context, query string) []ProfileMetadata {
limit := 10 limit := 10
profiles := make([]ProfileMetadata, 0, limit*len(sys.UserSearchRelays)) profiles := make([]ProfileMetadata, 0, limit*len(sys.UserSearchRelays.URLs))
for ie := range sys.Pool.SubManyEose(ctx, sys.UserSearchRelays, nostr.Filters{ for ie := range sys.Pool.SubManyEose(ctx, sys.UserSearchRelays.URLs, nostr.Filters{
{ {
Search: query, Search: query,
Limit: limit, Limit: limit,

159
sdk/specific_event.go Normal file
View File

@ -0,0 +1,159 @@
package sdk
import (
"context"
"fmt"
"slices"
"sync"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
// FetchSpecificEvent tries to get a specific event from a NIP-19 code using whatever means necessary.
func (sys *System) FetchSpecificEvent(
ctx context.Context,
code string,
withRelays bool,
) (event *nostr.Event, successRelays []string, err error) {
// this is for deciding what relays will go on nevent and nprofile later
priorityRelays := make([]string, 0, 8)
prefix, data, err := nip19.Decode(code)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode %w", err)
}
author := ""
var filter nostr.Filter
relays := make([]string, 0, 10)
fallback := make([]string, 0, 10)
successRelays = make([]string, 0, 10)
switch v := data.(type) {
case nostr.EventPointer:
author = v.Author
filter.IDs = []string{v.ID}
relays = append(relays, v.Relays...)
relays = appendUnique(relays, sys.FallbackRelays.Next())
fallback = append(fallback, sys.JustIDRelays.URLs...)
fallback = appendUnique(fallback, sys.FallbackRelays.Next())
for _, r := range v.Relays {
priorityRelays = append(priorityRelays, r)
}
case nostr.EntityPointer:
author = v.PublicKey
filter.Authors = []string{v.PublicKey}
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
filter.Kinds = []int{v.Kind}
relays = append(relays, v.Relays...)
relays = appendUnique(relays, sys.FallbackRelays.Next())
fallback = append(fallback, sys.FallbackRelays.Next(), sys.FallbackRelays.Next())
case string:
if prefix == "note" {
filter.IDs = []string{v}
relays = append(relays, sys.JustIDRelays.Next(), sys.JustIDRelays.Next())
fallback = appendUnique(fallback,
sys.FallbackRelays.Next(), sys.JustIDRelays.Next(), sys.FallbackRelays.Next())
}
}
// try to fetch in our internal eventstore first
if res, _ := sys.StoreRelay.QuerySync(ctx, filter); len(res) != 0 {
evt := res[0]
return evt, nil, nil
}
if author != "" {
// fetch relays for author
authorRelays := sys.FetchOutboxRelays(ctx, author, 3)
relays = appendUnique(relays, authorRelays...)
priorityRelays = appendUnique(priorityRelays, authorRelays...)
}
var result *nostr.Event
for _, attempt := range []struct {
label string
relays []string
nonUnique bool
}{
{
label: "fetch-" + prefix,
relays: relays,
// set this to true if the caller wants relays, so we won't return immediately
// but will instead wait a little while to see if more relays respond
nonUnique: withRelays,
},
{
label: "fetchf-" + prefix,
relays: fallback,
nonUnique: false,
},
} {
// actually fetch the event here
countdown := 6.0
subManyCtx := ctx
if attempt.nonUnique {
// keep track of where we have actually found the event so we can show that
var cancel context.CancelFunc
subManyCtx, cancel = context.WithTimeout(ctx, time.Second*6)
defer cancel()
go func() {
for {
time.Sleep(100 * time.Millisecond)
if countdown <= 0 {
cancel()
break
}
countdown -= 0.1
}
}()
}
fetchProfileOnce := sync.Once{}
for ie := range sys.Pool.SubManyEoseNonUnique(
subManyCtx,
attempt.relays,
nostr.Filters{filter},
nostr.WithLabel(attempt.label),
) {
fetchProfileOnce.Do(func() {
go sys.FetchProfileMetadata(ctx, ie.PubKey)
})
successRelays = append(successRelays, ie.Relay.URL)
if result == nil || ie.CreatedAt > result.CreatedAt {
result = ie.Event
}
countdown = min(countdown-0.5, 1)
}
}
if result == nil {
return nil, nil, fmt.Errorf("couldn't find this %s", prefix)
}
// save stuff in cache and in internal store
sys.StoreRelay.Publish(ctx, *result)
// put priority relays first so they get used in nevent and nprofile
slices.SortFunc(successRelays, func(a, b string) int {
vpa := slices.Contains(priorityRelays, a)
vpb := slices.Contains(priorityRelays, b)
if vpa == vpb {
return 1
}
if vpa && !vpb {
return 1
}
return -1
})
return result, successRelays, nil
}

View File

@ -19,12 +19,13 @@ type System struct {
MetadataCache cache.Cache32[ProfileMetadata] MetadataCache cache.Cache32[ProfileMetadata]
Hints hints.HintsDB Hints hints.HintsDB
Pool *nostr.SimplePool Pool *nostr.SimplePool
RelayListRelays []string RelayListRelays *RelayStream
FollowListRelays []string FollowListRelays *RelayStream
MetadataRelays []string MetadataRelays *RelayStream
FallbackRelays []string FallbackRelays *RelayStream
UserSearchRelays []string JustIDRelays *RelayStream
NoteSearchRelays []string UserSearchRelays *RelayStream
NoteSearchRelays *RelayStream
Store eventstore.Store Store eventstore.Store
StoreRelay nostr.RelayStore StoreRelay nostr.RelayStore
@ -35,35 +36,50 @@ type System struct {
type SystemModifier func(sys *System) type SystemModifier func(sys *System)
type RelayStream struct {
URLs []string
serial int
}
func NewRelayStream(urls ...string) *RelayStream {
return &RelayStream{URLs: urls, serial: -1}
}
func (rs *RelayStream) Next() string {
rs.serial++
return rs.URLs[rs.serial%len(rs.URLs)]
}
func NewSystem(mods ...SystemModifier) *System { func NewSystem(mods ...SystemModifier) *System {
sys := &System{ sys := &System{
RelayListCache: cache_memory.New32[RelayList](1000), RelayListCache: cache_memory.New32[RelayList](1000),
FollowListCache: cache_memory.New32[FollowList](1000), FollowListCache: cache_memory.New32[FollowList](1000),
MetadataCache: cache_memory.New32[ProfileMetadata](1000), MetadataCache: cache_memory.New32[ProfileMetadata](1000),
RelayListRelays: []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"}, RelayListRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"),
FollowListRelays: []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"}, FollowListRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"),
MetadataRelays: []string{"wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"}, MetadataRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"),
FallbackRelays: []string{ FallbackRelays: NewRelayStream(
"wss://relay.primal.net",
"wss://relay.damus.io", "wss://relay.damus.io",
"wss://nostr.wine",
"wss://nostr.mom", "wss://nostr.mom",
"wss://offchain.pub",
"wss://nos.lol", "wss://nos.lol",
"wss://mostr.pub", "wss://mostr.pub",
"wss://relay.nostr.band", "wss://relay.nostr.band",
"wss://nostr21.com", ),
}, JustIDRelays: NewRelayStream(
UserSearchRelays: []string{ "wss://cache2.primal.net/v1",
"wss://relay.noswhere.com",
"wss://relay.nostr.band",
),
UserSearchRelays: NewRelayStream(
"wss://search.nos.today",
"wss://nostr.wine", "wss://nostr.wine",
"wss://relay.nostr.band", "wss://relay.nostr.band",
"wss://relay.noswhere.com", ),
}, NoteSearchRelays: NewRelayStream(
NoteSearchRelays: []string{
"wss://nostr.wine", "wss://nostr.wine",
"wss://relay.nostr.band", "wss://relay.nostr.band",
"wss://relay.noswhere.com", "wss://search.nos.today",
}, ),
Hints: memory_hints.NewHintDB(), Hints: memory_hints.NewHintDB(),
outboxShortTermCache: cache_memory.New32[[]string](1000), outboxShortTermCache: cache_memory.New32[[]string](1000),
@ -99,37 +115,43 @@ func WithHintsDB(hdb hints.HintsDB) SystemModifier {
func WithRelayListRelays(list []string) SystemModifier { func WithRelayListRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.RelayListRelays = list sys.RelayListRelays.URLs = list
} }
} }
func WithMetadataRelays(list []string) SystemModifier { func WithMetadataRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.MetadataRelays = list sys.MetadataRelays.URLs = list
} }
} }
func WithFollowListRelays(list []string) SystemModifier { func WithFollowListRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.FollowListRelays = list sys.FollowListRelays.URLs = list
} }
} }
func WithFallbackRelays(list []string) SystemModifier { func WithFallbackRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.FallbackRelays = list sys.FallbackRelays.URLs = list
}
}
func WithJustIDRelays(list []string) SystemModifier {
return func(sys *System) {
sys.JustIDRelays.URLs = list
} }
} }
func WithUserSearchRelays(list []string) SystemModifier { func WithUserSearchRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.UserSearchRelays = list sys.UserSearchRelays.URLs = list
} }
} }
func WithNoteSearchRelays(list []string) SystemModifier { func WithNoteSearchRelays(list []string) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.NoteSearchRelays = list sys.NoteSearchRelays.URLs = list
} }
} }