diff --git a/cache.go b/cache.go index e308468..a60a4f9 100644 --- a/cache.go +++ b/cache.go @@ -4,6 +4,7 @@ package main import ( "encoding/json" + "strings" "time" "github.com/dgraph-io/badger" @@ -59,6 +60,38 @@ func (c *Cache) Get(key string) ([]byte, bool) { return val, true } +func (c *Cache) GetPaginatedkeys(prefix string, page int, size int) []string { + keys := []string{} + err := c.DB.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false + it := txn.NewIterator(opts) + defer it.Close() + start := (page-1)*size + 1 + index := 1 + for it.Seek([]byte(prefix)); it.ValidForPrefix([]byte(prefix)); it.Next() { + if index < start { + index++ + continue + } + if index > start+size-1 { + break + } + item := it.Item() + k := item.Key() + keys = append(keys, strings.TrimPrefix(string(k), prefix+":")) + index++ + } + return nil + }) + + if err != nil { + log.Fatal().Err(err).Msg("") + } + + return keys +} + func (c *Cache) GetJSON(key string, recv any) bool { b, ok := c.Get(key) if !ok { diff --git a/main.go b/main.go index a798b74..c7e64e5 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,14 @@ package main import ( + "context" "embed" + "fmt" "html" "net/http" "os" "text/template" + "time" "github.com/kelseyhightower/envconfig" "github.com/rs/zerolog" @@ -30,6 +33,21 @@ var ( log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() ) +func updateArchives(ctx context.Context) { + for { + select { + // Check for the cancellation signal. + case <-ctx.Done(): + fmt.Println("Exit updateArchives gracefully...") + return + default: + loadNpubsArchive(ctx) + loadRelaysArchive(ctx) + } + time.Sleep(24 * time.Hour) + } +} + func main() { err := envconfig.Process("", &s) if err != nil { @@ -39,6 +57,10 @@ func main() { // initialize disk cache defer cache.initialize()() + // initialize the function to update the npubs/relays archive + ctx := context.Background() + go updateArchives(ctx) + // initialize templates // use a mapping to expressly link the templates and share them between more kinds/types templateMapping["profile"] = "profile.html" @@ -53,6 +75,7 @@ func main() { "mdToHTML": mdToHTML, "escapeString": html.EscapeString, "sanitizeXSS": sanitizeXSS, + "trimProtocol": trimProtocol, } tmpl = template.Must( @@ -65,10 +88,15 @@ func main() { http.HandleFunc("/njump/image/", generate) http.HandleFunc("/njump/proxy/", proxy) http.Handle("/njump/static/", http.StripPrefix("/njump/", http.FileServer(http.FS(static)))) + http.HandleFunc("/npubs-archive/", renderArchive) + http.HandleFunc("/relays-archive/", renderArchive) http.HandleFunc("/", render) log.Print("listening at http://0.0.0.0:" + s.Port) if err := http.ListenAndServe("0.0.0.0:"+s.Port, nil); err != nil { log.Fatal().Err(err).Msg("") } + + select {} + } diff --git a/nostr.go b/nostr.go index 03c0a9e..982f4ae 100644 --- a/nostr.go +++ b/nostr.go @@ -34,6 +34,17 @@ var ( profiles = []string{ "wss://purplepag.es", } + + trustedPubKeys = []string{ + "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805", // dtonon + "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf + "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", // hodlbod + "ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49", // Michael Dilger + } + + excludedRelays = []string{ + "wss://filter.nostr.wine", // paid + } ) func getRelay() string { @@ -159,7 +170,7 @@ func getLastNotes(ctx context.Context, code string, limit int) []*nostr.Event { for event := range events { lastNotes = nostr.InsertEventIntoDescendingList(lastNotes, event) } - + return lastNotes } @@ -180,3 +191,40 @@ func relaysForPubkey(ctx context.Context, pubkey string, extraRelays ...string) pubkeyRelays = unique(pubkeyRelays) return pubkeyRelays } + +func contactsForPubkey(ctx context.Context, pubkey string, extraRelays ...string) []string { + pubkeyContacts := make([]string, 0, 100) + relays := make([]string, 0, 12) + if ok := cache.GetJSON("cc:"+pubkey, &pubkeyContacts); !ok { + fmt.Printf("Searching contacts for %s\n", pubkey) + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*1500) + + pubkeyRelays := relaysForPubkey(ctx, pubkey, relays...) + relays = append(relays, pubkeyRelays...) + relays = append(relays, always...) + relays = append(relays, profiles...) + + ch := pool.SubManyEose(ctx, relays, nostr.Filters{ + { + Kinds: []int{3}, + Authors: []string{pubkey}, + Limit: 2, + }, + }) + + for event := range ch { + for _, tag := range event.Tags { + if tag[0] == "p" { + pubkeyContacts = append(pubkeyContacts, tag[1]) + } + } + } + + cancel() + if len(pubkeyContacts) > 0 { + cache.SetJSONWithTTL("cc:"+pubkey, pubkeyContacts, time.Hour*6) + } + } + pubkeyContacts = unique(pubkeyContacts) + return pubkeyContacts +} diff --git a/null_cache.go b/null_cache.go index d53f27f..bd6e2a2 100644 --- a/null_cache.go +++ b/null_cache.go @@ -10,10 +10,11 @@ var cache = Cache{} type Cache struct{} -func (c *Cache) initialize() func() { return func() {} } -func (c *Cache) Get(key string) ([]byte, bool) { return nil, false } -func (c *Cache) GetJSON(key string, recv any) bool { return false } -func (c *Cache) Set(key string, value []byte) {} -func (c *Cache) SetJSON(key string, value any) {} -func (c *Cache) SetWithTTL(key string, value []byte, ttl time.Duration) {} -func (c *Cache) SetJSONWithTTL(key string, value any, ttl time.Duration) {} +func (c *Cache) initialize() func() { return func() {} } +func (c *Cache) Get(key string) ([]byte, bool) { return nil, false } +func (c *Cache) GetJSON(key string, recv any) bool { return false } +func (c *Cache) Set(key string, value []byte) {} +func (c *Cache) SetJSON(key string, value any) {} +func (c *Cache) SetWithTTL(key string, value []byte, ttl time.Duration) {} +func (c *Cache) SetJSONWithTTL(key string, value any, ttl time.Duration) {} +func (c *Cache) GetPaginatedkeys(prefix string, page int, size int) []string { return []string{} } diff --git a/render_archive.go b/render_archive.go new file mode 100644 index 0000000..bd1b30a --- /dev/null +++ b/render_archive.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/nbd-wtf/go-nostr/nip19" +) + +func renderArchive(w http.ResponseWriter, r *http.Request) { + resultsPerPage := 50 + lastIndex := strings.LastIndex(r.URL.Path, "/") + page := 1 + if lastIndex != -1 { + pageString := r.URL.Path[lastIndex+1:] + pageInt, err := strconv.Atoi(pageString) + if err != nil { + page = 1 + } else { + page = pageInt + } + } + + prefix := "" + path_prefix := "" + title := "" + area := strings.Split(r.URL.Path[1:], "/")[0] + if area == "npubs-archive" { + prefix = "pa" + path_prefix = "" + title = "Nostr npubs archive" + } else { + prefix = "ra" + path_prefix = "r/" + title = "Nostr relays archive" + } + + keys := cache.GetPaginatedkeys(prefix, page, resultsPerPage) + data := []string{} + for i := 0; i < len(keys); i++ { + if area == "npubs-archive" { + npub, _ := nip19.EncodePublicKey(keys[i]) + data = append(data, npub) + } else { + data = append(data, keys[i]) + } + } + + prevPage := page - 1 + nextPage := page + 1 + if len(keys) == 0 { + prevPage = 0 + nextPage = 0 + } + + params := map[string]any{ + "title": title, + "pathPrefix": path_prefix, + "data": data, + "paginationUrl": area, + "nextPage": fmt.Sprint(nextPage), + "prevPage": fmt.Sprint(prevPage), + } + + w.Header().Set("Cache-Control", "max-age=86400") + + if err := tmpl.ExecuteTemplate(w, "archive.html", params); err != nil { + log.Error().Err(err).Msg("error rendering") + return + } +} diff --git a/static/styles.css b/static/styles.css index 165e56e..6b3aae7 100644 --- a/static/styles.css +++ b/static/styles.css @@ -712,6 +712,15 @@ iframe { color: #969696; } } +.container .column_content a.pagination { + color: #e32a6d; +} +.container .column_content a.pagination.next { + float: right; +} +.container .column_content a.pagination.prev { + float: left; +} .container .column_clients { position: -webkit-sticky; position: sticky; diff --git a/static/styles.scss b/static/styles.scss index f401eeb..4a9f8f3 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -657,6 +657,15 @@ iframe { } } } + a.pagination { + color: $color-accent1; + &.next { + float: right; + } + &.prev { + float: left; + } + } } .column_clients { diff --git a/templates/archive.html b/templates/archive.html new file mode 100644 index 0000000..32abd80 --- /dev/null +++ b/templates/archive.html @@ -0,0 +1,62 @@ + + + +
+