diff --git a/sdk/system.go b/sdk/system.go index 4923396..291398c 100644 --- a/sdk/system.go +++ b/sdk/system.go @@ -40,6 +40,7 @@ type System struct { RelaySetsCache cache.Cache32[GenericSets[string, RelayURL]] FollowSetsCache cache.Cache32[GenericSets[nostr.PubKey, ProfileRef]] TopicSetsCache cache.Cache32[GenericSets[string, Topic]] + ZapProviderCache cache.Cache32[nostr.PubKey] Hints hints.HintsDB Pool *nostr.Pool RelayListRelays *RelayStream @@ -131,6 +132,9 @@ func NewSystem() *System { if sys.RelayListCache == nil { sys.RelayListCache = cache_memory.New[GenericList[string, Relay]](8000) } + if sys.ZapProviderCache == nil { + sys.ZapProviderCache = cache_memory.New[nostr.PubKey](8000) + } if sys.Store == nil { sys.Store = &nullstore.NullStore{} diff --git a/sdk/zap.go b/sdk/zap.go new file mode 100644 index 0000000..d9d2dd6 --- /dev/null +++ b/sdk/zap.go @@ -0,0 +1,49 @@ +package sdk + +import ( + "context" + "io" + "net/http" + "strings" + "time" + + "fiatjaf.com/nostr" + "github.com/tidwall/gjson" +) + +// FetchZapProvider fetches the zap provider public key for a given user from their profile metadata. +// It uses a cache to avoid repeated fetches. If no zap provider is set in the profile, returns an empty PubKey. +func (sys *System) FetchZapProvider(ctx context.Context, pk nostr.PubKey) nostr.PubKey { + if v, ok := sys.ZapProviderCache.Get(pk); ok { + return v + } + + pm := sys.FetchProfileMetadata(ctx, pk) + + if pm.LUD16 != "" { + parts := strings.Split(pm.LUD16, "@") + if len(parts) == 2 { + url := "http://" + parts[1] + "/.well-known/lnurlp/" + parts[0] + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err == nil { + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err == nil { + defer resp.Body.Close() + if body, err := io.ReadAll(resp.Body); err == nil { + gj := gjson.ParseBytes(body) + if gj.Get("allowsNostr").Type == gjson.True { + if pk, err := nostr.PubKeyFromHex(gj.Get("nostrPubkey").Str); err == nil { + sys.ZapProviderCache.SetWithTTL(pk, pk, time.Hour*6) + return pk + } + } + } + } + } + } + } + + sys.ZapProviderCache.SetWithTTL(pk, nostr.ZeroPK, time.Hour*2) + return nostr.ZeroPK +} diff --git a/sdk/zap_test.go b/sdk/zap_test.go new file mode 100644 index 0000000..c84f081 --- /dev/null +++ b/sdk/zap_test.go @@ -0,0 +1,22 @@ +package sdk + +import ( + "context" + "testing" + + "fiatjaf.com/nostr" + "github.com/stretchr/testify/require" +) + +func TestFetchZapProvider(t *testing.T) { + sys := NewSystem() + ctx := context.Background() + + pk, err := nostr.PubKeyFromHex("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52") + require.NoError(t, err) + + zp := sys.FetchZapProvider(ctx, pk) + expected, err := nostr.PubKeyFromHex("f81611363554b64306467234d7396ec88455707633f54738f6c4683535098cd3") + require.NoError(t, err) + require.Equal(t, expected, zp) +}