2024-10-06 15:53:33 -03:00
|
|
|
package sdk
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"slices"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/nbd-wtf/go-nostr"
|
|
|
|
"github.com/nbd-wtf/go-nostr/nip19"
|
2024-12-31 23:10:20 -03:00
|
|
|
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
2024-10-06 15:53:33 -03:00
|
|
|
)
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// FetchSpecificEventParameters contains options for fetching specific events.
|
2025-02-16 18:45:29 -03:00
|
|
|
type FetchSpecificEventParameters struct {
|
2025-03-04 11:08:31 -03:00
|
|
|
// WithRelays indicates whether to include relay information in the response
|
|
|
|
// (this causes the request to take longer as it will wait for all relays to respond).
|
|
|
|
WithRelays bool
|
|
|
|
|
|
|
|
// SkipLocalStore indicates whether to skip checking the local store for the event
|
|
|
|
// and storing the result in the local store.
|
2025-02-16 18:45:29 -03:00
|
|
|
SkipLocalStore bool
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// FetchSpecificEventFromInput tries to get a specific event from a NIP-19 code or event ID.
|
|
|
|
// It supports nevent, naddr, and note NIP-19 codes, as well as raw event IDs.
|
2025-01-05 14:18:53 -03:00
|
|
|
func (sys *System) FetchSpecificEventFromInput(
|
|
|
|
ctx context.Context,
|
|
|
|
input string,
|
2025-02-16 18:45:29 -03:00
|
|
|
params FetchSpecificEventParameters,
|
2025-01-05 14:18:53 -03:00
|
|
|
) (event *nostr.Event, successRelays []string, err error) {
|
|
|
|
var pointer nostr.Pointer
|
|
|
|
|
2025-01-23 14:44:36 -03:00
|
|
|
prefix, data, err := nip19.Decode(input)
|
2025-01-05 14:18:53 -03:00
|
|
|
if err == nil {
|
2025-01-23 14:44:36 -03:00
|
|
|
switch prefix {
|
|
|
|
case "nevent":
|
|
|
|
pointer = data.(nostr.EventPointer)
|
|
|
|
case "naddr":
|
|
|
|
pointer = data.(nostr.EntityPointer)
|
|
|
|
case "note":
|
|
|
|
pointer = nostr.EventPointer{ID: data.(string)}
|
2025-01-05 14:18:53 -03:00
|
|
|
default:
|
|
|
|
return nil, nil, fmt.Errorf("invalid code '%s'", input)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if nostr.IsValid32ByteHex(input) {
|
|
|
|
pointer = nostr.EventPointer{ID: input}
|
|
|
|
} else {
|
|
|
|
return nil, nil, fmt.Errorf("failed to decode '%s': %w", input, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-16 18:45:29 -03:00
|
|
|
return sys.FetchSpecificEvent(ctx, pointer, params)
|
2025-01-05 14:18:53 -03:00
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// FetchSpecificEvent tries to get a specific event using a Pointer (EventPointer or EntityPointer).
|
|
|
|
// It first checks the local store, then queries relays associated with the event or author,
|
|
|
|
// and finally falls back to general-purpose relays.
|
2024-10-06 15:53:33 -03:00
|
|
|
func (sys *System) FetchSpecificEvent(
|
|
|
|
ctx context.Context,
|
2025-01-05 14:18:53 -03:00
|
|
|
pointer nostr.Pointer,
|
2025-02-16 18:45:29 -03:00
|
|
|
params FetchSpecificEventParameters,
|
2024-10-06 15:53:33 -03:00
|
|
|
) (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)
|
|
|
|
|
|
|
|
var filter nostr.Filter
|
2025-01-05 14:18:53 -03:00
|
|
|
author := ""
|
2024-10-06 15:53:33 -03:00
|
|
|
relays := make([]string, 0, 10)
|
|
|
|
fallback := make([]string, 0, 10)
|
|
|
|
successRelays = make([]string, 0, 10)
|
|
|
|
|
2025-01-05 14:18:53 -03:00
|
|
|
switch v := pointer.(type) {
|
2024-10-06 15:53:33 -03:00
|
|
|
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())
|
2024-12-31 23:10:20 -03:00
|
|
|
priorityRelays = append(priorityRelays, v.Relays...)
|
2024-10-06 15:53:33 -03:00
|
|
|
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())
|
2024-12-31 23:10:20 -03:00
|
|
|
priorityRelays = append(priorityRelays, v.Relays...)
|
2024-10-06 15:53:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// try to fetch in our internal eventstore first
|
2025-02-16 18:45:29 -03:00
|
|
|
if !params.SkipLocalStore {
|
|
|
|
if res, _ := sys.StoreRelay.QuerySync(ctx, filter); len(res) != 0 {
|
|
|
|
evt := res[0]
|
|
|
|
return evt, nil, nil
|
|
|
|
}
|
2024-10-06 15:53:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
if author != "" {
|
|
|
|
// fetch relays for author
|
|
|
|
authorRelays := sys.FetchOutboxRelays(ctx, author, 3)
|
2024-12-31 23:10:20 -03:00
|
|
|
|
|
|
|
// after that we register these hints as associated with author
|
|
|
|
// (we do this after fetching author outbox relays because we are already going to prioritize these hints)
|
|
|
|
now := nostr.Now()
|
|
|
|
for _, relay := range priorityRelays {
|
2025-01-18 18:19:54 -03:00
|
|
|
sys.Hints.Save(author, nostr.NormalizeURL(relay), hints.LastInHint, now)
|
2024-12-31 23:10:20 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// arrange these
|
2024-10-06 15:53:33 -03:00
|
|
|
relays = appendUnique(relays, authorRelays...)
|
|
|
|
priorityRelays = appendUnique(priorityRelays, authorRelays...)
|
|
|
|
}
|
|
|
|
|
|
|
|
var result *nostr.Event
|
2024-10-14 14:46:59 -03:00
|
|
|
fetchProfileOnce := sync.Once{}
|
2024-10-06 15:53:33 -03:00
|
|
|
|
2024-10-14 14:46:59 -03:00
|
|
|
attempts:
|
2024-10-06 15:53:33 -03:00
|
|
|
for _, attempt := range []struct {
|
2024-10-14 14:46:59 -03:00
|
|
|
label string
|
|
|
|
relays []string
|
|
|
|
slowWithRelays bool
|
2024-10-06 15:53:33 -03:00
|
|
|
}{
|
|
|
|
{
|
2025-01-05 14:18:53 -03:00
|
|
|
label: "fetchspecific",
|
2024-10-06 15:53:33 -03:00
|
|
|
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
|
2025-02-16 18:45:29 -03:00
|
|
|
slowWithRelays: params.WithRelays,
|
2024-10-06 15:53:33 -03:00
|
|
|
},
|
|
|
|
{
|
2025-01-05 14:18:53 -03:00
|
|
|
label: "fetchspecific",
|
2024-10-14 14:46:59 -03:00
|
|
|
relays: fallback,
|
|
|
|
slowWithRelays: false,
|
2024-10-06 15:53:33 -03:00
|
|
|
},
|
|
|
|
} {
|
|
|
|
// actually fetch the event here
|
|
|
|
countdown := 6.0
|
|
|
|
subManyCtx := ctx
|
|
|
|
|
2025-02-16 18:45:29 -03:00
|
|
|
for ie := range sys.Pool.FetchMany(
|
2024-10-06 15:53:33 -03:00
|
|
|
subManyCtx,
|
|
|
|
attempt.relays,
|
2025-02-16 18:45:29 -03:00
|
|
|
filter,
|
2024-10-06 15:53:33 -03:00
|
|
|
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
|
|
|
|
}
|
2024-10-14 14:46:59 -03:00
|
|
|
|
|
|
|
if !attempt.slowWithRelays {
|
|
|
|
break attempts
|
|
|
|
}
|
|
|
|
|
2024-10-06 15:53:33 -03:00
|
|
|
countdown = min(countdown-0.5, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if result == nil {
|
2025-01-05 14:18:53 -03:00
|
|
|
return nil, nil, fmt.Errorf("couldn't find this %v", pointer)
|
2024-10-06 15:53:33 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// save stuff in cache and in internal store
|
2025-02-16 18:45:29 -03:00
|
|
|
if !params.SkipLocalStore {
|
|
|
|
sys.StoreRelay.Publish(ctx, *result)
|
|
|
|
}
|
2024-10-06 15:53:33 -03:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|