diff --git a/sdk/list.go b/sdk/list.go index 7fe11be..f7a9a1c 100644 --- a/sdk/list.go +++ b/sdk/list.go @@ -30,7 +30,8 @@ func fetchGenericList[I TagItemWithValue]( sys *System, ctx context.Context, pubkey string, - kind int, + actualKind int, + replaceableIndex replaceableIndex, parseTag func(nostr.Tag) (I, bool), cache cache.Cache32[GenericList[I]], 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 // 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 - lockIdx := (int(pubkey[13]) + kind) % 24 + lockIdx := (int(pubkey[13]) + int(replaceableIndex)) % 24 genericListMutexes[lockIdx].Lock() if valueWasJustCached[lockIdx] { @@ -56,7 +57,7 @@ func fetchGenericList[I TagItemWithValue]( 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 { items := parseItemsFromEventTags(events[0], parseTag) v.Event = events[0] @@ -67,7 +68,7 @@ func fetchGenericList[I TagItemWithValue]( } if !skipFetch { - thunk := sys.replaceableLoaders[kind].Load(ctx, pubkey) + thunk := sys.replaceableLoaders[replaceableIndex].Load(ctx, pubkey) evt, err := thunk() if err == nil { items := parseItemsFromEventTags(evt, parseTag) diff --git a/sdk/lists_event.go b/sdk/lists_event.go new file mode 100644 index 0000000..fcf30cd --- /dev/null +++ b/sdk/lists_event.go @@ -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 +} diff --git a/sdk/follows.go b/sdk/lists_profile.go similarity index 55% rename from sdk/follows.go rename to sdk/lists_profile.go index 98bd32f..253e5c2 100644 --- a/sdk/follows.go +++ b/sdk/lists_profile.go @@ -8,22 +8,25 @@ import ( "github.com/nbd-wtf/go-nostr" ) -type FollowList = GenericList[Follow] - -type Follow struct { +type ProfileRef struct { Pubkey string Relay 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 { - fl, _ := fetchGenericList(sys, ctx, pubkey, 3, parseFollow, sys.FollowListCache, false) +func (sys *System) FetchFollowList(ctx context.Context, pubkey string) GenericList[ProfileRef] { + fl, _ := fetchGenericList(sys, ctx, pubkey, 3, kind_3, parseProfileRef, sys.FollowListCache, false) 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 { return fw, false } diff --git a/sdk/lists_relay.go b/sdk/lists_relay.go new file mode 100644 index 0000000..da85b47 --- /dev/null +++ b/sdk/lists_relay.go @@ -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 +} diff --git a/sdk/metadata.go b/sdk/metadata.go index 4b73eee..d1f2535 100644 --- a/sdk/metadata.go +++ b/sdk/metadata.go @@ -117,7 +117,7 @@ func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey string) (pm pm.PubKey = pubkey - thunk0 := sys.replaceableLoaders[0].Load(ctx, pubkey) + thunk0 := sys.replaceableLoaders[kind_0].Load(ctx, pubkey) evt, err := thunk0() if err == nil { pm, _ = ParseMetadata(evt) diff --git a/sdk/mutes.go b/sdk/mutes.go deleted file mode 100644 index d5a2a6e..0000000 --- a/sdk/mutes.go +++ /dev/null @@ -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 -} diff --git a/sdk/outbox.go b/sdk/outbox.go index 49cabb9..6ba4996 100644 --- a/sdk/outbox.go +++ b/sdk/outbox.go @@ -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 - 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) if len(relays) == 0 { diff --git a/sdk/relays.go b/sdk/relays.go deleted file mode 100644 index 75994a2..0000000 --- a/sdk/relays.go +++ /dev/null @@ -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 -} diff --git a/sdk/replaceable_loader.go b/sdk/replaceable_loader.go index 91fc962..694d14e 100644 --- a/sdk/replaceable_loader.go +++ b/sdk/replaceable_loader.go @@ -12,13 +12,38 @@ import ( "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] func (sys *System) initializeDataloaders() { - sys.replaceableLoaders = make(map[int]*dataloader.Loader[string, *nostr.Event]) - for _, kind := range []int{0, 3, 10000, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10015, 10030} { - sys.replaceableLoaders[kind] = sys.createReplaceableDataloader(kind) - } + sys.replaceableLoaders[kind_0] = sys.createReplaceableDataloader(0) + sys.replaceableLoaders[kind_3] = sys.createReplaceableDataloader(3) + 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] { diff --git a/sdk/system.go b/sdk/system.go index 52e964a..7809fb1 100644 --- a/sdk/system.go +++ b/sdk/system.go @@ -15,24 +15,28 @@ import ( ) type System struct { - RelayListCache cache.Cache32[RelayList] - FollowListCache cache.Cache32[FollowList] - MuteListCache cache.Cache32[FollowList] - MetadataCache cache.Cache32[ProfileMetadata] - Hints hints.HintsDB - Pool *nostr.SimplePool - RelayListRelays *RelayStream - FollowListRelays *RelayStream - MetadataRelays *RelayStream - FallbackRelays *RelayStream - JustIDRelays *RelayStream - UserSearchRelays *RelayStream - NoteSearchRelays *RelayStream - Store eventstore.Store + MetadataCache cache.Cache32[ProfileMetadata] + RelayListCache cache.Cache32[GenericList[Relay]] + FollowListCache cache.Cache32[GenericList[ProfileRef]] + MuteListCache cache.Cache32[GenericList[ProfileRef]] + BookmarkListCache cache.Cache32[GenericList[EventRef]] + PinListCache cache.Cache32[GenericList[EventRef]] + BlockedRelayListCache cache.Cache32[GenericList[RelayURL]] + SearchRelayListCache cache.Cache32[GenericList[RelayURL]] + Hints hints.HintsDB + Pool *nostr.SimplePool + RelayListRelays *RelayStream + FollowListRelays *RelayStream + MetadataRelays *RelayStream + FallbackRelays *RelayStream + JustIDRelays *RelayStream + UserSearchRelays *RelayStream + NoteSearchRelays *RelayStream + Store eventstore.Store StoreRelay nostr.RelayStore - replaceableLoaders map[int]*dataloader.Loader[string, *nostr.Event] + replaceableLoaders []*dataloader.Loader[string, *nostr.Event] outboxShortTermCache cache.Cache32[[]string] } @@ -54,13 +58,17 @@ func (rs *RelayStream) Next() string { func NewSystem(mods ...SystemModifier) *System { sys := &System{ - RelayListCache: cache_memory.New32[RelayList](1000), - FollowListCache: cache_memory.New32[FollowList](1000), - MuteListCache: cache_memory.New32[FollowList](1000), - MetadataCache: cache_memory.New32[ProfileMetadata](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"), + MetadataCache: cache_memory.New32[ProfileMetadata](8000), + RelayListCache: cache_memory.New32[GenericList[Relay]](8000), + FollowListCache: cache_memory.New32[GenericList[ProfileRef]](1000), + MuteListCache: cache_memory.New32[GenericList[ProfileRef]](1000), + BookmarkListCache: cache_memory.New32[GenericList[EventRef]](1000), + PinListCache: cache_memory.New32[GenericList[EventRef]](1000), + 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( "wss://relay.damus.io", "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) { sys.RelayListCache = cache } } -func WithFollowListCache(cache cache.Cache32[FollowList]) SystemModifier { +func WithFollowListCache(cache cache.Cache32[GenericList[ProfileRef]]) SystemModifier { return func(sys *System) { sys.FollowListCache = cache } } -func WithMuteListCache(cache cache.Cache32[FollowList]) SystemModifier { - return func(sys *System) { - sys.MuteListCache = cache - } -} - func WithMetadataCache(cache cache.Cache32[ProfileMetadata]) SystemModifier { return func(sys *System) { sys.MetadataCache = cache diff --git a/sdk/tracker.go b/sdk/tracker.go index 74827df..48da24e 100644 --- a/sdk/tracker.go +++ b/sdk/tracker.go @@ -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 { for _, relay := range ref.Profile.Relays { if IsVirtualRelay(relay) {