fetch seenOn relays for individual events.

This commit is contained in:
fiatjaf
2023-10-02 15:17:39 -03:00
parent 08c16d371c
commit 4f3141f66a
7 changed files with 92 additions and 29 deletions

View File

@@ -25,6 +25,7 @@ type Event struct {
type Data struct {
typ string
event *nostr.Event
relays []string
npub string
npubShort string
nevent string
@@ -47,7 +48,7 @@ type Data struct {
func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, error) {
// code can be a nevent, nprofile, npub or nip05 identifier, in which case we try to fetch the associated event
event, err := getEvent(ctx, code)
event, relays, err := getEvent(ctx, code)
if err != nil {
log.Warn().Err(err).Str("code", code).Msg("failed to fetch event for code")
return nil, err
@@ -143,7 +144,7 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
if event.Kind != 0 {
ctx, cancel := context.WithTimeout(ctx, time.Second*3)
author, _ = getEvent(ctx, npub)
author, _, _ = getEvent(ctx, npub)
cancel()
}
@@ -191,6 +192,7 @@ func grabData(ctx context.Context, code string, isProfileSitemap bool) (*Data, e
return &Data{
typ: typ,
event: event,
relays: relays,
npub: npub,
npubShort: npubShort,
nevent: nevent,

4
go.mod
View File

@@ -1,6 +1,6 @@
module git.fiatjaf.com/njump
go 1.20
go 1.21
require (
github.com/apatters/go-wordwrap v1.0.0
@@ -9,7 +9,7 @@ require (
github.com/kelseyhightower/envconfig v1.4.0
github.com/lukevers/freetype-go v0.0.0-20150513150840-77e276735410
github.com/microcosm-cc/bluemonday v1.0.24
github.com/nbd-wtf/go-nostr v0.23.1
github.com/nbd-wtf/go-nostr v0.24.1
github.com/pelletier/go-toml v1.9.5
github.com/rs/cors v1.10.0
github.com/rs/zerolog v1.29.1

5
go.sum
View File

@@ -84,6 +84,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -113,8 +114,8 @@ github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO
github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nbd-wtf/go-nostr v0.23.1 h1:O2zHqPfGosbqBSGzwzL7S7zzJt57rY9HIKCMWIr2Lps=
github.com/nbd-wtf/go-nostr v0.23.1/go.mod h1:eE8Qf8QszZbCd9arBQyotXqATNUElWsTEEx+LLORhyQ=
github.com/nbd-wtf/go-nostr v0.24.1 h1:VqWDceiYTKZaOrizgwix/l/MXmDqqXHLqZd6rhiCxbo=
github.com/nbd-wtf/go-nostr v0.24.1/go.mod h1:eE8Qf8QszZbCd9arBQyotXqATNUElWsTEEx+LLORhyQ=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"math/rand"
"strings"
"time"
"github.com/nbd-wtf/go-nostr"
@@ -49,6 +50,11 @@ var (
}
)
type CachedEvent struct {
Event *nostr.Event `json:"e"`
Relays []string `json:"r"`
}
func getRelay() string {
if serial == 0 {
serial = rand.Intn(len(everything))
@@ -57,18 +63,26 @@ func getRelay() string {
return everything[serial]
}
func getEvent(ctx context.Context, code string) (*nostr.Event, error) {
func getEvent(ctx context.Context, code string) (*nostr.Event, []string, error) {
if b, ok := cache.Get(code); ok {
v := &nostr.Event{}
err := json.Unmarshal(b, v)
return v, err
// at this point `b` may be a StoredEvent json or an old naked Event json
// TODO: after a week we can assume everything will be StoredEvent, so we can simplify this
v := CachedEvent{}
err := json.Unmarshal(b, &v)
if v.Event == nil {
v.Event = &nostr.Event{}
err = json.Unmarshal(b, v.Event)
}
return v.Event, v.Relays, err
}
withRelays := true
prefix, data, err := nip19.Decode(code)
if err != nil {
pp, _ := nip05.QueryIdentifier(ctx, code)
if pp == nil {
return nil, fmt.Errorf("failed to decode %w", err)
return nil, nil, fmt.Errorf("failed to decode %w", err)
}
data = *pp
}
@@ -86,6 +100,7 @@ func getEvent(ctx context.Context, code string) (*nostr.Event, error) {
filter.Kinds = []int{0}
relays = append(relays, profiles...)
relays = append(relays, v.Relays...)
withRelays = false
case nostr.EventPointer:
author = v.Author
filter.IDs = []string{v.ID}
@@ -115,12 +130,16 @@ func getEvent(ctx context.Context, code string) (*nostr.Event, error) {
filter.Authors = []string{v}
filter.Kinds = []int{0}
relays = append(relays, profiles...)
withRelays = false
}
}
if author != "" {
// fetch relays for author
authorRelays := relaysForPubkey(ctx, author, relays...)
if len(authorRelays) > 5 {
authorRelays = authorRelays[:5]
}
relays = append(relays, authorRelays...)
}
@@ -131,17 +150,52 @@ func getEvent(ctx context.Context, code string) (*nostr.Event, error) {
relays = unique(relays)
ctx, cancel := context.WithTimeout(ctx, time.Second*8)
defer cancel()
if ie := pool.QuerySingle(ctx, relays, filter); ie.Event != nil {
b, err := json.Marshal(ie.Event)
if err != nil {
log.Error().Err(err).Stringer("event", ie.Event).Msg("error marshaling nson")
return ie.Event, nil
// actually fetch the event here
var result *nostr.Event
var successRelays []string = nil
if withRelays {
successRelays = make([]string, 0, len(relays))
countdown := 7.5
go func() {
for {
time.Sleep(500 * time.Millisecond)
if countdown <= 0 {
cancel()
break
}
countdown -= 0.5
}
}()
for ie := range pool.SubManyEoseNonUnique(ctx, relays, nostr.Filters{filter}) {
s := strings.TrimSuffix(
strings.TrimPrefix(
strings.TrimPrefix(
nostr.NormalizeURL(ie.Relay.URL),
"wss://",
),
"ws://",
),
"/",
)
successRelays = append(successRelays, s)
result = ie.Event
countdown = min(countdown, 1)
}
} else {
ie := pool.QuerySingle(ctx, relays, filter)
if ie != nil {
result = ie.Event
}
cache.SetWithTTL(code, []byte(b), time.Hour*24*7)
return ie.Event, nil
}
return nil, fmt.Errorf("couldn't find this %s", prefix)
if result == nil {
return nil, nil, fmt.Errorf("couldn't find this %s", prefix)
}
cache.SetJSONWithTTL(code, CachedEvent{Event: result, Relays: successRelays}, time.Hour*24*7)
return result, successRelays, nil
}
func getLastNotes(ctx context.Context, code string, limit int) []*nostr.Event {

View File

@@ -129,16 +129,20 @@ func render(w http.ResponseWriter, r *http.Request) {
}
seenOnRelays := ""
// event.seenOn && event.seenOn.length > 0
// ? `seen on [ ${event.seenOn.join(' ')} ]`
// : ''
if len(data.relays) > 0 {
seenOnRelays = fmt.Sprintf("seen on %s", strings.Join(data.relays, ", "))
}
textImageURL := ""
description := ""
if useTextImage {
textImageURL = fmt.Sprintf("https://%s/njump/image/%s", host, code)
if subject != "" {
description = fmt.Sprintf("%s -- %s", subject, seenOnRelays)
if seenOnRelays != "" {
description = fmt.Sprintf("%s -- %s", subject, seenOnRelays)
} else {
description = subject
}
} else {
description = seenOnRelays
}
@@ -224,8 +228,9 @@ func render(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Link", "<"+oembed+"&format=xml>; rel=\"alternate\"; type=\"text/xml+oembed\"")
}
// template
// template stuff
params := map[string]any{
"style": style,
"createdAt": data.createdAt,
"modifiedAt": data.modifiedAt,
"clients": generateClientList(code, data.event),
@@ -253,6 +258,7 @@ func render(w http.ResponseWriter, r *http.Request) {
"kindDescription": data.kindDescription,
"kindNIP": data.kindNIP,
"lastNotes": data.renderableLastNotes,
"seenOn": data.relays,
"parentNevent": data.parentNevent,
"authorRelays": data.authorRelays,
"oembed": oembed,

View File

@@ -33,7 +33,7 @@ func renderImage(w http.ResponseWriter, r *http.Request) {
return
}
event, err := getEvent(r.Context(), code)
event, _, err := getEvent(r.Context(), code)
if err != nil {
http.Error(w, "error fetching event: "+err.Error(), 404)
return
@@ -180,7 +180,7 @@ func renderQuotesAsBlockPrefixedText(ctx context.Context, input string) []string
submatch := nostrNoteNeventMatcher.FindStringSubmatch(matchText)
nip19 := submatch[0][6:]
event, err := getEvent(ctx, nip19)
event, _, err := getEvent(ctx, nip19)
if err != nil {
// error case concat this to previous block
blocks[b] += matchText

View File

@@ -179,7 +179,7 @@ func getPreviewStyle(r *http.Request) string {
case strings.Contains(ua, "iframely"):
return "iframely"
case strings.Contains(accept, "text/html"):
return ""
return "normal"
default:
return "unknown"
}
@@ -264,7 +264,7 @@ func shortenNostrURLs(input string) string {
}
func getNameFromNip19(ctx context.Context, nip19 string) string {
author, err := getEvent(ctx, nip19)
author, _, err := getEvent(ctx, nip19)
if err != nil {
return nip19
}
@@ -303,7 +303,7 @@ func renderQuotesAsHTML(ctx context.Context, input string, usingTelegramInstantV
submatch := nostrNoteNeventMatcher.FindStringSubmatch(match)
nip19 := submatch[1]
event, err := getEvent(ctx, nip19)
event, _, err := getEvent(ctx, nip19)
if err != nil {
log.Warn().Str("nip19", nip19).Msg("failed to get nip19")
return nip19