more list fetchers.

This commit is contained in:
fiatjaf 2025-01-01 18:16:36 -03:00
parent 159e5d21e6
commit c2c08ab6bc
11 changed files with 218 additions and 99 deletions

View File

@ -30,7 +30,8 @@ func fetchGenericList[I TagItemWithValue](
sys *System, sys *System,
ctx context.Context, ctx context.Context,
pubkey string, pubkey string,
kind int, actualKind int,
replaceableIndex replaceableIndex,
parseTag func(nostr.Tag) (I, bool), parseTag func(nostr.Tag) (I, bool),
cache cache.Cache32[GenericList[I]], cache cache.Cache32[GenericList[I]],
skipFetch bool, skipFetch bool,
@ -38,7 +39,7 @@ func fetchGenericList[I TagItemWithValue](
// we have 24 mutexes, so we can load up to 24 lists at the same time, but if we do the same exact // we have 24 mutexes, so we can load up to 24 lists at the same time, but if we do the same exact
// call that will do it only once, the subsequent ones will wait for a result to be cached // call that will do it only once, the subsequent ones will wait for a result to be cached
// and then return it from cache -- 13 is an arbitrary index for the pubkey // and then return it from cache -- 13 is an arbitrary index for the pubkey
lockIdx := (int(pubkey[13]) + kind) % 24 lockIdx := (int(pubkey[13]) + int(replaceableIndex)) % 24
genericListMutexes[lockIdx].Lock() genericListMutexes[lockIdx].Lock()
if valueWasJustCached[lockIdx] { if valueWasJustCached[lockIdx] {
@ -56,7 +57,7 @@ func fetchGenericList[I TagItemWithValue](
v := GenericList[I]{PubKey: pubkey} v := GenericList[I]{PubKey: pubkey}
events, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []int{kind}, Authors: []string{pubkey}}) events, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []int{actualKind}, Authors: []string{pubkey}})
if len(events) != 0 { if len(events) != 0 {
items := parseItemsFromEventTags(events[0], parseTag) items := parseItemsFromEventTags(events[0], parseTag)
v.Event = events[0] v.Event = events[0]
@ -67,7 +68,7 @@ func fetchGenericList[I TagItemWithValue](
} }
if !skipFetch { if !skipFetch {
thunk := sys.replaceableLoaders[kind].Load(ctx, pubkey) thunk := sys.replaceableLoaders[replaceableIndex].Load(ctx, pubkey)
evt, err := thunk() evt, err := thunk()
if err == nil { if err == nil {
items := parseItemsFromEventTags(evt, parseTag) items := parseItemsFromEventTags(evt, parseTag)

67
sdk/lists_event.go Normal file
View File

@ -0,0 +1,67 @@
package sdk
import (
"context"
"strconv"
"strings"
"github.com/nbd-wtf/go-nostr"
)
type EventRef struct{ nostr.Pointer }
func (e EventRef) Value() string { return e.Pointer.AsTagReference() }
func (sys *System) FetchBookmarkList(ctx context.Context, pubkey string) GenericList[EventRef] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10003, kind_10003, parseEventRef, sys.BookmarkListCache, false)
return ml
}
func (sys *System) FetchPinList(ctx context.Context, pubkey string) GenericList[EventRef] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10001, kind_10001, parseEventRef, sys.PinListCache, false)
return ml
}
func parseEventRef(tag nostr.Tag) (evr EventRef, ok bool) {
if len(tag) < 2 {
return evr, false
}
switch tag[0] {
case "e":
if !nostr.IsValid32ByteHex(tag[1]) {
return evr, false
}
pointer := nostr.EventPointer{
ID: tag[1],
}
if len(tag) >= 3 {
pointer.Relays = []string{nostr.NormalizeURL(tag[2])}
if len(tag) >= 4 {
pointer.Author = tag[3]
}
}
evr.Pointer = pointer
case "a":
spl := strings.SplitN(tag[1], ":", 3)
if len(spl) != 3 || !nostr.IsValidPublicKey(spl[1]) {
return evr, false
}
pointer := nostr.EntityPointer{
PublicKey: spl[1],
Identifier: spl[2],
}
if kind, err := strconv.Atoi(spl[0]); err != nil {
return evr, false
} else {
pointer.Kind = kind
}
if len(tag) >= 3 {
pointer.Relays = []string{nostr.NormalizeURL(tag[2])}
}
evr.Pointer = pointer
default:
return evr, false
}
return evr, false
}

View File

@ -8,22 +8,25 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
) )
type FollowList = GenericList[Follow] type ProfileRef struct {
type Follow struct {
Pubkey string Pubkey string
Relay string Relay string
Petname string Petname string
} }
func (f Follow) Value() string { return f.Pubkey } func (f ProfileRef) Value() string { return f.Pubkey }
func (sys *System) FetchFollowList(ctx context.Context, pubkey string) FollowList { func (sys *System) FetchFollowList(ctx context.Context, pubkey string) GenericList[ProfileRef] {
fl, _ := fetchGenericList(sys, ctx, pubkey, 3, parseFollow, sys.FollowListCache, false) fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache, false)
return fl return fl
} }
func parseFollow(tag nostr.Tag) (fw Follow, ok bool) { func (sys *System) FetchMuteList(ctx context.Context, pubkey string) GenericList[ProfileRef] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, kind_10000, parseProfileRef, sys.MuteListCache, false)
return ml
}
func parseProfileRef(tag nostr.Tag) (fw ProfileRef, ok bool) {
if len(tag) < 2 { if len(tag) < 2 {
return fw, false return fw, false
} }

72
sdk/lists_relay.go Normal file
View File

@ -0,0 +1,72 @@
package sdk
import (
"context"
"github.com/nbd-wtf/go-nostr"
)
type Relay struct {
URL string
Inbox bool
Outbox bool
}
func (r Relay) Value() string { return r.URL }
type RelayURL string
func (r RelayURL) Value() string { return string(r) }
func (sys *System) FetchRelayList(ctx context.Context, pubkey string) GenericList[Relay] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10002, kind_10002, parseRelayFromKind10002, sys.RelayListCache, false)
return ml
}
func (sys *System) FetchBlockedRelayList(ctx context.Context, pubkey string) GenericList[RelayURL] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10006, kind_10006, parseRelayURL, sys.BlockedRelayListCache, false)
return ml
}
func (sys *System) FetchSearchRelayList(ctx context.Context, pubkey string) GenericList[RelayURL] {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10007, kind_10007, parseRelayURL, sys.SearchRelayListCache, false)
return ml
}
func parseRelayFromKind10002(tag nostr.Tag) (rl Relay, ok bool) {
if u := tag.Value(); u != "" && tag[0] == "r" {
if !nostr.IsValidRelayURL(u) {
return rl, false
}
u := nostr.NormalizeURL(u)
relay := Relay{
URL: u,
}
if len(tag) == 2 {
relay.Inbox = true
relay.Outbox = true
} else if tag[2] == "write" {
relay.Outbox = true
} else if tag[2] == "read" {
relay.Inbox = true
}
return relay, true
}
return rl, false
}
func parseRelayURL(tag nostr.Tag) (rl RelayURL, ok bool) {
if u := tag.Value(); u != "" && tag[0] == "relay" {
if !nostr.IsValidRelayURL(u) {
return rl, false
}
u := nostr.NormalizeURL(u)
return RelayURL(u), true
}
return rl, false
}

