mirror of
https://github.com/aljazceru/njump.git
synced 2026-01-31 03:34:37 +01:00
fetch seenOn relays for individual events.
This commit is contained in:
6
data.go
6
data.go
@@ -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
4
go.mod
@@ -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
5
go.sum
@@ -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=
|
||||
|
||||
80
nostr.go
80
nostr.go
@@ -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 {
|
||||
|
||||
16
render.go
16
render.go
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
6
utils.go
6
utils.go
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user