View File

@ -117,7 +117,7 @@ func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey string) (pm
pm.PubKey = pubkey pm.PubKey = pubkey
thunk0 := sys.replaceableLoaders[0].Load(ctx, pubkey) thunk0 := sys.replaceableLoaders[kind_0].Load(ctx, pubkey)
evt, err := thunk0() evt, err := thunk0()
if err == nil { if err == nil {
pm, _ = ParseMetadata(evt) pm, _ = ParseMetadata(evt)

View File

@ -1,10 +0,0 @@
package sdk
import "context"
type MuteList = GenericList[Follow]
func (sys *System) FetchMuteList(ctx context.Context, pubkey string) MuteList {
ml, _ := fetchGenericList(sys, ctx, pubkey, 10000, parseFollow, sys.MuteListCache, false)
return ml
}

View File

@ -18,7 +18,7 @@ func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey string, n int)
} }
// if we have it cached that means we have at least tried to fetch recently and it won't be tried again // if we have it cached that means we have at least tried to fetch recently and it won't be tried again
fetchGenericList(sys, ctx, pubkey, 10002, parseRelayFromKind10002, sys.RelayListCache, false) fetchGenericList(sys, ctx, pubkey, 10002, kind_10002, parseRelayFromKind10002, sys.RelayListCache, false)
relays := sys.Hints.TopN(pubkey, 6) relays := sys.Hints.TopN(pubkey, 6)
if len(relays) == 0 { if len(relays) == 0 {

View File

@ -1,41 +0,0 @@
package sdk
import (
"github.com/nbd-wtf/go-nostr"
)
type RelayList = GenericList[Relay]
type Relay struct {
URL string
Inbox bool
Outbox bool
}
func (r Relay) Value() string { return r.URL }
func parseRelayFromKind10002(tag nostr.Tag) (rl Relay, ok bool) {
if u := tag.Value(); u != "" && tag[0] == "r" {
if !nostr.IsValidRelayURL(u) {
return rl, false
}
u := nostr.NormalizeURL(u)
relay := Relay{
URL: u,
}
if len(tag) == 2 {
relay.Inbox = true
relay.Outbox = true
} else if tag[2] == "write" {
relay.Outbox = true
} else if tag[2] == "read" {
relay.Inbox = true
}
return relay, true
}
return rl, false
}

View File

@ -12,13 +12,38 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
) )
type replaceableIndex int
const (
kind_0 replaceableIndex = 0
kind_3 replaceableIndex = 1
kind_10000 replaceableIndex = 2
kind_10001 replaceableIndex = 3
kind_10002 replaceableIndex = 4
kind_10003 replaceableIndex = 5
kind_10004 replaceableIndex = 6
kind_10005 replaceableIndex = 7
kind_10006 replaceableIndex = 8
kind_10007 replaceableIndex = 9
kind_10015 replaceableIndex = 10
kind_10030 replaceableIndex = 11
)
type EventResult dataloader.Result[*nostr.Event] type EventResult dataloader.Result[*nostr.Event]
func (sys *System) initializeDataloaders() { func (sys *System) initializeDataloaders() {
sys.replaceableLoaders = make(map[int]*dataloader.Loader[string, *nostr.Event]) sys.replaceableLoaders[kind_0] = sys.createReplaceableDataloader(0)
for _, kind := range []int{0, 3, 10000, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10015, 10030} { sys.replaceableLoaders[kind_3] = sys.createReplaceableDataloader(3)
sys.replaceableLoaders[kind] = sys.createReplaceableDataloader(kind) sys.replaceableLoaders[kind_10000] = sys.createReplaceableDataloader(10000)
} sys.replaceableLoaders[kind_10001] = sys.createReplaceableDataloader(10001)
sys.replaceableLoaders[kind_10002] = sys.createReplaceableDataloader(10002)
sys.replaceableLoaders[kind_10003] = sys.createReplaceableDataloader(10003)
sys.replaceableLoaders[kind_10004] = sys.createReplaceableDataloader(10004)
sys.replaceableLoaders[kind_10005] = sys.createReplaceableDataloader(10005)
sys.replaceableLoaders[kind_10006] = sys.createReplaceableDataloader(10006)
sys.replaceableLoaders[kind_10007] = sys.createReplaceableDataloader(10007)
sys.replaceableLoaders[kind_10015] = sys.createReplaceableDataloader(10015)
sys.replaceableLoaders[kind_10030] = sys.createReplaceableDataloader(10030)
} }
func (sys *System) createReplaceableDataloader(kind int) *dataloader.Loader[string, *nostr.Event] { func (sys *System) createReplaceableDataloader(kind int) *dataloader.Loader[string, *nostr.Event] {

View File

@ -15,24 +15,28 @@ import (
) )
type System struct { type System struct {
RelayListCache cache.Cache32[RelayList] MetadataCache cache.Cache32[ProfileMetadata]
FollowListCache cache.Cache32[FollowList] RelayListCache cache.Cache32[GenericList[Relay]]
MuteListCache cache.Cache32[FollowList] FollowListCache cache.Cache32[GenericList[ProfileRef]]
MetadataCache cache.Cache32[ProfileMetadata] MuteListCache cache.Cache32[GenericList[ProfileRef]]
Hints hints.HintsDB BookmarkListCache cache.Cache32[GenericList[EventRef]]
Pool *nostr.SimplePool PinListCache cache.Cache32[GenericList[EventRef]]
RelayListRelays *RelayStream BlockedRelayListCache cache.Cache32[GenericList[RelayURL]]
FollowListRelays *RelayStream SearchRelayListCache cache.Cache32[GenericList[RelayURL]]
MetadataRelays *RelayStream Hints hints.HintsDB
FallbackRelays *RelayStream Pool *nostr.SimplePool
JustIDRelays *RelayStream RelayListRelays *RelayStream
UserSearchRelays *RelayStream FollowListRelays *RelayStream
NoteSearchRelays *RelayStream MetadataRelays *RelayStream
Store eventstore.Store FallbackRelays *RelayStream
JustIDRelays *RelayStream
UserSearchRelays *RelayStream
NoteSearchRelays *RelayStream
Store eventstore.Store
StoreRelay nostr.RelayStore StoreRelay nostr.RelayStore
replaceableLoaders map[int]*dataloader.Loader[string, *nostr.Event] replaceableLoaders []*dataloader.Loader[string, *nostr.Event]
outboxShortTermCache cache.Cache32[[]string] outboxShortTermCache cache.Cache32[[]string]
} }
@ -54,13 +58,17 @@ func (rs *RelayStream) Next() string {
func NewSystem(mods ...SystemModifier) *System { func NewSystem(mods ...SystemModifier) *System {
sys := &System{ sys := &System{
RelayListCache: cache_memory.New32[RelayList](1000), MetadataCache: cache_memory.New32[ProfileMetadata](8000),
FollowListCache: cache_memory.New32[FollowList](1000), RelayListCache: cache_memory.New32[GenericList[Relay]](8000),
MuteListCache: cache_memory.New32[FollowList](1000), FollowListCache: cache_memory.New32[GenericList[ProfileRef]](1000),
MetadataCache: cache_memory.New32[ProfileMetadata](1000), MuteListCache: cache_memory.New32[GenericList[ProfileRef]](1000),
RelayListRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"), BookmarkListCache: cache_memory.New32[GenericList[EventRef]](1000),
FollowListRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"), PinListCache: cache_memory.New32[GenericList[EventRef]](1000),
MetadataRelays: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"), BlockedRelayListCache: cache_memory.New32[GenericList[RelayURL]](1000),
SearchRelayListCache: cache_memory.New32[GenericList[RelayURL]](1000),
RelayListRelays: NewRelayStream("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: NewRelayStream("wss://purplepag.es", "wss://user.kindpag.es", "wss://relay.nos.social"),
FallbackRelays: NewRelayStream( FallbackRelays: NewRelayStream(
"wss://relay.damus.io", "wss://relay.damus.io",
"wss://nostr.mom", "wss://nostr.mom",
@ -165,24 +173,18 @@ func WithStore(store eventstore.Store) SystemModifier {
} }
} }
func WithRelayListCache(cache cache.Cache32[RelayList]) SystemModifier { func WithRelayListCache(cache cache.Cache32[GenericList[Relay]]) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.RelayListCache = cache sys.RelayListCache = cache
} }
} }
func WithFollowListCache(cache cache.Cache32[FollowList]) SystemModifier { func WithFollowListCache(cache cache.Cache32[GenericList[ProfileRef]]) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.FollowListCache = cache sys.FollowListCache = cache
} }
} }
func WithMuteListCache(cache cache.Cache32[FollowList]) SystemModifier {
return func(sys *System) {
sys.MuteListCache = cache
}
}
func WithMetadataCache(cache cache.Cache32[ProfileMetadata]) SystemModifier { func WithMetadataCache(cache cache.Cache32[ProfileMetadata]) SystemModifier {
return func(sys *System) { return func(sys *System) {
sys.MetadataCache = cache sys.MetadataCache = cache

View File

@ -81,7 +81,7 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
} }
} }
for _, ref := range ParseReferences(ie.Event) { for ref := range ParseReferences(*ie.Event) {
if ref.Profile != nil { if ref.Profile != nil {
for _, relay := range ref.Profile.Relays { for _, relay := range ref.Profile.Relays {
if IsVirtualRelay(relay) { if IsVirtualRelay(relay) {